From c59337962804b00e48ed996fd6230f3924d05ad2 Mon Sep 17 00:00:00 2001 From: Simeon Cheeseman Date: Thu, 24 Mar 2022 13:38:04 +0900 Subject: [PATCH] [FIX] `function-component-definition`: replace `var` by `const` in certain situations Preserves existing variable definition if it exists. --- lib/rules/function-component-definition.js | 35 ++++++++++++--- .../rules/function-component-definition.js | 43 +++++++++++++++---- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/lib/rules/function-component-definition.js b/lib/rules/function-component-definition.js index f5dd96e16e..f326db3c99 100644 --- a/lib/rules/function-component-definition.js +++ b/lib/rules/function-component-definition.js @@ -21,8 +21,8 @@ function buildFunction(template, parts) { const NAMED_FUNCTION_TEMPLATES = { 'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}', - 'arrow-function': 'const {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}', - 'function-expression': 'const {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}', + 'arrow-function': '{varType} {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}', + 'function-expression': '{varType} {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}', }; const UNNAMED_FUNCTION_TEMPLATES = { @@ -145,6 +145,7 @@ module.exports = { create: Components.detect((context, components) => { const configuration = context.options[0] || {}; + let fileVarType = 'var'; const namedConfig = [].concat(configuration.namedComponents || 'function-declaration'); const unnamedConfig = [].concat(configuration.unnamedComponents || 'function-expression'); @@ -159,6 +160,10 @@ module.exports = { if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) return; if (isUnfixableBecauseOfExport(node)) return; if (isFunctionExpressionWithName(node)) return; + let varType = fileVarType; + if ((node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && node.parent.type === 'VariableDeclarator') { + varType = node.parent.parent.kind; + } return (fixer) => fixer.replaceTextRange(options.range, buildFunction(options.template, { typeAnnotation, @@ -167,6 +172,7 @@ module.exports = { returnType: getNodeText(node.returnType, source), body: getBody(node, source), name: getName(node), + varType, })); } @@ -209,11 +215,28 @@ module.exports = { // -------------------------------------------------------------------------- // Public // -------------------------------------------------------------------------- - + const validatePairs = []; + let hasEs6OrJsx = false; return { - FunctionDeclaration(node) { validate(node, 'function-declaration'); }, - ArrowFunctionExpression(node) { validate(node, 'arrow-function'); }, - FunctionExpression(node) { validate(node, 'function-expression'); }, + FunctionDeclaration(node) { + validatePairs.push([node, 'function-declaration']); + }, + ArrowFunctionExpression(node) { + validatePairs.push([node, 'arrow-function']); + }, + FunctionExpression(node) { + validatePairs.push([node, 'function-expression']); + }, + VariableDeclaration(node) { + hasEs6OrJsx = hasEs6OrJsx || node.kind === 'const' || node.kind === 'let'; + }, + JSXElement() { + hasEs6OrJsx = true; + }, + 'Program:exit'() { + if (hasEs6OrJsx) fileVarType = 'const'; + validatePairs.forEach((pair) => validate(pair[0], pair[1])); + }, }; }), }; diff --git a/tests/lib/rules/function-component-definition.js b/tests/lib/rules/function-component-definition.js index 2da6396a99..dc872bfed2 100644 --- a/tests/lib/rules/function-component-definition.js +++ b/tests/lib/rules/function-component-definition.js @@ -57,6 +57,10 @@ ruleTester.run('function-component-definition', rule, { code: 'var Hello = (props) => { return
}', options: [{ namedComponents: 'arrow-function' }], }, + { + code: 'const Hello = (props) => { return
}', + options: [{ namedComponents: 'arrow-function' }], + }, { code: 'function Hello(props) { return
}', options: [{ namedComponents: 'function-declaration' }], @@ -65,6 +69,10 @@ ruleTester.run('function-component-definition', rule, { code: 'var Hello = function(props) { return
}', options: [{ namedComponents: 'function-expression' }], }, + { + code: 'const Hello = function(props) { return
}', + options: [{ namedComponents: 'function-expression' }], + }, { code: 'function Hello() { return function() { return
} }', options: [{ unnamedComponents: 'function-expression' }], @@ -77,6 +85,10 @@ ruleTester.run('function-component-definition', rule, { code: 'var Foo = React.memo(function Foo() { return

})', options: [{ namedComponents: 'function-declaration' }], }, + { + code: 'const Foo = React.memo(function Foo() { return

})', + options: [{ namedComponents: 'function-declaration' }], + }, { // shouldn't trigger this rule since functions stating with a lowercase // letter are not considered components @@ -404,7 +416,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = (props) => { + const Hello = (props) => { return

; } `, @@ -474,7 +486,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = function(props) { + const Hello = function(props) { return
; } `, @@ -554,7 +566,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = (props: Test) => { + const Hello = (props: Test) => { return
; } `, @@ -584,7 +596,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = function(props: Test) { + const Hello = function(props: Test) { return
; } `, @@ -592,6 +604,21 @@ ruleTester.run('function-component-definition', rule, { errors: [{ messageId: 'function-expression' }], features: ['types'], }, + { + code: ` + function Hello(props: Test) { + return React.createElement('div'); + } + `, + output: ` + var Hello = function(props: Test) { + return React.createElement('div'); + } + `, + options: [{ namedComponents: 'function-expression' }], + errors: [{ messageId: 'function-expression' }], + features: ['types'], + }, { code: ` var Hello = (props: Test) => { @@ -674,7 +701,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = (props: Test) => { + const Hello = (props: Test) => { return
; } `, @@ -704,7 +731,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = function(props: Test) { + const Hello = function(props: Test) { return
; } `, @@ -874,7 +901,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - export var Hello = (props) => { + export const Hello = (props) => { return
; } `, @@ -934,7 +961,7 @@ ruleTester.run('function-component-definition', rule, { } `, output: ` - var Hello = (props) => { + const Hello = (props) => { return
; } `,