From 8e1a94b67d081fdc132e9a7e175db3fbf2e02956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opacin=CC=81ski?= Date: Sun, 28 Apr 2024 15:21:37 +0200 Subject: [PATCH] [Refactor] create getScope util; `context.getScope` is deprecated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Łopaciński Co-authored-by: Jordan Harband --- lib/rules/destructuring-assignment.js | 11 +-- lib/rules/forbid-prop-types.js | 2 +- lib/rules/jsx-fragments.js | 6 +- lib/rules/jsx-max-depth.js | 2 +- .../jsx-no-constructed-context-values.js | 3 +- lib/rules/jsx-no-leaked-render.js | 2 +- lib/rules/jsx-no-undef.js | 2 +- lib/rules/jsx-sort-default-props.js | 9 +- lib/rules/no-access-state-in-setstate.js | 22 +++-- lib/rules/no-array-index-key.js | 2 +- lib/rules/no-danger-with-children.js | 12 +-- lib/rules/no-direct-mutation-state.js | 4 +- lib/rules/no-set-state.js | 2 +- lib/rules/no-string-refs.js | 2 +- lib/rules/no-this-in-sfc.js | 2 +- lib/rules/no-unstable-nested-components.js | 6 +- lib/rules/no-unused-state.js | 5 +- lib/rules/prefer-exact-props.js | 2 +- lib/rules/prefer-stateless-function.js | 3 +- lib/rules/react-in-jsx-scope.js | 2 +- lib/rules/require-optimization.js | 14 ++-- lib/rules/sort-default-props.js | 8 +- lib/rules/sort-prop-types.js | 4 +- lib/rules/state-in-constructor.js | 6 +- lib/rules/static-property-placement.js | 16 ++-- lib/rules/style-prop-object.js | 3 +- lib/util/Components.js | 46 ++++++----- lib/util/ast.js | 6 +- lib/util/componentUtil.js | 15 ++-- lib/util/defaultProps.js | 6 +- lib/util/eslint.js | 10 +++ lib/util/isCreateElement.js | 2 +- lib/util/isDestructuredFromPragmaImport.js | 5 +- lib/util/jsx.js | 2 +- lib/util/propTypes.js | 82 ++++++++++--------- lib/util/usedPropTypes.js | 39 +++++---- lib/util/variable.js | 11 ++- 37 files changed, 218 insertions(+), 158 deletions(-) diff --git a/lib/rules/destructuring-assignment.js b/lib/rules/destructuring-assignment.js index 9cac8d5889..b4aa3da159 100644 --- a/lib/rules/destructuring-assignment.js +++ b/lib/rules/destructuring-assignment.js @@ -10,6 +10,7 @@ const eslintUtil = require('../util/eslint'); const isAssignmentLHS = require('../util/ast').isAssignmentLHS; const report = require('../util/report'); +const getScope = eslintUtil.getScope; const getText = eslintUtil.getText; const DEFAULT_OPTION = 'always'; @@ -105,7 +106,7 @@ module.exports = { function handleStatelessComponent(node) { const params = evalParams(node.params); - const SFCComponent = components.get(context.getScope(node).block); + const SFCComponent = components.get(getScope(context, node).block); if (!SFCComponent) { return; } @@ -123,7 +124,7 @@ module.exports = { } function handleStatelessComponentExit(node) { - const SFCComponent = components.get(context.getScope(node).block); + const SFCComponent = components.get(getScope(context, node).block); if (SFCComponent) { sfcParams.pop(); } @@ -195,7 +196,7 @@ module.exports = { 'FunctionExpression:exit': handleStatelessComponentExit, MemberExpression(node) { - let scope = context.getScope(node); + let scope = getScope(context, node); let SFCComponent = components.get(scope.block); while (!SFCComponent && scope.upper && scope.upper !== scope) { SFCComponent = components.get(scope.upper.block); @@ -213,7 +214,7 @@ module.exports = { VariableDeclarator(node) { const classComponent = utils.getParentComponent(node); - const SFCComponent = components.get(context.getScope(node).block); + const SFCComponent = components.get(getScope(context, node).block); const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); // let {foo} = props; @@ -251,7 +252,7 @@ module.exports = { && destructureInSignature === 'always' && node.init.name === 'props' ) { - const scopeSetProps = context.getScope().set.get('props'); + const scopeSetProps = getScope(context, node).set.get('props'); const propsRefs = scopeSetProps && scopeSetProps.references; if (!propsRefs) { return; diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js index 89f5c4b2d8..b561b26da1 100644 --- a/lib/rules/forbid-prop-types.js +++ b/lib/rules/forbid-prop-types.js @@ -163,7 +163,7 @@ module.exports = { checkProperties(node.properties); break; case 'Identifier': { - const propTypesObject = variableUtil.findVariableByName(context, node.name); + const propTypesObject = variableUtil.findVariableByName(context, node, node.name); if (propTypesObject && propTypesObject.properties) { checkProperties(propTypesObject.properties); } diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js index 770cbd087f..4dadb076d7 100644 --- a/lib/rules/jsx-fragments.js +++ b/lib/rules/jsx-fragments.js @@ -102,8 +102,8 @@ module.exports = { }; } - function refersToReactFragment(name) { - const variableInit = variableUtil.findVariableByName(context, name); + function refersToReactFragment(node, name) { + const variableInit = variableUtil.findVariableByName(context, node, name); if (!variableInit) { return false; } @@ -184,7 +184,7 @@ module.exports = { const openingEl = node.openingElement; const elName = elementType(openingEl); - if (fragmentNames.has(elName) || refersToReactFragment(elName)) { + if (fragmentNames.has(elName) || refersToReactFragment(node, elName)) { if (reportOnReactVersion(node)) { return; } diff --git a/lib/rules/jsx-max-depth.js b/lib/rules/jsx-max-depth.js index 01698264c7..6b1db78189 100644 --- a/lib/rules/jsx-max-depth.js +++ b/lib/rules/jsx-max-depth.js @@ -150,7 +150,7 @@ module.exports = { return; } - const variables = variableUtil.variablesInScope(context); + const variables = variableUtil.variablesInScope(context, node); const element = findJSXElementOrFragment(variables, node.expression.name, []); if (element) { diff --git a/lib/rules/jsx-no-constructed-context-values.js b/lib/rules/jsx-no-constructed-context-values.js index f28c51fd4a..140bcde42c 100644 --- a/lib/rules/jsx-no-constructed-context-values.js +++ b/lib/rules/jsx-no-constructed-context-values.js @@ -8,6 +8,7 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); +const getScope = require('../util/eslint').getScope; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -180,7 +181,7 @@ module.exports = { } const valueExpression = valueNode.expression; - const invocationScope = context.getScope(); + const invocationScope = getScope(context, node); // Check if the value prop is a construction const constructInfo = isConstruction(valueExpression, invocationScope); diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index 2cbe705561..f7eea1ad52 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -161,7 +161,7 @@ module.exports = { if (isCoerceValidLeftSide || getIsCoerceValidNestedLogicalExpression(leftSide)) { return; } - const variables = variableUtil.variablesInScope(context); + const variables = variableUtil.variablesInScope(context, node); const leftSideVar = variableUtil.getVariable(variables, leftSide.name); if (leftSideVar) { const leftSideValue = leftSideVar.defs diff --git a/lib/rules/jsx-no-undef.js b/lib/rules/jsx-no-undef.js index 656602c69b..a15de8e854 100644 --- a/lib/rules/jsx-no-undef.js +++ b/lib/rules/jsx-no-undef.js @@ -51,7 +51,7 @@ module.exports = { * @returns {void} */ function checkIdentifierInJSX(node) { - let scope = context.getScope(); + let scope = eslintUtil.getScope(context, node); const sourceCode = eslintUtil.getSourceCode(context); const sourceType = sourceCode.ast.sourceType; const scopeUpperBound = !allowGlobals && sourceType === 'module' ? 'module' : 'global'; diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index f65981ec64..89b7bb64cc 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -91,11 +91,14 @@ module.exports = { /** * Find a variable by name in the current scope. + * @param {ASTNode} node The node to look for. * @param {string} name Name of the variable to look for. * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. */ - function findVariableByName(name) { - const variable = variableUtil.variablesInScope(context).find((item) => item.name === name); + function findVariableByName(node, name) { + const variable = variableUtil + .variablesInScope(context, node) + .find((item) => item.name === name); if (!variable || !variable.defs[0] || !variable.defs[0].node) { return null; @@ -151,7 +154,7 @@ module.exports = { if (node.type === 'ObjectExpression') { checkSorted(node.properties); } else if (node.type === 'Identifier') { - const propTypesObject = findVariableByName(node.name); + const propTypesObject = findVariableByName(node, node.name); if (propTypesObject && propTypesObject.properties) { checkSorted(propTypesObject.properties); } diff --git a/lib/rules/no-access-state-in-setstate.js b/lib/rules/no-access-state-in-setstate.js index 89d4976077..be72ea42e4 100644 --- a/lib/rules/no-access-state-in-setstate.js +++ b/lib/rules/no-access-state-in-setstate.js @@ -8,6 +8,7 @@ const docsUrl = require('../util/docsUrl'); const componentUtil = require('../util/componentUtil'); const report = require('../util/report'); +const getScope = require('../util/eslint').getScope; // ------------------------------------------------------------------------------ // Rule Definition @@ -47,8 +48,15 @@ module.exports = { return current.arguments[0] === node; } - function isClassComponent() { - return !!(componentUtil.getParentES6Component(context) || componentUtil.getParentES5Component(context)); + /** + * @param {ASTNode} node + * @returns {boolean} + */ + function isClassComponent(node) { + return !!( + componentUtil.getParentES6Component(context, node) + || componentUtil.getParentES5Component(context, node) + ); } // The methods array contains all methods or functions that are using this.state @@ -58,7 +66,7 @@ module.exports = { const vars = []; return { CallExpression(node) { - if (!isClassComponent()) { + if (!isClassComponent(node)) { return; } // Appends all the methods that are calling another @@ -103,7 +111,7 @@ module.exports = { if ( node.property.name === 'state' && node.object.type === 'ThisExpression' - && isClassComponent() + && isClassComponent(node) ) { let current = node; while (current.type !== 'Program') { @@ -134,7 +142,7 @@ module.exports = { if (current.type === 'VariableDeclarator') { vars.push({ node, - scope: context.getScope(), + scope: getScope(context, node), variableName: current.id.name, }); break; @@ -158,7 +166,7 @@ module.exports = { while (current.type !== 'Program') { if (isFirstArgumentInSetStateCall(current, node)) { vars - .filter((v) => v.scope === context.getScope() && v.variableName === node.name) + .filter((v) => v.scope === getScope(context, node) && v.variableName === node.name) .forEach((v) => { report(context, messages.useCallback, 'useCallback', { node: v.node, @@ -176,7 +184,7 @@ module.exports = { if (property && property.key && property.key.name === 'state' && isDerivedFromThis) { vars.push({ node: property.key, - scope: context.getScope(), + scope: getScope(context, node), variableName: property.key.name, }); } diff --git a/lib/rules/no-array-index-key.js b/lib/rules/no-array-index-key.js index bc8c91f9ef..c79fd56e2c 100644 --- a/lib/rules/no-array-index-key.js +++ b/lib/rules/no-array-index-key.js @@ -28,7 +28,7 @@ function isCreateCloneElement(node, context) { } if (node.type === 'Identifier') { - const variable = variableUtil.findVariableByName(context, node.name); + const variable = variableUtil.findVariableByName(context, node, node.name); if (variable && variable.type === 'ImportSpecifier') { return variable.parent.source.value === 'react'; } diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js index 17d55930ff..d3508721fe 100644 --- a/lib/rules/no-danger-with-children.js +++ b/lib/rules/no-danger-with-children.js @@ -31,8 +31,9 @@ module.exports = { schema: [], // no options }, create(context) { - function findSpreadVariable(name) { - return variableUtil.variablesInScope(context).find((item) => item.name === name); + function findSpreadVariable(node, name) { + return variableUtil.variablesInScope(context, node) + .find((item) => item.name === name); } /** * Takes a ObjectExpression and returns the value of the prop if it has it @@ -50,7 +51,7 @@ module.exports = { return prop.key.name === propName; } if (prop.type === 'ExperimentalSpreadProperty' || prop.type === 'SpreadElement') { - const variable = findSpreadVariable(prop.argument.name); + const variable = findSpreadVariable(node, prop.argument.name); if (variable && variable.defs.length && variable.defs[0].node.init) { if (seenProps.indexOf(prop.argument.name) > -1) { return false; @@ -73,7 +74,7 @@ module.exports = { const attributes = node.openingElement.attributes; return attributes.find((attribute) => { if (attribute.type === 'JSXSpreadAttribute') { - const variable = findSpreadVariable(attribute.argument.name); + const variable = findSpreadVariable(node, attribute.argument.name); if (variable && variable.defs.length && variable.defs[0].node.init) { return findObjectProp(variable.defs[0].node.init, propName, []); } @@ -127,7 +128,8 @@ module.exports = { let props = node.arguments[1]; if (props.type === 'Identifier') { - const variable = variableUtil.variablesInScope(context).find((item) => item.name === props.name); + const variable = variableUtil.variablesInScope(context, node) + .find((item) => item.name === props.name); if (variable && variable.defs.length && variable.defs[0].node.init) { props = variable.defs[0].node.init; } diff --git a/lib/rules/no-direct-mutation-state.js b/lib/rules/no-direct-mutation-state.js index 9349a85fe5..3df0998c6e 100644 --- a/lib/rules/no-direct-mutation-state.js +++ b/lib/rules/no-direct-mutation-state.js @@ -98,7 +98,7 @@ module.exports = { }, AssignmentExpression(node) { - const component = components.get(utils.getParentComponent()); + const component = components.get(utils.getParentComponent(node)); if (shouldIgnoreComponent(component) || !node.left || !node.left.object) { return; } @@ -114,7 +114,7 @@ module.exports = { }, UpdateExpression(node) { - const component = components.get(utils.getParentComponent()); + const component = components.get(utils.getParentComponent(node)); if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') { return; } diff --git a/lib/rules/no-set-state.js b/lib/rules/no-set-state.js index c88db30d7e..199e922b75 100644 --- a/lib/rules/no-set-state.js +++ b/lib/rules/no-set-state.js @@ -68,7 +68,7 @@ module.exports = { ) { return; } - const component = components.get(utils.getParentComponent()); + const component = components.get(utils.getParentComponent(node)); const setStateUsages = (component && component.setStateUsages) || []; setStateUsages.push(callee); components.set(node, { diff --git a/lib/rules/no-string-refs.js b/lib/rules/no-string-refs.js index 466a214579..09ce286685 100644 --- a/lib/rules/no-string-refs.js +++ b/lib/rules/no-string-refs.js @@ -50,7 +50,7 @@ module.exports = { */ function isRefsUsage(node) { return !!( - (componentUtil.getParentES6Component(context) || componentUtil.getParentES5Component(context)) + (componentUtil.getParentES6Component(context, node) || componentUtil.getParentES5Component(context, node)) && node.object.type === 'ThisExpression' && node.property.name === 'refs' ); diff --git a/lib/rules/no-this-in-sfc.js b/lib/rules/no-this-in-sfc.js index cf9eb99bd4..c520abd31c 100644 --- a/lib/rules/no-this-in-sfc.js +++ b/lib/rules/no-this-in-sfc.js @@ -34,7 +34,7 @@ module.exports = { create: Components.detect((context, components, utils) => ({ MemberExpression(node) { if (node.object.type === 'ThisExpression') { - const component = components.get(utils.getParentStatelessComponent()); + const component = components.get(utils.getParentStatelessComponent(node)); if (!component || (component.node && component.node.parent && component.node.parent.type === 'Property')) { return; } diff --git a/lib/rules/no-unstable-nested-components.js b/lib/rules/no-unstable-nested-components.js index 90da77a5d6..c1dd606b6a 100644 --- a/lib/rules/no-unstable-nested-components.js +++ b/lib/rules/no-unstable-nested-components.js @@ -306,7 +306,7 @@ module.exports = { * @returns {Boolean} True if node is inside class component's render block, false if not */ function isInsideRenderMethod(node) { - const parentComponent = utils.getParentComponent(); + const parentComponent = utils.getParentComponent(node); if (!parentComponent || parentComponent.type !== 'ClassDeclaration') { return false; @@ -334,8 +334,8 @@ module.exports = { * @returns {Boolean} True if given node a function component declared inside class component, false if not */ function isFunctionComponentInsideClassComponent(node) { - const parentComponent = utils.getParentComponent(); - const parentStatelessComponent = utils.getParentStatelessComponent(); + const parentComponent = utils.getParentComponent(node); + const parentStatelessComponent = utils.getParentStatelessComponent(node); return ( parentComponent diff --git a/lib/rules/no-unused-state.js b/lib/rules/no-unused-state.js index 73a0e52163..0ed480694d 100644 --- a/lib/rules/no-unused-state.js +++ b/lib/rules/no-unused-state.js @@ -13,6 +13,7 @@ const docsUrl = require('../util/docsUrl'); const ast = require('../util/ast'); const componentUtil = require('../util/componentUtil'); const report = require('../util/report'); +const getScope = require('../util/eslint').getScope; // Descend through all wrapping TypeCastExpressions and return the expression // that was cast. @@ -107,7 +108,7 @@ module.exports = { 'componentDidUpdate', ]; - let scope = context.getScope(); + let scope = getScope(context, node); while (scope) { const parent = scope.block && scope.block.parent; if ( @@ -368,7 +369,7 @@ module.exports = { return; } - const childScope = context.getScope().childScopes.find((x) => x.block === node.value); + const childScope = getScope(context, node).childScopes.find((x) => x.block === node.value); if (!childScope) { return; } diff --git a/lib/rules/prefer-exact-props.js b/lib/rules/prefer-exact-props.js index 0bd4dd8b06..6d7db27d0f 100644 --- a/lib/rules/prefer-exact-props.js +++ b/lib/rules/prefer-exact-props.js @@ -149,7 +149,7 @@ module.exports = { reportPropTypesError(node); } else if (right.type === 'Identifier') { const identifier = right.name; - const propsDefinition = variableUtil.findVariableByName(context, identifier); + const propsDefinition = variableUtil.findVariableByName(context, node, identifier); if (isNonEmptyObjectExpression(propsDefinition)) { reportPropTypesError(node); } else if (isNonExactPropWrapperFunction(propsDefinition)) { diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index 35e471ff9c..bfbabe76be 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -17,6 +17,7 @@ const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const eslintUtil = require('../util/eslint'); +const getScope = eslintUtil.getScope; const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ @@ -345,7 +346,7 @@ module.exports = { // Mark `render` that do not return some JSX ReturnStatement(node) { let blockNode; - let scope = context.getScope(); + let scope = getScope(context, node); while (scope) { blockNode = scope.block && scope.block.parent; if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) { diff --git a/lib/rules/react-in-jsx-scope.js b/lib/rules/react-in-jsx-scope.js index 1af398deae..f2d8bc4837 100644 --- a/lib/rules/react-in-jsx-scope.js +++ b/lib/rules/react-in-jsx-scope.js @@ -37,7 +37,7 @@ module.exports = { const pragma = pragmaUtil.getFromContext(context); function checkIfReactIsInScope(node) { - const variables = variableUtil.variablesInScope(context); + const variables = variableUtil.variablesInScope(context, node); if (variableUtil.findVariable(variables, pragma)) { return; } diff --git a/lib/rules/require-optimization.js b/lib/rules/require-optimization.js index 9d440b19ee..dc0b60f73a 100644 --- a/lib/rules/require-optimization.js +++ b/lib/rules/require-optimization.js @@ -11,6 +11,7 @@ const Components = require('../util/Components'); const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getScope = require('../util/eslint').getScope; const messages = { noShouldComponentUpdate: 'Component is not optimized. Please add a shouldComponentUpdate method.', @@ -154,11 +155,12 @@ module.exports = { /** * Checks if we are declaring function in class - * @returns {Boolean} True if we are declaring function in class, false if not. + * @param {ASTNode} node + * @returns {boolean} True if we are declaring function in class, false if not. */ - function isFunctionInClass() { + function isFunctionInClass(node) { let blockNode; - let scope = context.getScope(); + let scope = getScope(context, node); while (scope) { blockNode = scope.block; if (blockNode && blockNode.type === 'ClassDeclaration') { @@ -173,7 +175,7 @@ module.exports = { return { ArrowFunctionExpression(node) { // Skip if the function is declared in the class - if (isFunctionInClass()) { + if (isFunctionInClass(node)) { return; } // Stateless Functional Components cannot be optimized (yet) @@ -193,7 +195,7 @@ module.exports = { FunctionDeclaration(node) { // Skip if the function is declared in the class - if (isFunctionInClass()) { + if (isFunctionInClass(node)) { return; } // Stateless Functional Components cannot be optimized (yet) @@ -202,7 +204,7 @@ module.exports = { FunctionExpression(node) { // Skip if the function is declared in the class - if (isFunctionInClass()) { + if (isFunctionInClass(node)) { return; } // Stateless Functional Components cannot be optimized (yet) diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js index 7f7243f6b4..aca868f95d 100644 --- a/lib/rules/sort-default-props.js +++ b/lib/rules/sort-default-props.js @@ -86,11 +86,13 @@ module.exports = { /** * Find a variable by name in the current scope. + * @param {ASTNode} node The node to look for. * @param {string} name Name of the variable to look for. * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. */ - function findVariableByName(name) { - const variable = variableUtil.variablesInScope(context).find((item) => item.name === name); + function findVariableByName(node, name) { + const variable = variableUtil.variablesInScope(context, node) + .find((item) => item.name === name); if (!variable || !variable.defs[0] || !variable.defs[0].node) { return null; @@ -146,7 +148,7 @@ module.exports = { if (node.type === 'ObjectExpression') { checkSorted(node.properties); } else if (node.type === 'Identifier') { - const propTypesObject = findVariableByName(node.name); + const propTypesObject = findVariableByName(node, node.name); if (propTypesObject && propTypesObject.properties) { checkSorted(propTypesObject.properties); } diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 5d44bca744..808dd1df1f 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -211,7 +211,7 @@ module.exports = { checkSorted(node.properties); break; case 'Identifier': { - const propTypesObject = variableUtil.findVariableByName(context, node.name); + const propTypesObject = variableUtil.findVariableByName(context, node, node.name); if (propTypesObject && propTypesObject.properties) { checkSorted(propTypesObject.properties); } @@ -264,7 +264,7 @@ module.exports = { if (firstArg.properties) { checkSorted(firstArg.properties); } else if (firstArg.type === 'Identifier') { - const variable = variableUtil.findVariableByName(context, firstArg.name); + const variable = variableUtil.findVariableByName(context, node, firstArg.name); if (variable && variable.properties) { checkSorted(variable.properties); } diff --git a/lib/rules/state-in-constructor.js b/lib/rules/state-in-constructor.js index c518b1713a..9dd756b4d2 100644 --- a/lib/rules/state-in-constructor.js +++ b/lib/rules/state-in-constructor.js @@ -44,7 +44,7 @@ module.exports = { option === 'always' && !node.static && node.key.name === 'state' - && componentUtil.getParentES6Component(context) + && componentUtil.getParentES6Component(context, node) ) { report(context, messages.stateInitConstructor, 'stateInitConstructor', { node, @@ -55,8 +55,8 @@ module.exports = { if ( option === 'never' && componentUtil.isStateMemberExpression(node.left) - && astUtil.inConstructor(context) - && componentUtil.getParentES6Component(context) + && astUtil.inConstructor(context, node) + && componentUtil.getParentES6Component(context, node) ) { report(context, messages.stateInitClassProp, 'stateInitClassProp', { node, diff --git a/lib/rules/static-property-placement.js b/lib/rules/static-property-placement.js index efc50da3bd..7baf2faeb4 100644 --- a/lib/rules/static-property-placement.js +++ b/lib/rules/static-property-placement.js @@ -12,6 +12,7 @@ const astUtil = require('../util/ast'); const componentUtil = require('../util/componentUtil'); const propsUtil = require('../util/props'); const report = require('../util/report'); +const getScope = require('../util/eslint').getScope; // ------------------------------------------------------------------------------ // Positioning Options @@ -97,11 +98,12 @@ module.exports = { /** * Checks if we are declaring context in class + * @param {ASTNode} node * @returns {Boolean} True if we are declaring context in class, false if not. */ - function isContextInClass() { + function isContextInClass(node) { let blockNode; - let scope = context.getScope(); + let scope = getScope(context, node); while (scope) { blockNode = scope.block; if (blockNode && blockNode.type === 'ClassDeclaration') { @@ -149,7 +151,7 @@ module.exports = { // ---------------------------------------------------------------------- return { 'ClassProperty, PropertyDefinition'(node) { - if (!componentUtil.getParentES6Component(context)) { + if (!componentUtil.getParentES6Component(context, node)) { return; } @@ -160,7 +162,7 @@ module.exports = { // If definition type is undefined then it must not be a defining expression or if the definition is inside a // class body then skip this node. const right = node.parent.right; - if (!right || right.type === 'undefined' || isContextInClass()) { + if (!right || right.type === 'undefined' || isContextInClass(node)) { return; } @@ -178,7 +180,11 @@ module.exports = { MethodDefinition(node) { // If the function is inside a class and is static getter then check if correctly positioned - if (componentUtil.getParentES6Component(context) && node.static && node.kind === 'get') { + if ( + componentUtil.getParentES6Component(context, node) + && node.static + && node.kind === 'get' + ) { // Report error if needed reportNodeIncorrectlyPositioned(node, STATIC_GETTER); } diff --git a/lib/rules/style-prop-object.js b/lib/rules/style-prop-object.js index d38cae17ed..4f77beccb2 100644 --- a/lib/rules/style-prop-object.js +++ b/lib/rules/style-prop-object.js @@ -61,7 +61,8 @@ module.exports = { * @param {object} node A Identifier node */ function checkIdentifiers(node) { - const variable = variableUtil.variablesInScope(context).find((item) => item.name === node.name); + const variable = variableUtil.variablesInScope(context, node) + .find((item) => item.name === node.name); if (!variable || !variable.defs[0] || !variable.defs[0].node.init) { return; diff --git a/lib/util/Components.js b/lib/util/Components.js index e1803b26e3..67fac5afc5 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -23,6 +23,7 @@ const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport'); const eslintUtil = require('./eslint'); +const getScope = eslintUtil.getScope; const getText = eslintUtil.getText; function getId(node) { @@ -293,11 +294,12 @@ function componentRule(rule, context) { /** * Check if variable is destructured from pragma import * + * @param {ASTNode} node The AST node to check * @param {string} variable The variable name to check - * @returns {Boolean} True if createElement is destructured from the pragma + * @returns {boolean} True if createElement is destructured from the pragma */ - isDestructuredFromPragmaImport(variable) { - return isDestructuredFromPragmaImport(context, variable); + isDestructuredFromPragmaImport(node, variable) { + return isDestructuredFromPragmaImport(context, node, variable); }, /** @@ -419,7 +421,7 @@ function componentRule(rule, context) { return wrapperFunction.property === node.callee.name && (!wrapperFunction.object // Functions coming from the current pragma need special handling - || (wrapperFunction.object === pragma && this.isDestructuredFromPragmaImport(node.callee.name)) + || (wrapperFunction.object === pragma && this.isDestructuredFromPragmaImport(node, node.callee.name)) ); }); }, @@ -433,14 +435,15 @@ function componentRule(rule, context) { /** * Get the parent component node from the current scope + * @param {ASTNode} node * * @returns {ASTNode} component node, null if we are not in a component */ - getParentComponent() { + getParentComponent(node) { return ( - componentUtil.getParentES6Component(context) - || componentUtil.getParentES5Component(context) - || utils.getParentStatelessComponent() + componentUtil.getParentES6Component(context, node) + || componentUtil.getParentES5Component(context, node) + || utils.getParentStatelessComponent(node) ); }, @@ -619,13 +622,13 @@ function componentRule(rule, context) { /** * Get the parent stateless component node from the current scope * + * @param {ASTNode} node The AST node being checked * @returns {ASTNode} component node, null if we are not in a component */ - getParentStatelessComponent() { - let scope = context.getScope(); + getParentStatelessComponent(node) { + let scope = getScope(context, node); while (scope) { - const node = scope.block; - const statelessComponent = utils.getStatelessComponent(node); + const statelessComponent = utils.getStatelessComponent(scope.block); if (statelessComponent) { return statelessComponent; } @@ -648,14 +651,15 @@ function componentRule(rule, context) { let componentNode; // Get the component path const componentPath = []; - while (node) { - if (node.property && node.property.type === 'Identifier') { - componentPath.push(node.property.name); + let nodeTemp = node; + while (nodeTemp) { + if (nodeTemp.property && nodeTemp.property.type === 'Identifier') { + componentPath.push(nodeTemp.property.name); } - if (node.object && node.object.type === 'Identifier') { - componentPath.push(node.object.name); + if (nodeTemp.object && nodeTemp.object.type === 'Identifier') { + componentPath.push(nodeTemp.object.name); } - node = node.object; + nodeTemp = nodeTemp.object; } componentPath.reverse(); const componentName = componentPath.slice(0, componentPath.length - 1).join('.'); @@ -666,7 +670,7 @@ function componentRule(rule, context) { return null; } let variableInScope; - const variables = variableUtil.variablesInScope(context); + const variables = variableUtil.variablesInScope(context, node); for (i = 0, j = variables.length; i < j; i++) { if (variables[i].name === variableName) { variableInScope = variables[i]; @@ -783,7 +787,7 @@ function componentRule(rule, context) { && node.callee.type === 'Identifier' && node.callee.name.match(USE_HOOK_PREFIX_REGEX); - const scope = (isPotentialReactHookCall || isPotentialHookCall) && context.getScope(); + const scope = (isPotentialReactHookCall || isPotentialHookCall) && getScope(context, node); const reactResolvedDefs = isPotentialReactHookCall && scope.references @@ -895,7 +899,7 @@ function componentRule(rule, context) { }, ThisExpression(node) { - const component = utils.getParentStatelessComponent(); + const component = utils.getParentStatelessComponent(node); if (!component || !/Function/.test(component.type) || !node.parent.property) { return; } diff --git a/lib/util/ast.js b/lib/util/ast.js index 4d76036d6a..5664dcb512 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -8,6 +8,7 @@ const estraverse = require('estraverse'); const eslintUtil = require('./eslint'); const getFirstTokens = eslintUtil.getFirstTokens; +const getScope = eslintUtil.getScope; const getSourceCode = eslintUtil.getSourceCode; // const pragmaUtil = require('./pragma'); @@ -257,10 +258,11 @@ function isClass(node) { /** * Check if we are in a class constructor * @param {Context} context + * @param {ASTNode} node The AST node being checked. * @return {boolean} */ -function inConstructor(context) { - let scope = context.getScope(); +function inConstructor(context, node) { + let scope = getScope(context, node); while (scope) { // @ts-ignore if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') { diff --git a/lib/util/componentUtil.js b/lib/util/componentUtil.js index 17249c33f5..f98762a49a 100644 --- a/lib/util/componentUtil.js +++ b/lib/util/componentUtil.js @@ -4,6 +4,7 @@ const doctrine = require('doctrine'); const pragmaUtil = require('./pragma'); const eslintUtil = require('./eslint'); +const getScope = eslintUtil.getScope; const getSourceCode = eslintUtil.getSourceCode; const getText = eslintUtil.getText; @@ -120,13 +121,14 @@ function isES6Component(node, context) { /** * Get the parent ES5 component node from the current scope * @param {Context} context + * @param {ASTNode} node * @returns {ASTNode|null} */ -function getParentES5Component(context) { - let scope = context.getScope(); +function getParentES5Component(context, node) { + let scope = getScope(context, node); while (scope) { // @ts-ignore - const node = scope.block && scope.block.parent && scope.block.parent.parent; + node = scope.block && scope.block.parent && scope.block.parent.parent; if (node && isES5Component(node, context)) { return node; } @@ -138,14 +140,15 @@ function getParentES5Component(context) { /** * Get the parent ES6 component node from the current scope * @param {Context} context + * @param {ASTNode} node * @returns {ASTNode | null} */ -function getParentES6Component(context) { - let scope = context.getScope(); +function getParentES6Component(context, node) { + let scope = getScope(context, node); while (scope && scope.type !== 'class') { scope = scope.upper; } - const node = scope && scope.block; + node = scope && scope.block; if (!node || !isES6Component(node, context)) { return null; } diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js index 7cfdc92d52..cd14c7fa41 100644 --- a/lib/util/defaultProps.js +++ b/lib/util/defaultProps.js @@ -23,7 +23,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) { */ function resolveNodeValue(node) { if (node.type === 'Identifier') { - return variableUtil.findVariableByName(context, node.name); + return variableUtil.findVariableByName(context, node, node.name); } if ( node.type === 'CallExpression' @@ -171,7 +171,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) { } // find component this propTypes/defaultProps belongs to - const component = components.get(componentUtil.getParentES6Component(context)); + const component = components.get(componentUtil.getParentES6Component(context, node)); if (!component) { return; } @@ -214,7 +214,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) { } // find component this propTypes/defaultProps belongs to - const component = components.get(componentUtil.getParentES6Component(context)); + const component = components.get(componentUtil.getParentES6Component(context, node)); if (!component) { return; } diff --git a/lib/util/eslint.js b/lib/util/eslint.js index edecd9b317..79a0537f3b 100644 --- a/lib/util/eslint.js +++ b/lib/util/eslint.js @@ -9,6 +9,15 @@ function getAncestors(context, node) { return sourceCode.getAncestors ? sourceCode.getAncestors(node) : context.getAncestors(); } +function getScope(context, node) { + const sourceCode = getSourceCode(context); + if (sourceCode.getScope) { + return sourceCode.getScope(node); + } + + return context.getScope(); +} + function markVariableAsUsed(name, node, context) { const sourceCode = getSourceCode(context); return sourceCode.markVariableAsUsed @@ -30,6 +39,7 @@ function getText(context) { module.exports = { getAncestors, getFirstTokens, + getScope, getSourceCode, getText, markVariableAsUsed, diff --git a/lib/util/isCreateElement.js b/lib/util/isCreateElement.js index 3681cd91b4..9e531964fa 100644 --- a/lib/util/isCreateElement.js +++ b/lib/util/isCreateElement.js @@ -24,7 +24,7 @@ module.exports = function isCreateElement(context, node) { node && node.callee && node.callee.name === 'createElement' - && isDestructuredFromPragmaImport(context, 'createElement') + && isDestructuredFromPragmaImport(context, node, 'createElement') ) { return true; } diff --git a/lib/util/isDestructuredFromPragmaImport.js b/lib/util/isDestructuredFromPragmaImport.js index 2d75f3f087..4d64596274 100644 --- a/lib/util/isDestructuredFromPragmaImport.js +++ b/lib/util/isDestructuredFromPragmaImport.js @@ -7,12 +7,13 @@ const variableUtil = require('./variable'); * Check if variable is destructured from pragma import * * @param {Context} context eslint context + * @param {ASTNode} node The AST node to check * @param {string} variable The variable name to check * @returns {boolean} True if createElement is destructured from the pragma */ -module.exports = function isDestructuredFromPragmaImport(context, variable) { +module.exports = function isDestructuredFromPragmaImport(context, node, variable) { const pragma = pragmaUtil.getFromContext(context); - const variables = variableUtil.variablesInScope(context); + const variables = variableUtil.variablesInScope(context, node); const variableInScope = variableUtil.getVariable(variables, variable); if (variableInScope) { const latestDef = variableUtil.getLatestVariableDefinition(variableInScope); diff --git a/lib/util/jsx.js b/lib/util/jsx.js index 330b6d994e..07a09a8022 100644 --- a/lib/util/jsx.js +++ b/lib/util/jsx.js @@ -121,7 +121,7 @@ function isReturningJSX(context, ASTnode, strict, ignoreNull) { } return false; case 'Identifier': { - const variable = variableUtil.findVariableByName(context, node.name); + const variable = variableUtil.findVariableByName(context, node, node.name); return isJSX(variable); } default: diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 1fb81e9942..6f879e2347 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -16,6 +16,7 @@ const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); const eslintUtil = require('./eslint'); const getFirstTokens = eslintUtil.getFirstTokens; +const getScope = eslintUtil.getScope; const getSourceCode = eslintUtil.getSourceCode; const getText = eslintUtil.getText; @@ -366,14 +367,15 @@ module.exports = function propTypesInstructions(context, components, utils) { /** * Resolve node of type Identifier when building declaration types. * @param {ASTNode} node + * @param {ASTNode} rootNode * @param {Function} callback called with the resolved value only if resolved. */ - function resolveValueForIdentifierNode(node, callback) { + function resolveValueForIdentifierNode(node, rootNode, callback) { if ( node && node.type === 'Identifier' ) { - const scope = context.getScope(); + const scope = getScope(context, rootNode); const identVariable = scope.variableScope.variables.find( (variable) => variable.name === node.name ); @@ -389,10 +391,11 @@ module.exports = function propTypesInstructions(context, components, utils) { * The representation is used to verify nested used properties. * @param {ASTNode} value Node of the PropTypes for the desired property * @param {string} parentName + * @param {ASTNode} rootNode * @return {Object} The representation of the declaration, empty object means * the property is declared without the need for further analysis. */ - function buildReactDeclarationTypes(value, parentName) { + function buildReactDeclarationTypes(value, parentName, rootNode) { if ( value && value.callee @@ -414,7 +417,7 @@ module.exports = function propTypesInstructions(context, components, utils) { // propTypes = { // example: variableType // } - resolveValueForIdentifierNode(value, (newValue) => { + resolveValueForIdentifierNode(value, rootNode, (newValue) => { identNodeResolved = true; value = newValue; }); @@ -436,7 +439,7 @@ module.exports = function propTypesInstructions(context, components, utils) { // example: variableType.isRequired // } if (!identNodeResolved) { - resolveValueForIdentifierNode(value, (newValue) => { + resolveValueForIdentifierNode(value, rootNode, (newValue) => { value = newValue; }); } @@ -467,7 +470,7 @@ module.exports = function propTypesInstructions(context, components, utils) { iterateProperties(context, argument.properties, (childKey, childValue, propNode) => { if (childValue) { // skip spread propTypes const fullName = [parentName, childKey].join('.'); - const types = buildReactDeclarationTypes(childValue, fullName); + const types = buildReactDeclarationTypes(childValue, fullName, rootNode); types.fullName = fullName; types.name = childKey; types.node = propNode; @@ -479,7 +482,7 @@ module.exports = function propTypesInstructions(context, components, utils) { case 'arrayOf': case 'objectOf': { const fullName = [parentName, '*'].join('.'); - const child = buildReactDeclarationTypes(argument, fullName); + const child = buildReactDeclarationTypes(argument, fullName, rootNode); child.fullName = fullName; child.name = '__ANY_KEY__'; child.node = argument; @@ -502,7 +505,7 @@ module.exports = function propTypesInstructions(context, components, utils) { /** @type {UnionTypeDefinition} */ const unionTypeDefinition = { type: 'union', - children: argument.elements.map((element) => buildReactDeclarationTypes(element, parentName)), + children: argument.elements.map((element) => buildReactDeclarationTypes(element, parentName, rootNode)), }; if (unionTypeDefinition.children.length === 0) { // no complex type found, simply accept everything @@ -578,13 +581,14 @@ module.exports = function propTypesInstructions(context, components, utils) { } class DeclarePropTypesForTSTypeAnnotation { - constructor(propTypes, declaredPropTypes) { + constructor(propTypes, declaredPropTypes, rootNode) { this.propTypes = propTypes; this.declaredPropTypes = declaredPropTypes; this.foundDeclaredPropertiesList = []; this.referenceNameMap = new Set(); this.sourceCode = getSourceCode(context); this.shouldIgnorePropTypes = false; + this.rootNode = rootNode; this.visitTSNode(this.propTypes); this.endAndStructDeclaredPropTypes(); } @@ -614,7 +618,7 @@ module.exports = function propTypesInstructions(context, components, utils) { this.visitTSNode(typeAnnotation); } else if (astUtil.isTSTypeParameterInstantiation(node)) { if (Array.isArray(node.params)) { - node.params.forEach(this.visitTSNode, this); + node.params.forEach((x) => this.visitTSNode(x)); } } else { this.shouldIgnorePropTypes = true; @@ -660,7 +664,7 @@ module.exports = function propTypesInstructions(context, components, utils) { return; } if (typeName === 'ReturnType') { - this.convertReturnTypeToPropTypes(node); + this.convertReturnTypeToPropTypes(node, this.rootNode); return; } // Prevent recursive inheritance will cause maximum callstack. @@ -714,24 +718,24 @@ module.exports = function propTypesInstructions(context, components, utils) { this.visitTSNode(typeAnnotation); } if (Array.isArray(node.extends)) { - node.extends.forEach(this.visitTSNode, this); + node.extends.forEach((x) => this.visitTSNode(x)); // This line is trying to handle typescript-eslint-parser // typescript-eslint-parser extension is name as heritage } else if (Array.isArray(node.heritage)) { - node.heritage.forEach(this.visitTSNode, this); + node.heritage.forEach((x) => this.visitTSNode(x)); } } convertIntersectionTypeToPropTypes(node) { if (!node) return; if (Array.isArray(node.types)) { - node.types.forEach(this.visitTSNode, this); + node.types.forEach((x) => this.visitTSNode(x)); } else { this.shouldIgnorePropTypes = true; } } - convertReturnTypeToPropTypes(node) { + convertReturnTypeToPropTypes(node, rootNode) { // ReturnType should always have one parameter const nodeTypeParams = node.typeParameters; if (nodeTypeParams) { @@ -781,7 +785,7 @@ module.exports = function propTypesInstructions(context, components, utils) { this.shouldIgnorePropTypes = true; return; } - const types = buildReactDeclarationTypes(value, key); + const types = buildReactDeclarationTypes(value, key, rootNode); types.fullName = key; types.name = key; types.node = propNode; @@ -855,8 +859,9 @@ module.exports = function propTypesInstructions(context, components, utils) { * Mark a prop type as declared * @param {ASTNode} node The AST node being checked. * @param {ASTNode} propTypes The AST node containing the proptypes + * @param {ASTNode} rootNode */ - function markPropTypesAsDeclared(node, propTypes) { + function markPropTypesAsDeclared(node, propTypes, rootNode) { let componentNode = node; while (componentNode && !components.get(componentNode)) { componentNode = componentNode.parent; @@ -874,7 +879,7 @@ module.exports = function propTypesInstructions(context, components, utils) { ignorePropsValidation = true; return; } - const types = buildReactDeclarationTypes(value, key); + const types = buildReactDeclarationTypes(value, key, rootNode); types.fullName = key; types.name = key; types.node = propNode; @@ -911,7 +916,8 @@ module.exports = function propTypesInstructions(context, components, utils) { const parentProp = getText(context, propTypes.parent.left.object).replace(/^.*\.propTypes\./, ''); const types = buildReactDeclarationTypes( propTypes.parent.right, - parentProp + parentProp, + rootNode ); types.name = propTypes.property.name; @@ -939,12 +945,11 @@ module.exports = function propTypesInstructions(context, components, utils) { break; } case 'Identifier': { - const variablesInScope = variableUtil.variablesInScope(context); - const firstMatchingVariable = variablesInScope + const firstMatchingVariable = variableUtil.variablesInScope(context, node) .find((variableInScope) => variableInScope.name === propTypes.name); if (firstMatchingVariable) { const defInScope = firstMatchingVariable.defs[firstMatchingVariable.defs.length - 1]; - markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init); + markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init, rootNode); return; } ignorePropsValidation = true; @@ -958,7 +963,7 @@ module.exports = function propTypesInstructions(context, components, utils) { ) && propTypes.arguments && propTypes.arguments[0] ) { - markPropTypesAsDeclared(node, propTypes.arguments[0]); + markPropTypesAsDeclared(node, propTypes.arguments[0], rootNode); return; } break; @@ -979,7 +984,7 @@ module.exports = function propTypesInstructions(context, components, utils) { break; case 'TSTypeReference': case 'TSTypeAnnotation': { - const tsTypeAnnotation = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes); + const tsTypeAnnotation = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes, rootNode); ignorePropsValidation = tsTypeAnnotation.shouldIgnorePropTypes; declaredPropTypes = tsTypeAnnotation.declaredPropTypes; } @@ -1000,8 +1005,9 @@ module.exports = function propTypesInstructions(context, components, utils) { /** * @param {ASTNode} node We expect either an ArrowFunctionExpression, * FunctionDeclaration, or FunctionExpression + * @param {ASTNode} rootNode */ - function markAnnotatedFunctionArgumentsAsDeclared(node) { + function markAnnotatedFunctionArgumentsAsDeclared(node, rootNode) { if (!node.params || !node.params.length) { return; } @@ -1022,7 +1028,7 @@ module.exports = function propTypesInstructions(context, components, utils) { ) { const propTypesParams = node.parent.typeParameters; const declaredPropTypes = {}; - const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesParams.params[1], declaredPropTypes); + const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesParams.params[1], declaredPropTypes, rootNode); components.set(node, { declaredPropTypes: obj.declaredPropTypes, ignorePropsValidation: obj.shouldIgnorePropTypes, @@ -1053,13 +1059,13 @@ module.exports = function propTypesInstructions(context, components, utils) { if (param.typeAnnotation && param.typeAnnotation.typeAnnotation && param.typeAnnotation.typeAnnotation.type === 'UnionTypeAnnotation') { param.typeAnnotation.typeAnnotation.types.forEach((annotation) => { if (annotation.type === 'GenericTypeAnnotation') { - markPropTypesAsDeclared(node, resolveTypeAnnotation(annotation)); + markPropTypesAsDeclared(node, resolveTypeAnnotation(annotation), rootNode); } else { - markPropTypesAsDeclared(node, annotation); + markPropTypesAsDeclared(node, annotation, rootNode); } }); } else { - markPropTypesAsDeclared(node, resolveTypeAnnotation(param)); + markPropTypesAsDeclared(node, resolveTypeAnnotation(param), rootNode); } } else { // implements what's discussed here: https://github.com/jsx-eslint/eslint-plugin-react/issues/2777#issuecomment-683944481 @@ -1075,7 +1081,7 @@ module.exports = function propTypesInstructions(context, components, utils) { if (!isValidReactGenericTypeAnnotation(annotation)) return; - markPropTypesAsDeclared(node, resolveTypeAnnotation(siblingIdentifier)); + markPropTypesAsDeclared(node, resolveTypeAnnotation(siblingIdentifier), rootNode); } } @@ -1137,15 +1143,15 @@ module.exports = function propTypesInstructions(context, components, utils) { ClassDeclaration(node) { if (isSuperTypeParameterPropsDeclaration(node)) { - markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); + markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node), node); } }, 'ClassProperty, PropertyDefinition'(node) { if (isAnnotatedClassPropsDeclaration(node)) { - markPropTypesAsDeclared(node, resolveTypeAnnotation(node)); + markPropTypesAsDeclared(node, resolveTypeAnnotation(node), node); } else if (propsUtil.isPropTypesDeclaration(node)) { - markPropTypesAsDeclared(node, node.value); + markPropTypesAsDeclared(node, node.value, node); } }, @@ -1155,13 +1161,13 @@ module.exports = function propTypesInstructions(context, components, utils) { if (!propsUtil.isPropTypesDeclaration(property)) { return; } - markPropTypesAsDeclared(node, property.value); + markPropTypesAsDeclared(node, property.value, node); }); }, FunctionExpression(node) { if (node.parent.type !== 'MethodDefinition') { - markAnnotatedFunctionArgumentsAsDeclared(node); + markAnnotatedFunctionArgumentsAsDeclared(node, node); } }, @@ -1198,7 +1204,7 @@ module.exports = function propTypesInstructions(context, components, utils) { return; } try { - markPropTypesAsDeclared(component.node, node.parent.right || node.parent); + markPropTypesAsDeclared(component.node, node.parent.right || node.parent, node); } catch (e) { if (e.constructor !== RangeError) { throw e; } } @@ -1218,7 +1224,7 @@ module.exports = function propTypesInstructions(context, components, utils) { } if (i >= 0) { - markPropTypesAsDeclared(node, node.value.body.body[i].argument); + markPropTypesAsDeclared(node, node.value.body.body[i].argument, node); } }, @@ -1249,7 +1255,7 @@ module.exports = function propTypesInstructions(context, components, utils) { 'Program:exit'() { classExpressions.forEach((node) => { if (isSuperTypeParameterPropsDeclaration(node)) { - markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); + markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node), node); } }); }, diff --git a/lib/util/usedPropTypes.js b/lib/util/usedPropTypes.js index 2ce808feb2..9ecef0af37 100644 --- a/lib/util/usedPropTypes.js +++ b/lib/util/usedPropTypes.js @@ -12,6 +12,7 @@ const testReactVersion = require('./version').testReactVersion; const ast = require('./ast'); const eslintUtil = require('./eslint'); +const getScope = eslintUtil.getScope; const getSourceCode = eslintUtil.getSourceCode; // ------------------------------------------------------------------------------ @@ -83,11 +84,12 @@ function mustBeValidated(component) { /** * Check if we are in a lifecycle method * @param {object} context + * @param {ASTNode} node The AST node being checked. * @param {boolean} checkAsyncSafeLifeCycles * @return {boolean} true if we are in a class constructor, false if not */ -function inLifeCycleMethod(context, checkAsyncSafeLifeCycles) { - let scope = context.getScope(); +function inLifeCycleMethod(context, node, checkAsyncSafeLifeCycles) { + let scope = getScope(context, node); while (scope) { if (scope.block && scope.block.parent && scope.block.parent.key) { const name = scope.block.parent.key.name; @@ -161,11 +163,11 @@ function isSetStateUpdater(node) { && node.parent.arguments[0] === node; } -function isPropArgumentInSetStateUpdater(context, name) { +function isPropArgumentInSetStateUpdater(context, node, name) { if (typeof name !== 'string') { return; } - let scope = context.getScope(); + let scope = getScope(context, node); while (scope) { const unwrappedParentCalleeNode = scope.block && scope.block.parent @@ -189,10 +191,11 @@ function isPropArgumentInSetStateUpdater(context, name) { /** * @param {Context} context + * @param {ASTNode} node * @returns {boolean} */ -function isInClassComponent(context) { - return !!(componentUtil.getParentES6Component(context) || componentUtil.getParentES5Component(context)); +function isInClassComponent(context, node) { + return !!(componentUtil.getParentES6Component(context, node) || componentUtil.getParentES5Component(context, node)); } /** @@ -220,16 +223,16 @@ function hasSpreadOperator(context, node) { /** * Checks if the node is a propTypes usage of the form `this.props.*`, `props.*`, `prevProps.*`, or `nextProps.*`. - * @param {ASTNode} node * @param {Context} context + * @param {ASTNode} node * @param {Object} utils * @param {boolean} checkAsyncSafeLifeCycles * @returns {boolean} */ -function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafeLifeCycles) { +function isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles) { const unwrappedObjectNode = ast.unwrapTSAsExpression(node.object); - if (isInClassComponent(context)) { + if (isInClassComponent(context, node)) { // this.props.* if (isThisDotProps(unwrappedObjectNode)) { return true; @@ -237,12 +240,12 @@ function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafe // props.* or prevProps.* or nextProps.* if ( isCommonVariableNameForProps(unwrappedObjectNode.name) - && (inLifeCycleMethod(context, checkAsyncSafeLifeCycles) || astUtil.inConstructor(context)) + && (inLifeCycleMethod(context, node, checkAsyncSafeLifeCycles) || astUtil.inConstructor(context, node)) ) { return true; } // this.setState((_, props) => props.*)) - if (isPropArgumentInSetStateUpdater(context, unwrappedObjectNode.name)) { + if (isPropArgumentInSetStateUpdater(context, node, unwrappedObjectNode.name)) { return true; } return false; @@ -277,7 +280,7 @@ function getPropertyName(context, node, utils, checkAsyncSafeLifeCycles) { } // Accept number as well but only accept props[123] if (typeof property.value === 'number') { - if (isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafeLifeCycles)) { + if (isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles)) { return property.raw; } } @@ -365,7 +368,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils) throw new Error(`${node.type} ASTNodes are not handled by markPropTypesAsUsed`); } - const component = components.get(utils.getParentComponent()); + const component = components.get(utils.getParentComponent(node)); const usedPropTypes = (component && component.usedPropTypes) || []; let ignoreUnusedPropTypesValidation = (component && component.ignoreUnusedPropTypesValidation) || false; @@ -475,7 +478,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils) const unwrappedInitNode = ast.unwrapTSAsExpression(node.init); // let props = this.props - if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context) && node.id.type === 'Identifier') { + if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context, node) && node.id.type === 'Identifier') { propVariables.set(node.id.name, []); } @@ -504,14 +507,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils) // let {firstname} = props if ( isCommonVariableNameForProps(unwrappedInitNode.name) - && (utils.getParentStatelessComponent() || isInLifeCycleMethod(node, checkAsyncSafeLifeCycles)) + && (utils.getParentStatelessComponent(node) || isInLifeCycleMethod(node, checkAsyncSafeLifeCycles)) ) { markPropTypesAsUsed(node.id); return; } // let {firstname} = this.props - if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context)) { + if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context, node)) { markPropTypesAsUsed(node.id); return; } @@ -535,14 +538,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils) 'FunctionExpression:exit': popScope, JSXSpreadAttribute(node) { - const component = components.get(utils.getParentComponent()); + const component = components.get(utils.getParentComponent(node)); components.set(component ? component.node : node, { ignoreUnusedPropTypesValidation: node.argument.type !== 'ObjectExpression', }); }, 'MemberExpression, OptionalMemberExpression'(node) { - if (isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafeLifeCycles)) { + if (isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles)) { markPropTypesAsUsed(node); return; } diff --git a/lib/util/variable.js b/lib/util/variable.js index a93cf18b5e..2903c67344 100644 --- a/lib/util/variable.js +++ b/lib/util/variable.js @@ -6,6 +6,7 @@ 'use strict'; const toReversed = require('array.prototype.toreversed'); +const getScope = require('./eslint').getScope; /** * Search a particular variable in a list @@ -33,10 +34,11 @@ function getVariable(variables, name) { * Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21 * * @param {Object} context The current rule context. + * @param {ASTNode} node The node to start looking from. * @returns {Array} The variables list */ -function variablesInScope(context) { - let scope = context.getScope(); +function variablesInScope(context, node) { + let scope = getScope(context, node); let variables = scope.variables; while (scope.type !== 'global') { @@ -56,11 +58,12 @@ function variablesInScope(context) { /** * Find a variable by name in the current scope. * @param {Object} context The current rule context. + * @param {ASTNode} node The node to check. Must be an Identifier node. * @param {string} name Name of the variable to look for. * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. */ -function findVariableByName(context, name) { - const variable = getVariable(variablesInScope(context), name); +function findVariableByName(context, node, name) { + const variable = getVariable(variablesInScope(context, node), name); if (!variable || !variable.defs[0] || !variable.defs[0].node) { return null;