From 88f7f0985df152b79e5bcd427d03bbe092f3070c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 14 Jan 2024 22:46:40 +0900 Subject: [PATCH 001/174] [Fix] `prop-types`: handle nested forwardRef + memo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #3521. Co-authored-by: 김상두 Co-authored-by: Jordan Harband --- CHANGELOG.md | 2 ++ lib/rules/prop-types.js | 25 +++++++++++++++++++++- tests/lib/rules/prop-types.js | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b95e4560f..fdf6c826a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-key`]: detect conditional returns ([#3630][] @yialo) * [`jsx-newline`]: prevent a crash when `allowMultilines ([#3633][] @ljharb) * [`no-unknown-property`]: use a better regex to avoid a crash ([#3666][] @ljharb @SCH227) +* [`prop-types`]: handle nested forwardRef + memo ([#3679][] @developer-bandi) ### Changed * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) @@ -33,6 +34,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek) +[#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 [#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677 [#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675 [#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674 diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index bb317a2467..9ab3640938 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -154,7 +154,7 @@ module.exports = { while (node) { const component = components.get(node); - const isDeclared = component && component.confidence === 2 + const isDeclared = component && component.confidence >= 2 && internalIsDeclaredInComponent(component.declaredPropTypes || {}, names); if (isDeclared) { @@ -186,6 +186,28 @@ module.exports = { }); } + /** + * @param {Object} component The current component to process + * @param {Array} list The all components to process + * @returns {Boolean} True if the component is nested False if not. + */ + function checkNestedComponent(component, list) { + const componentIsMemo = component.node.callee && component.node.callee.name === 'memo'; + const argumentIsForwardRef = component.node.arguments && component.node.arguments[0].callee && component.node.arguments[0].callee.name === 'forwardRef'; + if (componentIsMemo && argumentIsForwardRef) { + const forwardComponent = list.find( + (innerComponent) => ( + innerComponent.node.range[0] === component.node.arguments[0].range[0] + && innerComponent.node.range[0] === component.node.arguments[0].range[0] + )); + + const isValidated = mustBeValidated(forwardComponent); + const isIgnorePropsValidation = forwardComponent.ignorePropsValidation; + + return isIgnorePropsValidation || isValidated; + } + } + return { 'Program:exit'() { const list = components.list(); @@ -193,6 +215,7 @@ module.exports = { values(list) .filter((component) => mustBeValidated(component)) .forEach((component) => { + if (checkNestedComponent(component, values(list))) return; reportUndeclaredPropTypes(component); }); }, diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 049e387da6..df762d74ef 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -4141,6 +4141,46 @@ ruleTester.run('prop-types', rule, { }; `, features: ['ts', 'no-babel'], + }, + { + code: ` + import React, { memo } from 'react'; + interface Props1 { + age: number; + } + const HelloTemp = memo(({ age }: Props1) => { + return
Hello {age}
; + }); + export const Hello = HelloTemp + `, + features: ['types'], + }, + { + code: ` + import React, { forwardRef, memo } from 'react'; + interface Props1 { + age: number; + } + const HelloTemp = forwardRef(({ age }: Props1) => { + return
Hello {age}
; + }); + export const Hello = memo(HelloTemp); + `, + features: ['types'], + }, + { + code: ` + import React, { forwardRef, memo } from 'react'; + interface Props1 { + age: number; + } + export const Hello = memo( + forwardRef(({ age }: Props1) => { + return
Hello {age}
; + }), + ); + `, + features: ['types'], } )), From 4edc1f0c1765d6224fa3705933b2845c9fdfa830 Mon Sep 17 00:00:00 2001 From: Nano Miratus Date: Sat, 17 Feb 2024 23:12:52 +0100 Subject: [PATCH 002/174] [Docs] `iframe-missing-sandbox`: fix link to iframe attribute on mdn --- CHANGELOG.md | 2 ++ docs/rules/iframe-missing-sandbox.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf6c826a0..0b0ff902a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Refactor] [`jsx-props-no-multi-spaces`]: extract type parameters to var ([#3634][] @HenryBrown0) * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek) +* [Docs] [`iframe-missing-sandbox`]: fix link to iframe attribute on mdn ([#3690][] @nnmrts) +[#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 [#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 [#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677 [#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675 diff --git a/docs/rules/iframe-missing-sandbox.md b/docs/rules/iframe-missing-sandbox.md index 85e3931faa..1934e67b63 100644 --- a/docs/rules/iframe-missing-sandbox.md +++ b/docs/rules/iframe-missing-sandbox.md @@ -4,7 +4,7 @@ The sandbox attribute enables an extra set of restrictions for the content in the iframe. Using sandbox attribute is considered a good security practice. -See +See ## Rule Details From b9292b4442740a9df40d0bba3d79ed124fc87f47 Mon Sep 17 00:00:00 2001 From: jaesoekjjang Date: Sat, 20 Jan 2024 19:34:56 +0900 Subject: [PATCH 003/174] [New] add `checked-requires-onchange-or-readonly` rule --- CHANGELOG.md | 3 + README.md | 205 +++++++++--------- .../checked-requires-onchange-or-readonly.md | 44 ++++ .../checked-requires-onchange-or-readonly.js | 141 ++++++++++++ lib/rules/index.js | 1 + .../checked-requires-onchange-or-readonly.js | 111 ++++++++++ 6 files changed, 403 insertions(+), 102 deletions(-) create mode 100644 docs/rules/checked-requires-onchange-or-readonly.md create mode 100644 lib/rules/checked-requires-onchange-or-readonly.js create mode 100644 tests/lib/rules/checked-requires-onchange-or-readonly.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0ff902a5..7c21e803d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * `linkAttribute` setting, [`jsx-no-target-blank`]: support multiple properties ([#3673][] @burtek) * [`jsx-no-script-url`]: add `includeFromSettings` option to support `linkAttributes` setting ([#3673][] @burtek) * [`jsx-one-expression-per-line`]: add `non-jsx` option to allow non-JSX children in one line ([#3677][] @burtek) +* add [`checked-requires-onchange-or-readonly`] rule ([#3680][] @jaesoekjjang) ### Fixed * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0) @@ -36,6 +37,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`iframe-missing-sandbox`]: fix link to iframe attribute on mdn ([#3690][] @nnmrts) [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 +[#3680]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3680 [#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 [#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677 [#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675 @@ -4140,6 +4142,7 @@ If you're still not using React 15 you can keep the old behavior by setting the [`boolean-prop-naming`]: docs/rules/boolean-prop-naming.md [`button-has-type`]: docs/rules/button-has-type.md +[`checked-requires-onchange-or-readonly`]: docs/rules/checked-requires-onchange-or-readonly.md [`default-props-match-prop-types`]: docs/rules/default-props-match-prop-types.md [`destructuring-assignment`]: docs/rules/destructuring-assignment.md [`display-name`]: docs/rules/display-name.md diff --git a/README.md b/README.md index 9c67ff12b5..b68767f0f0 100644 --- a/README.md +++ b/README.md @@ -291,108 +291,109 @@ module.exports = [ 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\ ❌ Deprecated. -| Name                                 | Description | 💼 | 🚫 | 🔧 | 💡 | ❌ | -| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | -| [boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | | | | | -| [button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | | | | | -| [default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps have a corresponding non-required PropType | | | | | | -| [destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context | | | 🔧 | | | -| [display-name](docs/rules/display-name.md) | Disallow missing displayName in a React component definition | ☑️ | | | | | -| [forbid-component-props](docs/rules/forbid-component-props.md) | Disallow certain props on components | | | | | | -| [forbid-dom-props](docs/rules/forbid-dom-props.md) | Disallow certain props on DOM Nodes | | | | | | -| [forbid-elements](docs/rules/forbid-elements.md) | Disallow certain elements | | | | | | -| [forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Disallow using another component's propTypes | | | | | | -| [forbid-prop-types](docs/rules/forbid-prop-types.md) | Disallow certain propTypes | | | | | | -| [function-component-definition](docs/rules/function-component-definition.md) | Enforce a specific function type for function components | | | 🔧 | | | -| [hook-use-state](docs/rules/hook-use-state.md) | Ensure destructuring and symmetric naming of useState hook value and setter variables | | | | 💡 | | -| [iframe-missing-sandbox](docs/rules/iframe-missing-sandbox.md) | Enforce sandbox attribute on iframe elements | | | | | | -| [jsx-boolean-value](docs/rules/jsx-boolean-value.md) | Enforce boolean attributes notation in JSX | | | 🔧 | | | -| [jsx-child-element-spacing](docs/rules/jsx-child-element-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | | | | -| [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md) | Enforce closing bracket location in JSX | | | 🔧 | | | -| [jsx-closing-tag-location](docs/rules/jsx-closing-tag-location.md) | Enforce closing tag location for multiline JSX | | | 🔧 | | | -| [jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md) | Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes | | | 🔧 | | | -| [jsx-curly-newline](docs/rules/jsx-curly-newline.md) | Enforce consistent linebreaks in curly braces in JSX attributes and expressions | | | 🔧 | | | -| [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | 🔧 | | | -| [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md) | Enforce or disallow spaces around equal signs in JSX attributes | | | 🔧 | | | -| [jsx-filename-extension](docs/rules/jsx-filename-extension.md) | Disallow file extensions that may contain JSX | | | | | | -| [jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md) | Enforce proper position of the first property in JSX | | | 🔧 | | | -| [jsx-fragments](docs/rules/jsx-fragments.md) | Enforce shorthand or standard form for React fragments | | | 🔧 | | | -| [jsx-handler-names](docs/rules/jsx-handler-names.md) | Enforce event handler naming conventions in JSX | | | | | | -| [jsx-indent](docs/rules/jsx-indent.md) | Enforce JSX indentation | | | 🔧 | | | -| [jsx-indent-props](docs/rules/jsx-indent-props.md) | Enforce props indentation in JSX | | | 🔧 | | | -| [jsx-key](docs/rules/jsx-key.md) | Disallow missing `key` props in iterators/collection literals | ☑️ | | | | | -| [jsx-max-depth](docs/rules/jsx-max-depth.md) | Enforce JSX maximum depth | | | | | | -| [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Enforce maximum of props on a single line in JSX | | | 🔧 | | | -| [jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. | | | 🔧 | | | -| [jsx-no-bind](docs/rules/jsx-no-bind.md) | Disallow `.bind()` or arrow functions in JSX props | | | | | | -| [jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Disallow comments from being inserted as text nodes | ☑️ | | | | | -| [jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Disallows JSX context provider values from taking values that will cause needless rerenders | | | | | | -| [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md) | Disallow duplicate properties in JSX | ☑️ | | | | | -| [jsx-no-leaked-render](docs/rules/jsx-no-leaked-render.md) | Disallow problematic leaked values from being rendered | | | 🔧 | | | -| [jsx-no-literals](docs/rules/jsx-no-literals.md) | Disallow usage of string literals in JSX | | | | | | -| [jsx-no-script-url](docs/rules/jsx-no-script-url.md) | Disallow usage of `javascript:` URLs | | | | | | -| [jsx-no-target-blank](docs/rules/jsx-no-target-blank.md) | Disallow `target="_blank"` attribute without `rel="noreferrer"` | ☑️ | | 🔧 | | | -| [jsx-no-undef](docs/rules/jsx-no-undef.md) | Disallow undeclared variables in JSX | ☑️ | | | | | -| [jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md) | Disallow unnecessary fragments | | | 🔧 | | | -| [jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md) | Require one JSX element per line | | | 🔧 | | | -| [jsx-pascal-case](docs/rules/jsx-pascal-case.md) | Enforce PascalCase for user-defined JSX components | | | | | | -| [jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | | 🔧 | | | -| [jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | | | | | | -| [jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | ❌ | -| [jsx-sort-props](docs/rules/jsx-sort-props.md) | Enforce props alphabetical sorting | | | 🔧 | | | -| [jsx-space-before-closing](docs/rules/jsx-space-before-closing.md) | Enforce spacing before closing bracket in JSX | | | 🔧 | | ❌ | -| [jsx-tag-spacing](docs/rules/jsx-tag-spacing.md) | Enforce whitespace in and around the JSX opening and closing brackets | | | 🔧 | | | -| [jsx-uses-react](docs/rules/jsx-uses-react.md) | Disallow React to be incorrectly marked as unused | ☑️ | 🏃 | | | | -| [jsx-uses-vars](docs/rules/jsx-uses-vars.md) | Disallow variables used in JSX to be incorrectly marked as unused | ☑️ | | | | | -| [jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md) | Disallow missing parentheses around multiline JSX | | | 🔧 | | | -| [no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Disallow when this.state is accessed within setState | | | | | | -| [no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Disallow adjacent inline elements not separated by whitespace. | | | | | | -| [no-array-index-key](docs/rules/no-array-index-key.md) | Disallow usage of Array index in keys | | | | | | -| [no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields | | | 🔧 | | | -| [no-children-prop](docs/rules/no-children-prop.md) | Disallow passing of children as props | ☑️ | | | | | -| [no-danger](docs/rules/no-danger.md) | Disallow usage of dangerous JSX properties | | | | | | -| [no-danger-with-children](docs/rules/no-danger-with-children.md) | Disallow when a DOM element is using both children and dangerouslySetInnerHTML | ☑️ | | | | | -| [no-deprecated](docs/rules/no-deprecated.md) | Disallow usage of deprecated methods | ☑️ | | | | | -| [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) | Disallow usage of setState in componentDidMount | | | | | | -| [no-did-update-set-state](docs/rules/no-did-update-set-state.md) | Disallow usage of setState in componentDidUpdate | | | | | | -| [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md) | Disallow direct mutation of this.state | ☑️ | | | | | -| [no-find-dom-node](docs/rules/no-find-dom-node.md) | Disallow usage of findDOMNode | ☑️ | | | | | -| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | | | 💡 | | -| [no-is-mounted](docs/rules/no-is-mounted.md) | Disallow usage of isMounted | ☑️ | | | | | -| [no-multi-comp](docs/rules/no-multi-comp.md) | Disallow multiple component definition per file | | | | | | -| [no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements | | | | | | -| [no-object-type-as-default-prop](docs/rules/no-object-type-as-default-prop.md) | Disallow usage of referential-type variables as default param in functional component | | | | | | -| [no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md) | Disallow usage of shouldComponentUpdate when extending React.PureComponent | | | | | | -| [no-render-return-value](docs/rules/no-render-return-value.md) | Disallow usage of the return value of ReactDOM.render | ☑️ | | | | | -| [no-set-state](docs/rules/no-set-state.md) | Disallow usage of setState | | | | | | -| [no-string-refs](docs/rules/no-string-refs.md) | Disallow using string references | ☑️ | | | | | -| [no-this-in-sfc](docs/rules/no-this-in-sfc.md) | Disallow `this` from being used in stateless functional components | | | | | | -| [no-typos](docs/rules/no-typos.md) | Disallow common typos | | | | | | -| [no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Disallow unescaped HTML entities from appearing in markup | ☑️ | | | | | -| [no-unknown-property](docs/rules/no-unknown-property.md) | Disallow usage of unknown DOM property | ☑️ | | 🔧 | | | -| [no-unsafe](docs/rules/no-unsafe.md) | Disallow usage of unsafe lifecycle methods | | ☑️ | | | | -| [no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Disallow creating unstable components inside components | | | | | | -| [no-unused-class-component-methods](docs/rules/no-unused-class-component-methods.md) | Disallow declaring unused methods of component class | | | | | | -| [no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Disallow definitions of unused propTypes | | | | | | -| [no-unused-state](docs/rules/no-unused-state.md) | Disallow definitions of unused state | | | | | | -| [no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Disallow usage of setState in componentWillUpdate | | | | | | -| [prefer-es6-class](docs/rules/prefer-es6-class.md) | Enforce ES5 or ES6 class for React Components | | | | | | -| [prefer-exact-props](docs/rules/prefer-exact-props.md) | Prefer exact proptype definitions | | | | | | -| [prefer-read-only-props](docs/rules/prefer-read-only-props.md) | Enforce that props are read-only | | | 🔧 | | | -| [prefer-stateless-function](docs/rules/prefer-stateless-function.md) | Enforce stateless components to be written as a pure function | | | | | | -| [prop-types](docs/rules/prop-types.md) | Disallow missing props validation in a React component definition | ☑️ | | | | | -| [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md) | Disallow missing React when using JSX | ☑️ | 🏃 | | | | -| [require-default-props](docs/rules/require-default-props.md) | Enforce a defaultProps definition for every prop that is not a required prop | | | | | | -| [require-optimization](docs/rules/require-optimization.md) | Enforce React components to have a shouldComponentUpdate method | | | | | | -| [require-render-return](docs/rules/require-render-return.md) | Enforce ES5 or ES6 class for returning value in render function | ☑️ | | | | | -| [self-closing-comp](docs/rules/self-closing-comp.md) | Disallow extra closing tags for components without children | | | 🔧 | | | -| [sort-comp](docs/rules/sort-comp.md) | Enforce component methods order | | | | | | -| [sort-default-props](docs/rules/sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | | -| [sort-prop-types](docs/rules/sort-prop-types.md) | Enforce propTypes declarations alphabetical sorting | | | 🔧 | | | -| [state-in-constructor](docs/rules/state-in-constructor.md) | Enforce class component state initialization style | | | | | | -| [static-property-placement](docs/rules/static-property-placement.md) | Enforces where React component static properties should be positioned. | | | | | | -| [style-prop-object](docs/rules/style-prop-object.md) | Enforce style prop value is an object | | | | | | -| [void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md) | Disallow void DOM elements (e.g. ``, `
`) from receiving children | | | | | | +| Name                                  | Description | 💼 | 🚫 | 🔧 | 💡 | ❌ | +| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | +| [boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | | | | | +| [button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | | | | | +| [checked-requires-onchange-or-readonly](docs/rules/checked-requires-onchange-or-readonly.md) | Enforce using `onChange` or `readonly` attribute when `checked` is used | | | | | | +| [default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps have a corresponding non-required PropType | | | | | | +| [destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context | | | 🔧 | | | +| [display-name](docs/rules/display-name.md) | Disallow missing displayName in a React component definition | ☑️ | | | | | +| [forbid-component-props](docs/rules/forbid-component-props.md) | Disallow certain props on components | | | | | | +| [forbid-dom-props](docs/rules/forbid-dom-props.md) | Disallow certain props on DOM Nodes | | | | | | +| [forbid-elements](docs/rules/forbid-elements.md) | Disallow certain elements | | | | | | +| [forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Disallow using another component's propTypes | | | | | | +| [forbid-prop-types](docs/rules/forbid-prop-types.md) | Disallow certain propTypes | | | | | | +| [function-component-definition](docs/rules/function-component-definition.md) | Enforce a specific function type for function components | | | 🔧 | | | +| [hook-use-state](docs/rules/hook-use-state.md) | Ensure destructuring and symmetric naming of useState hook value and setter variables | | | | 💡 | | +| [iframe-missing-sandbox](docs/rules/iframe-missing-sandbox.md) | Enforce sandbox attribute on iframe elements | | | | | | +| [jsx-boolean-value](docs/rules/jsx-boolean-value.md) | Enforce boolean attributes notation in JSX | | | 🔧 | | | +| [jsx-child-element-spacing](docs/rules/jsx-child-element-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | | | | +| [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md) | Enforce closing bracket location in JSX | | | 🔧 | | | +| [jsx-closing-tag-location](docs/rules/jsx-closing-tag-location.md) | Enforce closing tag location for multiline JSX | | | 🔧 | | | +| [jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md) | Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes | | | 🔧 | | | +| [jsx-curly-newline](docs/rules/jsx-curly-newline.md) | Enforce consistent linebreaks in curly braces in JSX attributes and expressions | | | 🔧 | | | +| [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | 🔧 | | | +| [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md) | Enforce or disallow spaces around equal signs in JSX attributes | | | 🔧 | | | +| [jsx-filename-extension](docs/rules/jsx-filename-extension.md) | Disallow file extensions that may contain JSX | | | | | | +| [jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md) | Enforce proper position of the first property in JSX | | | 🔧 | | | +| [jsx-fragments](docs/rules/jsx-fragments.md) | Enforce shorthand or standard form for React fragments | | | 🔧 | | | +| [jsx-handler-names](docs/rules/jsx-handler-names.md) | Enforce event handler naming conventions in JSX | | | | | | +| [jsx-indent](docs/rules/jsx-indent.md) | Enforce JSX indentation | | | 🔧 | | | +| [jsx-indent-props](docs/rules/jsx-indent-props.md) | Enforce props indentation in JSX | | | 🔧 | | | +| [jsx-key](docs/rules/jsx-key.md) | Disallow missing `key` props in iterators/collection literals | ☑️ | | | | | +| [jsx-max-depth](docs/rules/jsx-max-depth.md) | Enforce JSX maximum depth | | | | | | +| [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Enforce maximum of props on a single line in JSX | | | 🔧 | | | +| [jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. | | | 🔧 | | | +| [jsx-no-bind](docs/rules/jsx-no-bind.md) | Disallow `.bind()` or arrow functions in JSX props | | | | | | +| [jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Disallow comments from being inserted as text nodes | ☑️ | | | | | +| [jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Disallows JSX context provider values from taking values that will cause needless rerenders | | | | | | +| [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md) | Disallow duplicate properties in JSX | ☑️ | | | | | +| [jsx-no-leaked-render](docs/rules/jsx-no-leaked-render.md) | Disallow problematic leaked values from being rendered | | | 🔧 | | | +| [jsx-no-literals](docs/rules/jsx-no-literals.md) | Disallow usage of string literals in JSX | | | | | | +| [jsx-no-script-url](docs/rules/jsx-no-script-url.md) | Disallow usage of `javascript:` URLs | | | | | | +| [jsx-no-target-blank](docs/rules/jsx-no-target-blank.md) | Disallow `target="_blank"` attribute without `rel="noreferrer"` | ☑️ | | 🔧 | | | +| [jsx-no-undef](docs/rules/jsx-no-undef.md) | Disallow undeclared variables in JSX | ☑️ | | | | | +| [jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md) | Disallow unnecessary fragments | | | 🔧 | | | +| [jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md) | Require one JSX element per line | | | 🔧 | | | +| [jsx-pascal-case](docs/rules/jsx-pascal-case.md) | Enforce PascalCase for user-defined JSX components | | | | | | +| [jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | | 🔧 | | | +| [jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | | | | | | +| [jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | ❌ | +| [jsx-sort-props](docs/rules/jsx-sort-props.md) | Enforce props alphabetical sorting | | | 🔧 | | | +| [jsx-space-before-closing](docs/rules/jsx-space-before-closing.md) | Enforce spacing before closing bracket in JSX | | | 🔧 | | ❌ | +| [jsx-tag-spacing](docs/rules/jsx-tag-spacing.md) | Enforce whitespace in and around the JSX opening and closing brackets | | | 🔧 | | | +| [jsx-uses-react](docs/rules/jsx-uses-react.md) | Disallow React to be incorrectly marked as unused | ☑️ | 🏃 | | | | +| [jsx-uses-vars](docs/rules/jsx-uses-vars.md) | Disallow variables used in JSX to be incorrectly marked as unused | ☑️ | | | | | +| [jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md) | Disallow missing parentheses around multiline JSX | | | 🔧 | | | +| [no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Disallow when this.state is accessed within setState | | | | | | +| [no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Disallow adjacent inline elements not separated by whitespace. | | | | | | +| [no-array-index-key](docs/rules/no-array-index-key.md) | Disallow usage of Array index in keys | | | | | | +| [no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields | | | 🔧 | | | +| [no-children-prop](docs/rules/no-children-prop.md) | Disallow passing of children as props | ☑️ | | | | | +| [no-danger](docs/rules/no-danger.md) | Disallow usage of dangerous JSX properties | | | | | | +| [no-danger-with-children](docs/rules/no-danger-with-children.md) | Disallow when a DOM element is using both children and dangerouslySetInnerHTML | ☑️ | | | | | +| [no-deprecated](docs/rules/no-deprecated.md) | Disallow usage of deprecated methods | ☑️ | | | | | +| [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) | Disallow usage of setState in componentDidMount | | | | | | +| [no-did-update-set-state](docs/rules/no-did-update-set-state.md) | Disallow usage of setState in componentDidUpdate | | | | | | +| [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md) | Disallow direct mutation of this.state | ☑️ | | | | | +| [no-find-dom-node](docs/rules/no-find-dom-node.md) | Disallow usage of findDOMNode | ☑️ | | | | | +| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | | | 💡 | | +| [no-is-mounted](docs/rules/no-is-mounted.md) | Disallow usage of isMounted | ☑️ | | | | | +| [no-multi-comp](docs/rules/no-multi-comp.md) | Disallow multiple component definition per file | | | | | | +| [no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements | | | | | | +| [no-object-type-as-default-prop](docs/rules/no-object-type-as-default-prop.md) | Disallow usage of referential-type variables as default param in functional component | | | | | | +| [no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md) | Disallow usage of shouldComponentUpdate when extending React.PureComponent | | | | | | +| [no-render-return-value](docs/rules/no-render-return-value.md) | Disallow usage of the return value of ReactDOM.render | ☑️ | | | | | +| [no-set-state](docs/rules/no-set-state.md) | Disallow usage of setState | | | | | | +| [no-string-refs](docs/rules/no-string-refs.md) | Disallow using string references | ☑️ | | | | | +| [no-this-in-sfc](docs/rules/no-this-in-sfc.md) | Disallow `this` from being used in stateless functional components | | | | | | +| [no-typos](docs/rules/no-typos.md) | Disallow common typos | | | | | | +| [no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Disallow unescaped HTML entities from appearing in markup | ☑️ | | | | | +| [no-unknown-property](docs/rules/no-unknown-property.md) | Disallow usage of unknown DOM property | ☑️ | | 🔧 | | | +| [no-unsafe](docs/rules/no-unsafe.md) | Disallow usage of unsafe lifecycle methods | | ☑️ | | | | +| [no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Disallow creating unstable components inside components | | | | | | +| [no-unused-class-component-methods](docs/rules/no-unused-class-component-methods.md) | Disallow declaring unused methods of component class | | | | | | +| [no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Disallow definitions of unused propTypes | | | | | | +| [no-unused-state](docs/rules/no-unused-state.md) | Disallow definitions of unused state | | | | | | +| [no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Disallow usage of setState in componentWillUpdate | | | | | | +| [prefer-es6-class](docs/rules/prefer-es6-class.md) | Enforce ES5 or ES6 class for React Components | | | | | | +| [prefer-exact-props](docs/rules/prefer-exact-props.md) | Prefer exact proptype definitions | | | | | | +| [prefer-read-only-props](docs/rules/prefer-read-only-props.md) | Enforce that props are read-only | | | 🔧 | | | +| [prefer-stateless-function](docs/rules/prefer-stateless-function.md) | Enforce stateless components to be written as a pure function | | | | | | +| [prop-types](docs/rules/prop-types.md) | Disallow missing props validation in a React component definition | ☑️ | | | | | +| [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md) | Disallow missing React when using JSX | ☑️ | 🏃 | | | | +| [require-default-props](docs/rules/require-default-props.md) | Enforce a defaultProps definition for every prop that is not a required prop | | | | | | +| [require-optimization](docs/rules/require-optimization.md) | Enforce React components to have a shouldComponentUpdate method | | | | | | +| [require-render-return](docs/rules/require-render-return.md) | Enforce ES5 or ES6 class for returning value in render function | ☑️ | | | | | +| [self-closing-comp](docs/rules/self-closing-comp.md) | Disallow extra closing tags for components without children | | | 🔧 | | | +| [sort-comp](docs/rules/sort-comp.md) | Enforce component methods order | | | | | | +| [sort-default-props](docs/rules/sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | | +| [sort-prop-types](docs/rules/sort-prop-types.md) | Enforce propTypes declarations alphabetical sorting | | | 🔧 | | | +| [state-in-constructor](docs/rules/state-in-constructor.md) | Enforce class component state initialization style | | | | | | +| [static-property-placement](docs/rules/static-property-placement.md) | Enforces where React component static properties should be positioned. | | | | | | +| [style-prop-object](docs/rules/style-prop-object.md) | Enforce style prop value is an object | | | | | | +| [void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md) | Disallow void DOM elements (e.g. ``, `
`) from receiving children | | | | | | diff --git a/docs/rules/checked-requires-onchange-or-readonly.md b/docs/rules/checked-requires-onchange-or-readonly.md new file mode 100644 index 0000000000..13ec64dd0e --- /dev/null +++ b/docs/rules/checked-requires-onchange-or-readonly.md @@ -0,0 +1,44 @@ +# Enforce using `onChange` or `readonly` attribute when `checked` is used (`react/checked-requires-onchange-or-readonly`) + + + +This rule enforces `onChange` or `readonly` attribute for `checked` property of input elements. + +It also warns when `checked` and `defaultChecked` properties are used together. + +## Rule Details + +Example of **incorrect** code for this rule: + +```jsx + + + + +React.createElement('input', { checked: false }); +React.createElement('input', { type: 'checkbox', checked: true }); +React.createElement('input', { type: 'checkbox', checked: true, defaultChecked: true }); +``` + +Example of **correct** code for this rule: + +```jsx + {}} /> + + + + +React.createElement('input', { type: 'checkbox', checked: true, onChange() {} }); +React.createElement('input', { type: 'checkbox', checked: true, readOnly: true }); +React.createElement('input', { type: 'checkbox', checked: true, onChange() {}, readOnly: true }); +React.createElement('input', { type: 'checkbox', defaultChecked: true }); +``` + +## Rule Options + +```js +"react/checked-requires-onchange-or-readonly": [, { + "ignoreMissingProperties": , + "ignoreExclusiveCheckedAttribute": +}] +``` diff --git a/lib/rules/checked-requires-onchange-or-readonly.js b/lib/rules/checked-requires-onchange-or-readonly.js new file mode 100644 index 0000000000..4c1cc15dd1 --- /dev/null +++ b/lib/rules/checked-requires-onchange-or-readonly.js @@ -0,0 +1,141 @@ +/** + * @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used' + * @author Jaesoekjjang + */ + +'use strict'; + +const ASTUtils = require('jsx-ast-utils'); +const flatMap = require('array.prototype.flatmap'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); +const docsUrl = require('../util/docsUrl'); + +const messages = { + missingProperty: '`checked` should be used with either `onChange` or `readOnly`.', + exclusiveCheckedAttribute: 'Use either `checked` or `defaultChecked`, but not both.', +}; + +const targetPropSet = new Set(['checked', 'onChange', 'readOnly', 'defaultChecked']); + +const defaultOptions = { + ignoreMissingProperties: true, + ignoreExclusiveCheckedAttribute: true, +}; + +/** + * @param {string[]} properties + * @param {string} keyName + * @returns {Set} + */ +function extractTargetProps(properties, keyName) { + return new Set( + flatMap( + properties, + (prop) => ( + prop[keyName] && targetPropSet.has(prop[keyName].name) + ? [prop[keyName].name] + : [] + ) + ) + ); +} + +module.exports = { + meta: { + docs: { + description: 'Enforce using `onChange` or `readonly` attribute when `checked` is used', + category: 'Best Practices', + recommended: false, + url: docsUrl('checked-requires-onchange-or-readonly'), + }, + messages, + schema: [{ + additionalProperties: false, + properties: { + ignoreMissingProperties: { + type: 'boolean', + }, + ignoreExclusiveCheckedAttribute: { + type: 'boolean', + }, + }, + }], + }, + create(context) { + const options = Object.assign({}, defaultOptions, context.options[0]); + + function reportMissingProperty(node) { + report( + context, + messages.missingProperty, + 'missingProperty', + { node } + ); + } + + function reportExclusiveCheckedAttribute(node) { + report( + context, + messages.exclusiveCheckedAttribute, + 'exclusiveCheckedAttribute', + { node } + ); + } + + /** + * @param {ASTNode} node + * @param {Set} propSet + * @returns {void} + */ + const checkAttributesAndReport = (node, propSet) => { + if (!propSet.has('checked')) { + return; + } + + if (options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) { + reportExclusiveCheckedAttribute(node); + } + + if ( + options.ignoreMissingProperties + && !(propSet.has('onChange') || propSet.has('readOnly')) + ) { + reportMissingProperty(node); + } + }; + + return { + JSXOpeningElement(node) { + if (ASTUtils.elementType(node) !== 'input') { + return; + } + + const propSet = extractTargetProps(node.attributes, 'name'); + checkAttributesAndReport(node, propSet); + }, + CallExpression(node) { + if (!isCreateElement(node, context)) { + return; + } + + const firstArg = node.arguments[0]; + const secondArg = node.arguments[1]; + if ( + !firstArg + || firstArg.type !== 'Literal' + || firstArg.value !== 'input' + ) { + return; + } + + if (!secondArg || secondArg.type !== 'ObjectExpression') { + return; + } + + const propSet = extractTargetProps(secondArg.properties, 'key'); + checkAttributesAndReport(node, propSet); + }, + }; + }, +}; diff --git a/lib/rules/index.js b/lib/rules/index.js index d7142ed9b4..0c14f0795e 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -5,6 +5,7 @@ module.exports = { 'boolean-prop-naming': require('./boolean-prop-naming'), 'button-has-type': require('./button-has-type'), + 'checked-requires-onchange-or-readonly': require('./checked-requires-onchange-or-readonly'), 'default-props-match-prop-types': require('./default-props-match-prop-types'), 'destructuring-assignment': require('./destructuring-assignment'), 'display-name': require('./display-name'), diff --git a/tests/lib/rules/checked-requires-onchange-or-readonly.js b/tests/lib/rules/checked-requires-onchange-or-readonly.js new file mode 100644 index 0000000000..7f9a14cac4 --- /dev/null +++ b/tests/lib/rules/checked-requires-onchange-or-readonly.js @@ -0,0 +1,111 @@ +/** + * @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used' + * @author Jaesoekjjang + */ + +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/checked-requires-onchange-or-readonly'); + +const parsers = require('../../helpers/parsers'); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run('checked-requires-onchange-or-readonly', rule, { + valid: parsers.all([ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + "React.createElement('input')", + "React.createElement('input', { checked: true, onChange: noop })", + "React.createElement('input', { checked: false, onChange: noop })", + "React.createElement('input', { checked: true, readOnly: true })", + "React.createElement('input', { checked: true, onChange: noop, readOnly: true })", + "React.createElement('input', { checked: foo, onChange: noop, readOnly: true })", + { + code: '', + options: [{ ignoreMissingProperties: false }], + }, + { + code: '', + options: [{ ignoreMissingProperties: false }], + }, + { + code: '', + options: [{ ignoreExclusiveCheckedAttribute: false }], + }, + { + code: '', + options: [{ ignoreExclusiveCheckedAttribute: false }], + }, + '', + "React.createElement('span')", + '(()=>{})()', + ]), + invalid: parsers.all([ + { + code: '', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: '', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: '', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: '', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: '', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: '', + errors: [ + { messageId: 'exclusiveCheckedAttribute' }, + { messageId: 'missingProperty' }, + ], + }, + { + code: 'React.createElement("input", { checked: false })', + errors: [{ messageId: 'missingProperty' }], + }, + { + code: 'React.createElement("input", { checked: true, defaultChecked: true })', + errors: [ + { messageId: 'exclusiveCheckedAttribute' }, + { messageId: 'missingProperty' }, + ], + }, + { + code: '', + options: [{ ignoreMissingProperties: false }], + errors: [{ messageId: 'exclusiveCheckedAttribute' }], + }, + { + code: '', + options: [{ ignoreExclusiveCheckedAttribute: false }], + errors: [{ messageId: 'missingProperty' }], + }, + ]), +}); From d4e58aacf08b8eb846f1587f305a41556d699754 Mon Sep 17 00:00:00 2001 From: "C. T. Lin" Date: Mon, 28 Aug 2023 15:33:35 +0800 Subject: [PATCH 004/174] [Docs] `hook-use-state`: fix an undefined variable --- CHANGELOG.md | 2 ++ docs/rules/hook-use-state.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c21e803d8..1d4d736039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek) * [Docs] [`iframe-missing-sandbox`]: fix link to iframe attribute on mdn ([#3690][] @nnmrts) +* [Docs] [`hook-use-state`]: fix an undefined variable ([#3626][] @chentsulin) [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 [#3680]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3680 @@ -54,6 +55,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [#3634]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3634 [#3633]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3633 [#3630]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3630 +[#3626]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3626 [#3623]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3623 [#3615]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3615 [#3545]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3545 diff --git a/docs/rules/hook-use-state.md b/docs/rules/hook-use-state.md index 838d86b607..9654f270fb 100644 --- a/docs/rules/hook-use-state.md +++ b/docs/rules/hook-use-state.md @@ -27,7 +27,7 @@ export default function useColor() { // useState call is destructured into value + setter pair, but identifier // names do not follow the [thing, setThing] naming convention const [color, updateColor] = React.useState(); - return useStateResult; + return [color, updateColor]; } ``` From b8a0580f881c299933357331e5fceae915486e7c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Feb 2024 00:00:56 -0800 Subject: [PATCH 005/174] [Dev Deps] update `@babel/core`, `@babel/eslint-parser`, `eslint-doc-generator` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index febbcc6a0e..e47e17d5d7 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,8 @@ "string.prototype.matchall": "^4.0.10" }, "devDependencies": { - "@babel/core": "^7.23.6", - "@babel/eslint-parser": "^7.23.3", + "@babel/core": "^7.23.9", + "@babel/eslint-parser": "^7.23.10", "@babel/plugin-syntax-decorators": "^7.23.3", "@babel/plugin-syntax-do-expressions": "^7.23.3", "@babel/plugin-syntax-function-bind": "^7.23.3", @@ -59,7 +59,7 @@ "babel-eslint": "^8 || ^9 || ^10.1.0", "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint-config-airbnb-base": "^15.0.0", - "eslint-doc-generator": "^1.6.1", + "eslint-doc-generator": "^1.6.2", "eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", "eslint-plugin-import": "^2.29.1", "eslint-remote-tester": "^3.0.1", From 36e791de784e7fa1edd5b6d232fbcaf2dd7cf9bf Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 19 Feb 2024 00:01:56 -0800 Subject: [PATCH 006/174] [Deps] update `array.prototype.findlast`, `array.prototype.tosorted`, `es-iterator-helpers` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e47e17d5d7..8a3d16489b 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,12 @@ "bugs": "https://github.com/jsx-eslint/eslint-plugin-react/issues", "dependencies": { "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.3", + "array.prototype.findlast": "^1.2.4", "array.prototype.flatmap": "^1.3.2", "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.15", + "es-iterator-helpers": "^1.0.17", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", From e880213f3958f44ccca4642d7593e26de72864cf Mon Sep 17 00:00:00 2001 From: Daniil Suvorov Date: Mon, 26 Feb 2024 11:13:08 +0300 Subject: [PATCH 007/174] [Fix] `no-unknown-property`: add `fetchPriority` - DefinitelyTyped/DefinitelyTyped#68645 - https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchpriority --- CHANGELOG.md | 2 ++ lib/rules/no-unknown-property.js | 2 +- tests/lib/rules/no-unknown-property.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d4d736039..9e89710802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-newline`]: prevent a crash when `allowMultilines ([#3633][] @ljharb) * [`no-unknown-property`]: use a better regex to avoid a crash ([#3666][] @ljharb @SCH227) * [`prop-types`]: handle nested forwardRef + memo ([#3679][] @developer-bandi) +* [`no-unknown-property`]: add `fetchPriority` ([#3697][] @SevereCloud) ### Changed * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) @@ -37,6 +38,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`iframe-missing-sandbox`]: fix link to iframe attribute on mdn ([#3690][] @nnmrts) * [Docs] [`hook-use-state`]: fix an undefined variable ([#3626][] @chentsulin) +[#3697]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3697 [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 [#3680]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3680 [#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index 2e77f2898b..ec913e1fca 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -257,7 +257,7 @@ const DOM_PROPERTY_NAMES_TWO_WORDS = [ 'onError', 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoad', 'onWheel', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onToggle', 'onTransitionEnd', 'radioGroup', 'readOnly', 'referrerPolicy', - 'rowSpan', 'srcDoc', 'srcLang', 'srcSet', 'useMap', + 'rowSpan', 'srcDoc', 'srcLang', 'srcSet', 'useMap', 'fetchPriority', // SVG attributes // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute 'crossOrigin', 'accentHeight', 'alignmentBaseline', 'arabicForm', 'attributeName', diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js index 3c20feebd4..89624d540a 100644 --- a/tests/lib/rules/no-unknown-property.js +++ b/tests/lib/rules/no-unknown-property.js @@ -46,7 +46,7 @@ ruleTester.run('no-unknown-property', rule, { { code: '
;' }, { code: 'Read more' }, { code: '' }, - { code: 'A cat sleeping on a keyboard' }, + { code: 'A cat sleeping on a keyboard' }, { code: '' }, { code: '' }, { code: '' }, From 83a26dd3ddb8b89829b9ca5a55dd21c8fafb45d4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 Mar 2024 21:44:18 -0800 Subject: [PATCH 008/174] [Fix] `forbid-elements`: prevent a crash on `createElement()` See https://github.com/jsx-eslint/eslint-plugin-react/issues/3632#issuecomment-1975046999 --- CHANGELOG.md | 2 ++ lib/rules/forbid-elements.js | 4 ++++ tests/lib/rules/forbid-elements.js | 3 +++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e89710802..0cc3999fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-unknown-property`]: use a better regex to avoid a crash ([#3666][] @ljharb @SCH227) * [`prop-types`]: handle nested forwardRef + memo ([#3679][] @developer-bandi) * [`no-unknown-property`]: add `fetchPriority` ([#3697][] @SevereCloud) +* [`forbid-elements`]: prevent a crash on `createElement()` ([#3632][] @ljharb) ### Changed * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) @@ -56,6 +57,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [#3638]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3638 [#3634]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3634 [#3633]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3633 +[#3632]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3632 [#3630]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3630 [#3626]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3626 [#3623]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3623 diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index 7da8f89f42..c7f978a164 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -99,6 +99,10 @@ module.exports = { } const argument = node.arguments[0]; + if (!argument) { + return; + } + const argType = argument.type; if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) { diff --git a/tests/lib/rules/forbid-elements.js b/tests/lib/rules/forbid-elements.js index 927f7bd97f..0a80c3f853 100644 --- a/tests/lib/rules/forbid-elements.js +++ b/tests/lib/rules/forbid-elements.js @@ -82,6 +82,9 @@ ruleTester.run('forbid-elements', rule, { code: 'React.createElement(1)', options: [{ forbid: ['button'] }], }, + { + code: 'React.createElement()', + }, ]), invalid: parsers.all([ From bd55ca2364fc76fd4f5b735a8778a04c2c609843 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 Mar 2024 22:01:46 -0800 Subject: [PATCH 009/174] [Docs] update URLs --- README.md | 2 +- docs/rules/hook-use-state.md | 2 +- docs/rules/no-invalid-html-attribute.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b68767f0f0..64e57912a1 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ module.exports = [ 🏃 Set in the `jsx-runtime` [configuration](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs).\ ☑️ Set in the `recommended` [configuration](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs).\ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ -💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\ +💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\ ❌ Deprecated. | Name                                  | Description | 💼 | 🚫 | 🔧 | 💡 | ❌ | diff --git a/docs/rules/hook-use-state.md b/docs/rules/hook-use-state.md index 9654f270fb..3229d16bf4 100644 --- a/docs/rules/hook-use-state.md +++ b/docs/rules/hook-use-state.md @@ -1,6 +1,6 @@ # Ensure destructuring and symmetric naming of useState hook value and setter variables (`react/hook-use-state`) -💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). +💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-invalid-html-attribute.md b/docs/rules/no-invalid-html-attribute.md index d749ead037..9f251ca9ae 100644 --- a/docs/rules/no-invalid-html-attribute.md +++ b/docs/rules/no-invalid-html-attribute.md @@ -1,6 +1,6 @@ # Disallow usage of invalid attributes (`react/no-invalid-html-attribute`) -💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). +💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). From 2124d13297d0c9547ec4383160147410c30a5cfd Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 22 Feb 2024 09:29:21 +1030 Subject: [PATCH 010/174] [Fix] add a few jsdoc type annotations to work around TS inference for consumers --- configs/all.js | 10 ++++++++++ lib/rules/index.js | 1 + 2 files changed, 11 insertions(+) diff --git a/configs/all.js b/configs/all.js index c507744ac1..bcb416e295 100644 --- a/configs/all.js +++ b/configs/all.js @@ -9,6 +9,10 @@ function filterRules(rules, predicate) { return fromEntries(entries(rules).filter((entry) => predicate(entry[1]))); } +/** + * @param {object} rules - rules object mapping rule name to rule module + * @returns {Record} + */ function configureAsError(rules) { return fromEntries(Object.keys(rules).map((key) => [`react/${key}`, 2])); } @@ -20,6 +24,12 @@ const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated); module.exports = { plugins: { + /** + * @type {{ + * deprecatedRules: Record, + * rules: Record, + * }} + */ react: { deprecatedRules, rules: allRules, diff --git a/lib/rules/index.js b/lib/rules/index.js index 0c14f0795e..784831bba7 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -2,6 +2,7 @@ /* eslint global-require: 0 */ +/** @type {Record} */ module.exports = { 'boolean-prop-naming': require('./boolean-prop-naming'), 'button-has-type': require('./button-has-type'), From 6cb0f008d1ca1b657361f377d3828d6ef68656d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 18 Feb 2024 22:58:38 +0900 Subject: [PATCH 011/174] [Fix] `jsx-boolean-value`: make error messages clearer --- CHANGELOG.md | 2 + lib/rules/jsx-boolean-value.js | 37 ++++------------- tests/lib/rules/jsx-boolean-value.js | 61 ++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc3999fcb..4fac357d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`forbid-elements`]: prevent a crash on `createElement()` ([#3632][] @ljharb) ### Changed +* [`jsx-boolean-value`]: make error messages clearer ([#3691][] @developer-bandi) * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) * [Refactor] [`boolean-prop-naming`]: invert if statement ([#3634][] @HenryBrown0) * [Refactor] [`function-component-definition`]: exit early if no type params ([#3634][] @HenryBrown0) @@ -40,6 +41,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`hook-use-state`]: fix an undefined variable ([#3626][] @chentsulin) [#3697]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3697 +[#3691]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3691 [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 [#3680]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3680 [#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js index ae6dda0da7..0090c0a174 100644 --- a/lib/rules/jsx-boolean-value.js +++ b/lib/rules/jsx-boolean-value.js @@ -21,19 +21,6 @@ const exceptionsSchema = { const ALWAYS = 'always'; const NEVER = 'never'; -const errorData = new WeakMap(); -/** - * @param {object} exceptions - * @returns {object} - */ -function getErrorData(exceptions) { - if (!errorData.has(exceptions)) { - const exceptionProps = Array.from(exceptions, (name) => `\`${name}\``).join(', '); - const exceptionsMessage = exceptions.size > 0 ? ` for the following props: ${exceptionProps}` : ''; - errorData.set(exceptions, { exceptionsMessage }); - } - return errorData.get(exceptions); -} /** * @param {string} configuration * @param {Set} exceptions @@ -62,12 +49,9 @@ function isNever(configuration, exceptions, propName) { } const messages = { - omitBoolean: 'Value must be omitted for boolean attributes{{exceptionsMessage}}', - omitBoolean_noMessage: 'Value must be omitted for boolean attributes', - setBoolean: 'Value must be set for boolean attributes{{exceptionsMessage}}', - setBoolean_noMessage: 'Value must be set for boolean attributes', - omitPropAndBoolean: 'Value and Prop must be omitted for false attributes{{exceptionsMessage}}', - omitPropAndBoolean_noMessage: 'Value and Prop must be omitted for false attributes', + omitBoolean: 'Value must be omitted for boolean attribute `{{propName}}`', + setBoolean: 'Value must be set for boolean attribute `{{propName}}`', + omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`', }; module.exports = { @@ -135,8 +119,8 @@ module.exports = { isAlways(configuration, exceptions, propName) && value === null ) { - const data = getErrorData(exceptions); - const messageId = data.exceptionsMessage ? 'setBoolean' : 'setBoolean_noMessage'; + const messageId = 'setBoolean'; + const data = { propName }; report(context, messages[messageId], messageId, { node, data, @@ -152,8 +136,8 @@ module.exports = { && value.type === 'JSXExpressionContainer' && value.expression.value === true ) { - const data = getErrorData(exceptions); - const messageId = data.exceptionsMessage ? 'omitBoolean' : 'omitBoolean_noMessage'; + const messageId = 'omitBoolean'; + const data = { propName }; report(context, messages[messageId], messageId, { node, data, @@ -169,11 +153,8 @@ module.exports = { && value.type === 'JSXExpressionContainer' && value.expression.value === false ) { - const data = getErrorData(exceptions); - const messageId = data.exceptionsMessage - ? 'omitPropAndBoolean' - : 'omitPropAndBoolean_noMessage'; - + const messageId = 'omitPropAndBoolean'; + const data = { propName }; report(context, messages[messageId], messageId, { node, data, diff --git a/tests/lib/rules/jsx-boolean-value.js b/tests/lib/rules/jsx-boolean-value.js index 678b492740..e8cbbb3e7f 100644 --- a/tests/lib/rules/jsx-boolean-value.js +++ b/tests/lib/rules/jsx-boolean-value.js @@ -63,7 +63,10 @@ ruleTester.run('jsx-boolean-value', rule, { output: ';', options: ['never'], errors: [ - { messageId: 'omitBoolean_noMessage' }, + { + messageId: 'omitBoolean', + data: { propName: 'foo' }, + }, ], }, { @@ -73,11 +76,11 @@ ruleTester.run('jsx-boolean-value', rule, { errors: [ { messageId: 'omitBoolean', - data: { exceptionsMessage: ' for the following props: `foo`, `bar`' }, + data: { propName: 'foo' }, }, { messageId: 'omitBoolean', - data: { exceptionsMessage: ' for the following props: `foo`, `bar`' }, + data: { propName: 'bar' }, }, ], }, @@ -85,14 +88,20 @@ ruleTester.run('jsx-boolean-value', rule, { code: ';', output: ';', errors: [ - { messageId: 'omitBoolean_noMessage' }, + { + messageId: 'omitBoolean', + data: { propName: 'foo' }, + }, ], }, { code: ';', output: ';', errors: [ - { messageId: 'omitBoolean_noMessage' }, + { + messageId: 'omitBoolean', + data: { propName: 'foo' }, + }, ], }, { @@ -100,7 +109,10 @@ ruleTester.run('jsx-boolean-value', rule, { output: ';', options: ['always'], errors: [ - { messageId: 'setBoolean_noMessage' }, + { + messageId: 'setBoolean', + data: { propName: 'foo' }, + }, ], }, { @@ -110,11 +122,11 @@ ruleTester.run('jsx-boolean-value', rule, { errors: [ { messageId: 'setBoolean', - data: { exceptionsMessage: ' for the following props: `foo`, `bar`' }, + data: { propName: 'foo' }, }, { messageId: 'setBoolean', - data: { exceptionsMessage: ' for the following props: `foo`, `bar`' }, + data: { propName: 'bar' }, }, ], }, @@ -123,8 +135,14 @@ ruleTester.run('jsx-boolean-value', rule, { output: ';', options: ['never', { assumeUndefinedIsFalse: true }], errors: [ - { messageId: 'omitPropAndBoolean_noMessage' }, - { messageId: 'omitPropAndBoolean_noMessage' }, + { + messageId: 'omitPropAndBoolean', + data: { propName: 'foo' }, + }, + { + messageId: 'omitPropAndBoolean', + data: { propName: 'bak' }, + }, ], }, { @@ -137,11 +155,30 @@ ruleTester.run('jsx-boolean-value', rule, { errors: [ { messageId: 'omitPropAndBoolean', - data: { exceptionsMessage: ' for the following props: `baz`, `bak`' }, + data: { propName: 'baz' }, }, { messageId: 'omitPropAndBoolean', - data: { exceptionsMessage: ' for the following props: `baz`, `bak`' }, + data: { propName: 'bak' }, + }, + ], + }, + { + code: ';', + output: ';', + options: ['always', { never: ['foo', 'bar'] }], + errors: [ + { + messageId: 'omitBoolean', + data: { propName: 'foo' }, + }, + { + messageId: 'omitBoolean', + data: { propName: 'bar' }, + }, + { + messageId: 'setBoolean', + data: { propName: 'baz' }, }, ], }, From 24d21ac16a3955209c33d0f8943027ead7e48881 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Mar 2024 15:53:25 -0800 Subject: [PATCH 012/174] [Dev Deps] update `@babel/core`, `@babel/plugin-syntax-decorators`, `eslint-doc-generator` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8a3d16489b..4c3989d27f 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,9 @@ "string.prototype.matchall": "^4.0.10" }, "devDependencies": { - "@babel/core": "^7.23.9", + "@babel/core": "^7.24.0", "@babel/eslint-parser": "^7.23.10", - "@babel/plugin-syntax-decorators": "^7.23.3", + "@babel/plugin-syntax-decorators": "^7.24.0", "@babel/plugin-syntax-do-expressions": "^7.23.3", "@babel/plugin-syntax-function-bind": "^7.23.3", "@babel/preset-react": "^7.23.3", @@ -59,7 +59,7 @@ "babel-eslint": "^8 || ^9 || ^10.1.0", "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint-config-airbnb-base": "^15.0.0", - "eslint-doc-generator": "^1.6.2", + "eslint-doc-generator": "^1.7.0", "eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", "eslint-plugin-import": "^2.29.1", "eslint-remote-tester": "^3.0.1", From 2e6b5578cf9d8df2a4d35b9f5f3d851cf07ff483 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Mar 2024 22:10:57 -0800 Subject: [PATCH 013/174] Update CHANGELOG and bump version --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fac357d9b..bc5aa9732d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## [7.34.0] - 2024.03.03 + ### Added * [`sort-prop-types`]: give errors on TS types ([#3615][] @akulsr0) * [`no-invalid-html-attribute`]: add support for `apple-touch-startup-image` `rel` attributes in `link` tags ([#3638][] @thomashockaday) @@ -40,6 +42,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`iframe-missing-sandbox`]: fix link to iframe attribute on mdn ([#3690][] @nnmrts) * [Docs] [`hook-use-state`]: fix an undefined variable ([#3626][] @chentsulin) +[7.34.0]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.33.2...v7.34.0 [#3697]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3697 [#3691]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3691 [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690 diff --git a/package.json b/package.json index 4c3989d27f..66d7078685 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-react", - "version": "7.33.2", + "version": "7.34.0", "author": "Yannick Croissant ", "description": "React specific linting rules for ESLint", "main": "index.js", From b7780ceeabadc744417801468e279d23f1b375ad Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 3 Mar 2024 22:16:34 -0800 Subject: [PATCH 014/174] [meta] fix CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc5aa9732d..8bc96b2e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0) * [`jsx-key`]: detect conditional returns ([#3630][] @yialo) -* [`jsx-newline`]: prevent a crash when `allowMultilines ([#3633][] @ljharb) +* [`jsx-newline`]: prevent a crash when `allowMultilines` ([#3633][] @ljharb) * [`no-unknown-property`]: use a better regex to avoid a crash ([#3666][] @ljharb @SCH227) * [`prop-types`]: handle nested forwardRef + memo ([#3679][] @developer-bandi) * [`no-unknown-property`]: add `fetchPriority` ([#3697][] @SevereCloud) From 730bac96b20d20116956c46414a65b5910687a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 5 Mar 2024 00:55:59 +0900 Subject: [PATCH 015/174] [Fix] `jsx-no-leaked-render`: prevent wrongly adding parens --- CHANGELOG.md | 5 +++++ lib/rules/jsx-no-leaked-render.js | 2 +- tests/lib/rules/jsx-no-leaked-render.js | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc96b2e46..826696a029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Fixed +* [`jsx-no-leaked-render`]: prevent wrongly adding parens ([#3700][] @developer-bandi) + +[#3700]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3700 + ## [7.34.0] - 2024.03.03 ### Added diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index 05f319edbf..b77cd576cc 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -79,7 +79,7 @@ function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNod return fixer.replaceText(reportedNode, `${newText} && ${alternateVal}`); } - if (rightNode.type === 'ConditionalExpression') { + if (rightNode.type === 'ConditionalExpression' || rightNode.type === 'LogicalExpression') { return fixer.replaceText(reportedNode, `${newText} && (${rightSideText})`); } if (rightNode.type === 'JSXElement') { diff --git a/tests/lib/rules/jsx-no-leaked-render.js b/tests/lib/rules/jsx-no-leaked-render.js index 969c93635c..f5729bf059 100644 --- a/tests/lib/rules/jsx-no-leaked-render.js +++ b/tests/lib/rules/jsx-no-leaked-render.js @@ -733,6 +733,24 @@ ruleTester.run('jsx-no-leaked-render', rule, { } `, }, + { + code: ` + const Component = ({ connection, hasError, hasErrorUpdate}) => { + return
{connection && (hasError || hasErrorUpdate)}
+ } + `, + options: [{ validStrategies: ['coerce'] }], + errors: [{ + message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes', + line: 3, + column: 24, + }], + output: ` + const Component = ({ connection, hasError, hasErrorUpdate}) => { + return
{!!connection && (hasError || hasErrorUpdate)}
+ } + `, + }, // cases: ternary isn't valid if strategy is only "coerce" { From 8c2bdb2636e9ab0eca588454c996e31e8c1dd29b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 4 Mar 2024 14:27:36 -0800 Subject: [PATCH 016/174] [actions] update workflows --- .github/workflows/node-18+.yml | 4 ++-- .github/workflows/node-minors.yml | 4 ++-- .github/workflows/node-pretest.yml | 4 ++-- .github/workflows/npm-publish.yml | 4 ++-- .github/workflows/rebase.yml | 12 +++--------- .github/workflows/release.yml | 2 +- .github/workflows/smoke-test.yml | 2 +- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/node-18+.yml b/.github/workflows/node-18+.yml index 921db8111b..e27cb0e7bf 100644 --- a/.github/workflows/node-18+.yml +++ b/.github/workflows/node-18+.yml @@ -39,7 +39,7 @@ jobs: - 8 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ljharb/actions/node/install@main name: 'nvm install ${{ matrix.node-version }} && npm install' with: @@ -50,7 +50,7 @@ jobs: NPM_CONFIG_LEGACY_PEER_DEPS: true - run: npx ls-engines - run: npm run unit-test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v3.1.5 node: name: 'node 18+' diff --git a/.github/workflows/node-minors.yml b/.github/workflows/node-minors.yml index 2b5230268a..548b571dd2 100644 --- a/.github/workflows/node-minors.yml +++ b/.github/workflows/node-minors.yml @@ -94,7 +94,7 @@ jobs: eslint: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ljharb/actions/node/install@main name: 'nvm install ${{ matrix.node-version }} && npm install' with: @@ -107,7 +107,7 @@ jobs: - run: npx ls-engines if: ${{ matrix.node-version >= 12 }} - run: npm run unit-test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v3.1.5 node: name: 'node 4 - 17' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 5177ab52fa..0f410f48b6 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ljharb/actions/node/install@main name: 'nvm install lts/* && npm install' with: @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ljharb/actions/node/install@main name: 'nvm install lts/* && npm install' with: diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 2b89b601b7..8ed3becf6b 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -15,7 +15,7 @@ jobs: outputs: is-new-version: ${{ steps.cpv.outputs.is-new-version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.tag }} @@ -103,7 +103,7 @@ jobs: prod.api.stepsecurity.io:443 registry.npmjs.org:443 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.tag }} diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index a4d47c74ca..b9e1712fc4 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -4,12 +4,6 @@ on: [pull_request_target] jobs: _: - name: "Automatic Rebase" - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ljharb/actions/.github/workflows/rebase.yml@main + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a110a1f476..0de329d737 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: run: echo "current_version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" shell: bash - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: mindsers/changelog-reader-action@v2 id: changelog_reader diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 5c5d99b338..a2dacffb91 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -10,7 +10,7 @@ jobs: if: ${{ github.repository == 'jsx-eslint/eslint-plugin-react' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ljharb/actions/node/install@main name: 'nvm install lts/* && npm install' with: From 901c794a10a4317f4590eddbb6bd0927481ce8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 5 Mar 2024 22:32:52 +0900 Subject: [PATCH 017/174] [Fix] `boolean-prop-naming`: detect TS interfaces --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 8 ++++++-- tests/lib/rules/boolean-prop-naming.js | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826696a029..cb5e6fbbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`jsx-no-leaked-render`]: prevent wrongly adding parens ([#3700][] @developer-bandi) +* [`boolean-prop-naming`]: detect TS interfaces ([#3701][] @developer-bandi) +[#3701]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3701 [#3700]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3700 ## [7.34.0] - 2024.03.03 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index b5dd37b529..62b687b6a1 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -264,7 +264,7 @@ module.exports = { } function findAllTypeAnnotations(identifier, node) { - if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation') { + if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation' || node.type === 'TSInterfaceBody') { const currentNode = [].concat( objectTypeAnnotations.get(identifier.name) || [], node @@ -363,6 +363,10 @@ module.exports = { findAllTypeAnnotations(node.id, node.typeAnnotation); }, + TSInterfaceDeclaration(node) { + findAllTypeAnnotations(node.id, node.body); + }, + // eslint-disable-next-line object-shorthand 'Program:exit'() { if (!rule) { @@ -386,7 +390,7 @@ module.exports = { [].concat(propType).forEach((prop) => { validatePropNaming( component.node, - prop.properties || prop.members + prop.properties || prop.members || prop.body ); }); } diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 2e2be01e5d..4cd01d6fc6 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -1239,5 +1239,20 @@ ruleTester.run('boolean-prop-naming', rule, { }, ], }, + { + code: ` + interface TestFNType { + enabled: boolean + } + const HelloNew = (props: TestFNType) => { return
}; + `, + options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], + features: ['ts', 'no-babel'], + errors: [ + { + message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + }, + ], + }, ]), }); From 0abebc66fed635698a65201f559c4a7b0ae1208c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Thu, 7 Mar 2024 22:12:23 +0900 Subject: [PATCH 018/174] [Fix] `boolean-prop-naming`: literalType error fix --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 2 +- tests/lib/rules/boolean-prop-naming.js | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5e6fbbe6..e22a6c8efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`jsx-no-leaked-render`]: prevent wrongly adding parens ([#3700][] @developer-bandi) * [`boolean-prop-naming`]: detect TS interfaces ([#3701][] @developer-bandi) +* [`boolean-prop-naming`]: literalType error fix ([#3704][] @developer-bandi) +[#3704]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3704 [#3701]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3701 [#3700]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3700 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 62b687b6a1..cc41537f21 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -380,7 +380,7 @@ module.exports = { let propType; if (annotation.type === 'GenericTypeAnnotation') { propType = objectTypeAnnotations.get(annotation.id.name); - } else if (annotation.type === 'ObjectTypeAnnotation') { + } else if (annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'TSTypeLiteral') { propType = annotation; } else if (annotation.type === 'TSTypeReference') { propType = objectTypeAnnotations.get(annotation.typeName.name); diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 4cd01d6fc6..0ce676f97e 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -1254,5 +1254,15 @@ ruleTester.run('boolean-prop-naming', rule, { }, ], }, + { + code: 'const Hello = (props: {enabled:boolean}) =>
;', + options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], + features: ['ts', 'no-babel'], + errors: [ + { + message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + }, + ], + }, ]), }); From 48291e82a1f87665a96ce84486f793b07f4d44ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Fri, 8 Mar 2024 21:02:12 +0900 Subject: [PATCH 019/174] [Fix] `boolean-prop-naming`: allow TSIntersectionType --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 3 +++ tests/lib/rules/boolean-prop-naming.js | 24 +++++++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e22a6c8efb..937ded5283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-no-leaked-render`]: prevent wrongly adding parens ([#3700][] @developer-bandi) * [`boolean-prop-naming`]: detect TS interfaces ([#3701][] @developer-bandi) * [`boolean-prop-naming`]: literalType error fix ([#3704][] @developer-bandi) +* [`boolean-prop-naming`]: allow TSIntersectionType ([#3705][] @developer-bandi) +[#3705]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3705 [#3704]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3704 [#3701]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3701 [#3700]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3700 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index cc41537f21..2ed70cbfde 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -5,6 +5,7 @@ 'use strict'; +const flatMap = require('array.prototype.flatmap'); const values = require('object.values'); const Components = require('../util/Components'); @@ -384,6 +385,8 @@ module.exports = { propType = annotation; } else if (annotation.type === 'TSTypeReference') { propType = objectTypeAnnotations.get(annotation.typeName.name); + } else if (annotation.type === 'TSIntersectionType') { + propType = flatMap(annotation.types, (type) => objectTypeAnnotations.get(type.typeName.name)); } if (propType) { diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 0ce676f97e..18932bb215 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -1245,7 +1245,7 @@ ruleTester.run('boolean-prop-naming', rule, { enabled: boolean } const HelloNew = (props: TestFNType) => { return
}; - `, + `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['ts', 'no-babel'], errors: [ @@ -1264,5 +1264,27 @@ ruleTester.run('boolean-prop-naming', rule, { }, ], }, + { + code: ` + type Props = { + enabled: boolean + } + type BaseProps = { + semi: boolean + } + + const Hello = (props: Props & BaseProps) =>
; + `, + options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], + features: ['ts', 'no-babel', 'no-ts-old'], + errors: [ + { + message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + }, + { + message: 'Prop name (semi) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + }, + ], + }, ]), }); From a92046325bed8de94e8a9f846ee4f4444c65c1d8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 8 Mar 2024 09:31:58 -0800 Subject: [PATCH 020/174] [Fix] `boolean-prop-naming`: improve error message --- CHANGELOG.md | 3 + lib/rules/boolean-prop-naming.js | 2 +- tests/lib/rules/boolean-prop-naming.js | 84 +++++++++++++++++++++----- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937ded5283..e307437ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: literalType error fix ([#3704][] @developer-bandi) * [`boolean-prop-naming`]: allow TSIntersectionType ([#3705][] @developer-bandi) +### Changed +* [`boolean-prop-naming`]: improve error message (@ljharb) + [#3705]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3705 [#3704]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3704 [#3701]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3701 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 2ed70cbfde..6d63feb17e 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -19,7 +19,7 @@ const report = require('../util/report'); // ------------------------------------------------------------------------------ const messages = { - patternMismatch: 'Prop name ({{propName}}) doesn\'t match rule ({{pattern}})', + patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`', }; module.exports = { diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 18932bb215..393c04a508 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -1106,12 +1106,16 @@ ruleTester.run('boolean-prop-naming', rule, { enabled: boolean } const HelloNew = (props: TestConstType) => { return
}; - `, + `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['ts', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^is[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1121,12 +1125,16 @@ ruleTester.run('boolean-prop-naming', rule, { enabled: boolean } const HelloNew = (props: TestFNType) => { return
}; - `, + `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['ts', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^is[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1142,7 +1150,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^is[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1160,7 +1172,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1176,7 +1192,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^is[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1194,7 +1214,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1212,7 +1236,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1232,10 +1260,18 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['types', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, { - message: 'Prop name (lol) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'lol', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1250,7 +1286,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['ts', 'no-babel'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^is[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1260,7 +1300,11 @@ ruleTester.run('boolean-prop-naming', rule, { features: ['ts', 'no-babel'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, @@ -1272,17 +1316,25 @@ ruleTester.run('boolean-prop-naming', rule, { type BaseProps = { semi: boolean } - + const Hello = (props: Props & BaseProps) =>
; `, options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], features: ['ts', 'no-babel', 'no-ts-old'], errors: [ { - message: 'Prop name (enabled) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, { - message: 'Prop name (semi) doesn\'t match rule (^(is|has)[A-Z]([A-Za-z0-9]?)+)', + messageId: 'patternMismatch', + data: { + propName: 'semi', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, }, ], }, From c3275a4fb2e796799680a584c0203c4734c3682e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 11 Mar 2024 20:00:53 -0700 Subject: [PATCH 021/174] [Fix] `no-unknown-property`: support `popover`, `popovertarget`, `popovertargetaction` attributes Fixes #3707 --- CHANGELOG.md | 2 ++ lib/rules/no-unknown-property.js | 2 ++ tests/lib/rules/no-unknown-property.js | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e307437ab2..b14a5c1bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: detect TS interfaces ([#3701][] @developer-bandi) * [`boolean-prop-naming`]: literalType error fix ([#3704][] @developer-bandi) * [`boolean-prop-naming`]: allow TSIntersectionType ([#3705][] @developer-bandi) +* [`no-unknown-property`]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#3707][] @ljharb) ### Changed * [`boolean-prop-naming`]: improve error message (@ljharb) +[#3707]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3707 [#3705]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3705 [#3704]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3704 [#3701]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3701 diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index ec913e1fca..781e5495bc 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -238,6 +238,8 @@ const DOM_PROPERTY_NAMES_ONE_WORD = [ 'results', 'security', // Video specific 'controls', + // popovers + 'popover', 'popovertarget', 'popovertargetaction', ]; const DOM_PROPERTY_NAMES_TWO_WORDS = [ diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js index 89624d540a..704d8429d5 100644 --- a/tests/lib/rules/no-unknown-property.js +++ b/tests/lib/rules/no-unknown-property.js @@ -169,6 +169,15 @@ ruleTester.run('no-unknown-property', rule, {
`, }, + { + code: ` +
+ + +
Greetings, one and all!
+
+ `, + }, ]), invalid: parsers.all([ { From da1013c6760a997dca3050a4d1d8452f783584f1 Mon Sep 17 00:00:00 2001 From: cuithon Date: Tue, 12 Mar 2024 15:12:52 +0800 Subject: [PATCH 022/174] [Docs] `jsx-no-constructed-context-values`: fix some typos Signed-off-by: cuithon --- docs/rules/jsx-no-constructed-context-values.md | 2 +- lib/rules/display-name.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/jsx-no-constructed-context-values.md b/docs/rules/jsx-no-constructed-context-values.md index aba9ac6254..deb8fb6239 100644 --- a/docs/rules/jsx-no-constructed-context-values.md +++ b/docs/rules/jsx-no-constructed-context-values.md @@ -8,7 +8,7 @@ This rule prevents non-stable values (i.e. object identities) from being used as One way to resolve this issue may be to wrap the value in a `useMemo()`. If it's a function then `useCallback()` can be used as well. -If you _expect_ the context to be rerun on each render, then consider adding a comment/lint supression explaining why. +If you _expect_ the context to be rerun on each render, then consider adding a comment/lint suppression explaining why. ## Examples diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index be0e2985b3..c1b3347117 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -246,7 +246,7 @@ module.exports = { } if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) { - // Skip over React.forwardRef declarations that are embeded within + // Skip over React.forwardRef declarations that are embedded within // a React.memo i.e. React.memo(React.forwardRef(/* ... */)) // This means that we raise a single error for the call to React.memo // instead of one for React.memo and one for React.forwardRef From 69de42e00f4c62e6bd3a180d1931c1981a6eb08f Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 15 Mar 2024 01:58:15 +0100 Subject: [PATCH 023/174] [Fix] `no-unknown-property`: only match `data-*` attributes containing `-` Fixes #3712 --- CHANGELOG.md | 2 ++ lib/rules/no-unknown-property.js | 2 +- tests/lib/rules/no-unknown-property.js | 33 +++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b14a5c1bd7..2a0d7424f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: literalType error fix ([#3704][] @developer-bandi) * [`boolean-prop-naming`]: allow TSIntersectionType ([#3705][] @developer-bandi) * [`no-unknown-property`]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#3707][] @ljharb) +* [`no-unknown-property`]: only match `data-*` attributes containing `-` ([#3713][] @silverwind) ### Changed * [`boolean-prop-naming`]: improve error message (@ljharb) +[#3713]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3713 [#3707]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3707 [#3705]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3705 [#3704]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3704 diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index 781e5495bc..9491f9c658 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -431,7 +431,7 @@ function normalizeAttributeCase(name) { * @returns {boolean} Result */ function isValidDataAttribute(name) { - return !/^data-xml/i.test(name) && /^data(-?[^:]*)$/.test(name); + return !/^data-xml/i.test(name) && /^data-[^:]*$/.test(name); } /** diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js index 704d8429d5..afb95ac2fb 100644 --- a/tests/lib/rules/no-unknown-property.js +++ b/tests/lib/rules/no-unknown-property.js @@ -41,6 +41,10 @@ ruleTester.run('no-unknown-property', rule, { features: ['jsx namespace'], }, { code: ';' }, + { + code: ';', + options: [{ requireDataLowercase: true }], + }, // Some HTML/DOM elements with common attributes should work { code: '
;' }, { code: '
;' }, @@ -603,7 +607,34 @@ ruleTester.run('no-unknown-property', rule, { ], }, { - code: '
;', + code: '
;', + errors: [ + { + messageId: 'dataLowercaseRequired', + data: { + name: 'data-testID', + lowerCaseName: 'data-testid', + }, + }, + { + messageId: 'dataLowercaseRequired', + data: { + name: 'data-under_sCoRe', + lowerCaseName: 'data-under_score', + }, + }, + { + messageId: 'unknownProp', + data: { + name: 'dataNotAnDataAttribute', + lowerCaseName: 'datanotandataattribute', + }, + }, + ], + options: [{ requireDataLowercase: true }], + }, + { + code: ';', errors: [ { messageId: 'dataLowercaseRequired', From e4ecbcfc8f83099a9bd5da18f45b5a6e66ebfb4a Mon Sep 17 00:00:00 2001 From: jaesoekjjang Date: Fri, 15 Mar 2024 23:51:51 +0900 Subject: [PATCH 024/174] [Fix] `checked-requires-onchange-or-readonly`: correct options that were behaving opposite --- CHANGELOG.md | 2 ++ .../checked-requires-onchange-or-readonly.js | 8 +++---- .../checked-requires-onchange-or-readonly.js | 24 ++++++++++++++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0d7424f6..394add2dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: allow TSIntersectionType ([#3705][] @developer-bandi) * [`no-unknown-property`]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#3707][] @ljharb) * [`no-unknown-property`]: only match `data-*` attributes containing `-` ([#3713][] @silverwind) +* [`checked-requires-onchange-or-readonly`]: correct options that were behaving opposite ([#3715][] @jaesoekjjang) ### Changed * [`boolean-prop-naming`]: improve error message (@ljharb) +[#3715]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3715 [#3713]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3713 [#3707]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3707 [#3705]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3705 diff --git a/lib/rules/checked-requires-onchange-or-readonly.js b/lib/rules/checked-requires-onchange-or-readonly.js index 4c1cc15dd1..420611fee5 100644 --- a/lib/rules/checked-requires-onchange-or-readonly.js +++ b/lib/rules/checked-requires-onchange-or-readonly.js @@ -19,8 +19,8 @@ const messages = { const targetPropSet = new Set(['checked', 'onChange', 'readOnly', 'defaultChecked']); const defaultOptions = { - ignoreMissingProperties: true, - ignoreExclusiveCheckedAttribute: true, + ignoreMissingProperties: false, + ignoreExclusiveCheckedAttribute: false, }; /** @@ -93,12 +93,12 @@ module.exports = { return; } - if (options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) { + if (!options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) { reportExclusiveCheckedAttribute(node); } if ( - options.ignoreMissingProperties + !options.ignoreMissingProperties && !(propSet.has('onChange') || propSet.has('readOnly')) ) { reportMissingProperty(node); diff --git a/tests/lib/rules/checked-requires-onchange-or-readonly.js b/tests/lib/rules/checked-requires-onchange-or-readonly.js index 7f9a14cac4..93e08ef60e 100644 --- a/tests/lib/rules/checked-requires-onchange-or-readonly.js +++ b/tests/lib/rules/checked-requires-onchange-or-readonly.js @@ -40,19 +40,23 @@ ruleTester.run('checked-requires-onchange-or-readonly', rule, { "React.createElement('input', { checked: foo, onChange: noop, readOnly: true })", { code: '', - options: [{ ignoreMissingProperties: false }], + options: [{ ignoreMissingProperties: true }], }, { code: '', - options: [{ ignoreMissingProperties: false }], + options: [{ ignoreMissingProperties: true }], }, { code: '', - options: [{ ignoreExclusiveCheckedAttribute: false }], + options: [{ ignoreExclusiveCheckedAttribute: true }], }, { code: '', - options: [{ ignoreExclusiveCheckedAttribute: false }], + options: [{ ignoreExclusiveCheckedAttribute: true }], + }, + { + code: '', + options: [{ ignoreMissingProperties: true, ignoreExclusiveCheckedAttribute: true }], }, '', "React.createElement('span')", @@ -99,13 +103,21 @@ ruleTester.run('checked-requires-onchange-or-readonly', rule, { }, { code: '', - options: [{ ignoreMissingProperties: false }], + options: [{ ignoreMissingProperties: true }], errors: [{ messageId: 'exclusiveCheckedAttribute' }], }, { code: '', - options: [{ ignoreExclusiveCheckedAttribute: false }], + options: [{ ignoreExclusiveCheckedAttribute: true }], errors: [{ messageId: 'missingProperty' }], }, + { + code: '', + options: [{ ignoreMissingProperties: false, ignoreExclusiveCheckedAttribute: false }], + errors: [ + { messageId: 'exclusiveCheckedAttribute' }, + { messageId: 'missingProperty' }, + ], + }, ]), }); From eea45bfcd10e80e0048cb05472a9091d5d52ad89 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 15 Mar 2024 13:20:44 -0700 Subject: [PATCH 025/174] Update CHANGELOG and bump version --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394add2dee..8acd8bc633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## [7.34.1] - 2024.03.15 + ### Fixed * [`jsx-no-leaked-render`]: prevent wrongly adding parens ([#3700][] @developer-bandi) * [`boolean-prop-naming`]: detect TS interfaces ([#3701][] @developer-bandi) @@ -17,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Changed * [`boolean-prop-naming`]: improve error message (@ljharb) +[7.34.1]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.0...v7.34.1 [#3715]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3715 [#3713]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3713 [#3707]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3707 diff --git a/package.json b/package.json index 66d7078685..685e65a222 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-react", - "version": "7.34.0", + "version": "7.34.1", "author": "Yannick Croissant ", "description": "React specific linting rules for ESLint", "main": "index.js", From d50d8865210d16b46a9bb0a531b19cef42eab133 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 9 Apr 2024 14:09:07 +0900 Subject: [PATCH 026/174] [types] add type annotations --- lib/rules/boolean-prop-naming.js | 1 + lib/rules/default-props-match-prop-types.js | 1 + lib/rules/destructuring-assignment.js | 1 + lib/rules/display-name.js | 1 + lib/rules/forbid-component-props.js | 1 + lib/rules/forbid-dom-props.js | 1 + lib/rules/function-component-definition.js | 1 + lib/rules/iframe-missing-sandbox.js | 1 + lib/rules/jsx-boolean-value.js | 1 + lib/rules/jsx-child-element-spacing.js | 1 + lib/rules/jsx-closing-tag-location.js | 1 + lib/rules/jsx-curly-brace-presence.js | 1 + lib/rules/jsx-curly-newline.js | 1 + lib/rules/jsx-filename-extension.js | 1 + lib/rules/jsx-first-prop-new-line.js | 1 + lib/rules/jsx-handler-names.js | 1 + lib/rules/jsx-key.js | 1 + lib/rules/jsx-max-depth.js | 1 + lib/rules/jsx-max-props-per-line.js | 1 + lib/rules/jsx-newline.js | 1 + lib/rules/jsx-no-comment-textnodes.js | 1 + lib/rules/jsx-no-constructed-context-values.js | 1 + lib/rules/jsx-no-duplicate-props.js | 1 + lib/rules/jsx-no-leaked-render.js | 1 + lib/rules/jsx-no-literals.js | 1 + lib/rules/jsx-no-script-url.js | 1 + lib/rules/jsx-no-target-blank.js | 1 + lib/rules/jsx-no-undef.js | 1 + lib/rules/jsx-no-useless-fragment.js | 1 + lib/rules/jsx-one-expression-per-line.js | 1 + lib/rules/jsx-pascal-case.js | 1 + lib/rules/jsx-props-no-multi-spaces.js | 1 + lib/rules/jsx-props-no-spreading.js | 1 + lib/rules/jsx-sort-props.js | 1 + lib/rules/jsx-tag-spacing.js | 1 + lib/rules/jsx-uses-react.js | 1 + lib/rules/jsx-uses-vars.js | 1 + lib/rules/jsx-wrap-multilines.js | 1 + lib/rules/no-array-index-key.js | 1 + lib/rules/no-arrow-function-lifecycle.js | 1 + lib/rules/no-danger.js | 1 + lib/rules/no-did-mount-set-state.js | 1 + lib/rules/no-did-update-set-state.js | 1 + lib/rules/no-direct-mutation-state.js | 1 + lib/rules/no-multi-comp.js | 1 + lib/rules/no-namespace.js | 1 + lib/rules/no-object-type-as-default-prop.js | 1 + lib/rules/no-redundant-should-component-update.js | 1 + lib/rules/no-set-state.js | 1 + lib/rules/no-string-refs.js | 1 + lib/rules/no-this-in-sfc.js | 1 + lib/rules/no-typos.js | 1 + lib/rules/no-unescaped-entities.js | 1 + lib/rules/no-unsafe.js | 1 + lib/rules/no-unstable-nested-components.js | 1 + lib/rules/no-unused-prop-types.js | 1 + lib/rules/no-will-update-set-state.js | 1 + lib/rules/prefer-es6-class.js | 1 + lib/rules/prefer-exact-props.js | 1 + lib/rules/prefer-read-only-props.js | 1 + lib/rules/prefer-stateless-function.js | 1 + lib/rules/prop-types.js | 1 + lib/rules/react-in-jsx-scope.js | 1 + lib/rules/require-default-props.js | 1 + lib/rules/require-optimization.js | 1 + lib/rules/require-render-return.js | 1 + lib/rules/self-closing-comp.js | 1 + lib/rules/sort-comp.js | 1 + lib/rules/sort-prop-types.js | 1 + lib/rules/state-in-constructor.js | 1 + lib/rules/static-property-placement.js | 1 + 71 files changed, 71 insertions(+) diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 6d63feb17e..9570b04054 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -22,6 +22,7 @@ const messages = { patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/default-props-match-prop-types.js b/lib/rules/default-props-match-prop-types.js index ba3db9328d..95b341650a 100644 --- a/lib/rules/default-props-match-prop-types.js +++ b/lib/rules/default-props-match-prop-types.js @@ -21,6 +21,7 @@ const messages = { defaultHasNoType: 'defaultProp "{{name}}" has no corresponding propTypes declaration.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/destructuring-assignment.js b/lib/rules/destructuring-assignment.js index e4abdaede9..e1a98d30a5 100644 --- a/lib/rules/destructuring-assignment.js +++ b/lib/rules/destructuring-assignment.js @@ -53,6 +53,7 @@ const messages = { destructureInSignature: 'Must destructure props in the function signature.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index c1b3347117..b4de069542 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -27,6 +27,7 @@ const messages = { noContextDisplayName: 'Context definition is missing display name', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/forbid-component-props.js b/lib/rules/forbid-component-props.js index cca553e123..a72a5afafb 100644 --- a/lib/rules/forbid-component-props.js +++ b/lib/rules/forbid-component-props.js @@ -22,6 +22,7 @@ const messages = { propIsForbidden: 'Prop "{{prop}}" is forbidden on Components', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/forbid-dom-props.js b/lib/rules/forbid-dom-props.js index d5131af98d..4638a8700d 100644 --- a/lib/rules/forbid-dom-props.js +++ b/lib/rules/forbid-dom-props.js @@ -37,6 +37,7 @@ const messages = { propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/function-component-definition.js b/lib/rules/function-component-definition.js index f8a95a9da0..43e3154873 100644 --- a/lib/rules/function-component-definition.js +++ b/lib/rules/function-component-definition.js @@ -113,6 +113,7 @@ const messages = { 'arrow-function': 'Function component is not an arrow function', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/iframe-missing-sandbox.js b/lib/rules/iframe-missing-sandbox.js index 9a8bd4774d..810e30b52b 100644 --- a/lib/rules/iframe-missing-sandbox.js +++ b/lib/rules/iframe-missing-sandbox.js @@ -109,6 +109,7 @@ function checkProps(context, node) { } } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js index 0090c0a174..1cff171147 100644 --- a/lib/rules/jsx-boolean-value.js +++ b/lib/rules/jsx-boolean-value.js @@ -54,6 +54,7 @@ const messages = { omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-child-element-spacing.js b/lib/rules/jsx-child-element-spacing.js index d8d2af67ab..06807efcc6 100644 --- a/lib/rules/jsx-child-element-spacing.js +++ b/lib/rules/jsx-child-element-spacing.js @@ -44,6 +44,7 @@ const messages = { spacingBeforeNext: 'Ambiguous spacing before next element {{element}}', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-closing-tag-location.js b/lib/rules/jsx-closing-tag-location.js index ef68285513..5d5a95cb7b 100644 --- a/lib/rules/jsx-closing-tag-location.js +++ b/lib/rules/jsx-closing-tag-location.js @@ -18,6 +18,7 @@ const messages = { matchIndent: 'Expected closing tag to match indentation of opening.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index c5467cc415..3eac3b47ea 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -36,6 +36,7 @@ const messages = { missingCurly: 'Need to wrap this literal in a JSX expression.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-curly-newline.js b/lib/rules/jsx-curly-newline.js index 77c2c82d8d..068a7103da 100644 --- a/lib/rules/jsx-curly-newline.js +++ b/lib/rules/jsx-curly-newline.js @@ -41,6 +41,7 @@ const messages = { unexpectedAfter: 'Unexpected newline after \'{\'.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'layout', diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js index 578ffaf309..4a2cfeede6 100644 --- a/lib/rules/jsx-filename-extension.js +++ b/lib/rules/jsx-filename-extension.js @@ -28,6 +28,7 @@ const messages = { extensionOnlyForJSX: 'Only files containing JSX may use the extension \'{{ext}}\'', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-first-prop-new-line.js b/lib/rules/jsx-first-prop-new-line.js index 026ab384a0..7c9d969b51 100644 --- a/lib/rules/jsx-first-prop-new-line.js +++ b/lib/rules/jsx-first-prop-new-line.js @@ -17,6 +17,7 @@ const messages = { propOnSameLine: 'Property should be placed on the same line as the component declaration', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-handler-names.js b/lib/rules/jsx-handler-names.js index e82db76474..8ff56fb47a 100644 --- a/lib/rules/jsx-handler-names.js +++ b/lib/rules/jsx-handler-names.js @@ -17,6 +17,7 @@ const messages = { badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}}\'', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index c13a00cf83..7ea874d0ae 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -32,6 +32,7 @@ const messages = { nonUniqueKeys: '`key` prop must be unique', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-max-depth.js b/lib/rules/jsx-max-depth.js index 2ea6653351..01698264c7 100644 --- a/lib/rules/jsx-max-depth.js +++ b/lib/rules/jsx-max-depth.js @@ -20,6 +20,7 @@ const messages = { wrongDepth: 'Expected the depth of nested jsx elements to be <= {{needed}}, but found {{found}}.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js index 89e8748521..95d7942f9a 100644 --- a/lib/rules/jsx-max-props-per-line.js +++ b/lib/rules/jsx-max-props-per-line.js @@ -23,6 +23,7 @@ const messages = { newLine: 'Prop `{{prop}}` must be placed on a new line', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-newline.js b/lib/rules/jsx-newline.js index b4408e7cd8..966bf6a34b 100644 --- a/lib/rules/jsx-newline.js +++ b/lib/rules/jsx-newline.js @@ -23,6 +23,7 @@ function isMultilined(node) { return node && node.loc.start.line !== node.loc.end.line; } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-comment-textnodes.js b/lib/rules/jsx-no-comment-textnodes.js index b33cea0cce..2a90467d3f 100644 --- a/lib/rules/jsx-no-comment-textnodes.js +++ b/lib/rules/jsx-no-comment-textnodes.js @@ -33,6 +33,7 @@ function checkText(node, context) { } } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-constructed-context-values.js b/lib/rules/jsx-no-constructed-context-values.js index 8bcf045dad..f28c51fd4a 100644 --- a/lib/rules/jsx-no-constructed-context-values.js +++ b/lib/rules/jsx-no-constructed-context-values.js @@ -129,6 +129,7 @@ const messages = { defaultMsgFunc: 'The {{type}} passed as the value prop to the Context provider (at line {{nodeLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-duplicate-props.js b/lib/rules/jsx-no-duplicate-props.js index 41e17828e2..cf7737698a 100644 --- a/lib/rules/jsx-no-duplicate-props.js +++ b/lib/rules/jsx-no-duplicate-props.js @@ -17,6 +17,7 @@ const messages = { noDuplicateProps: 'No duplicate props allowed', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index b77cd576cc..a6bf54b72a 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -111,6 +111,7 @@ function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNod /** * @type {import('eslint').Rule.RuleModule} */ +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-literals.js b/lib/rules/jsx-no-literals.js index 41e6d0b828..ca4f3a8d46 100644 --- a/lib/rules/jsx-no-literals.js +++ b/lib/rules/jsx-no-literals.js @@ -27,6 +27,7 @@ const messages = { literalNotInJSXExpression: 'Missing JSX expression container around literal string: "{{text}}"', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-script-url.js b/lib/rules/jsx-no-script-url.js index bcf9468a24..b18ce2ccb3 100644 --- a/lib/rules/jsx-no-script-url.js +++ b/lib/rules/jsx-no-script-url.js @@ -43,6 +43,7 @@ const messages = { noScriptURL: 'A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-target-blank.js b/lib/rules/jsx-no-target-blank.js index 795de8a70f..4ad12a464f 100644 --- a/lib/rules/jsx-no-target-blank.js +++ b/lib/rules/jsx-no-target-blank.js @@ -126,6 +126,7 @@ const messages = { noTargetBlankWithoutNoopener: 'Using target="_blank" without rel="noreferrer" or rel="noopener" (the former implies the latter and is preferred due to wider support) is a security risk: see https://mathiasbynens.github.io/rel-noopener/#recommendations', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { fixable: 'code', diff --git a/lib/rules/jsx-no-undef.js b/lib/rules/jsx-no-undef.js index d7a42ccff2..3a1f4dbc5f 100644 --- a/lib/rules/jsx-no-undef.js +++ b/lib/rules/jsx-no-undef.js @@ -17,6 +17,7 @@ const messages = { undefined: '\'{{identifier}}\' is not defined.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-no-useless-fragment.js b/lib/rules/jsx-no-useless-fragment.js index 2d2711a5f9..303b9719e5 100644 --- a/lib/rules/jsx-no-useless-fragment.js +++ b/lib/rules/jsx-no-useless-fragment.js @@ -83,6 +83,7 @@ const messages = { ChildOfHtmlElement: 'Passing a fragment to an HTML element is useless.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'suggestion', diff --git a/lib/rules/jsx-one-expression-per-line.js b/lib/rules/jsx-one-expression-per-line.js index 4c5a9c970f..f18fa919e1 100644 --- a/lib/rules/jsx-one-expression-per-line.js +++ b/lib/rules/jsx-one-expression-per-line.js @@ -21,6 +21,7 @@ const messages = { moveToNewLine: '`{{descriptor}}` must be placed on a new line', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js index a1bb48116a..efeef4032a 100644 --- a/lib/rules/jsx-pascal-case.js +++ b/lib/rules/jsx-pascal-case.js @@ -76,6 +76,7 @@ const messages = { usePascalOrSnakeCase: 'Imported JSX component {{name}} must be in PascalCase or SCREAMING_SNAKE_CASE', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-props-no-multi-spaces.js b/lib/rules/jsx-props-no-multi-spaces.js index b9cabafe32..edd122c413 100644 --- a/lib/rules/jsx-props-no-multi-spaces.js +++ b/lib/rules/jsx-props-no-multi-spaces.js @@ -17,6 +17,7 @@ const messages = { onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-props-no-spreading.js b/lib/rules/jsx-props-no-spreading.js index acbdea797a..a608011cd1 100644 --- a/lib/rules/jsx-props-no-spreading.js +++ b/lib/rules/jsx-props-no-spreading.js @@ -38,6 +38,7 @@ const messages = { noSpreading: 'Prop spreading is forbidden', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js index 6d19f201cd..3ca1724ebe 100644 --- a/lib/rules/jsx-sort-props.js +++ b/lib/rules/jsx-sort-props.js @@ -338,6 +338,7 @@ function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedLi }); } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-tag-spacing.js b/lib/rules/jsx-tag-spacing.js index e7c632b479..9cc18e8c37 100644 --- a/lib/rules/jsx-tag-spacing.js +++ b/lib/rules/jsx-tag-spacing.js @@ -255,6 +255,7 @@ const optionDefaults = { beforeClosing: 'allow', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/jsx-uses-react.js b/lib/rules/jsx-uses-react.js index 2bc2963d9e..bf06f508f8 100644 --- a/lib/rules/jsx-uses-react.js +++ b/lib/rules/jsx-uses-react.js @@ -12,6 +12,7 @@ const docsUrl = require('../util/docsUrl'); // Rule Definition // ------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { // eslint-disable-next-line eslint-plugin/prefer-message-ids -- https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/292 meta: { diff --git a/lib/rules/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js index a178d50565..9ea9ab001e 100644 --- a/lib/rules/jsx-uses-vars.js +++ b/lib/rules/jsx-uses-vars.js @@ -14,6 +14,7 @@ const docsUrl = require('../util/docsUrl'); const isTagNameRe = /^[a-z]/; const isTagName = (name) => isTagNameRe.test(name); +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { // eslint-disable-next-line eslint-plugin/prefer-message-ids -- https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/292 meta: { diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index eb9359e773..59fa5f294b 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -35,6 +35,7 @@ const messages = { parensOnNewLines: 'Parentheses around JSX should be on separate lines', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-array-index-key.js b/lib/rules/no-array-index-key.js index 08ecd7a6a9..bc8c91f9ef 100644 --- a/lib/rules/no-array-index-key.js +++ b/lib/rules/no-array-index-key.js @@ -41,6 +41,7 @@ const messages = { noArrayIndex: 'Do not use Array index in keys', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-arrow-function-lifecycle.js b/lib/rules/no-arrow-function-lifecycle.js index d9be92540f..a1de789b81 100644 --- a/lib/rules/no-arrow-function-lifecycle.js +++ b/lib/rules/no-arrow-function-lifecycle.js @@ -32,6 +32,7 @@ const messages = { lifecycle: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-danger.js b/lib/rules/no-danger.js index a7cf8b59f8..1cb0273739 100644 --- a/lib/rules/no-danger.js +++ b/lib/rules/no-danger.js @@ -43,6 +43,7 @@ const messages = { dangerousProp: 'Dangerous property \'{{name}}\' found', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-did-mount-set-state.js b/lib/rules/no-did-mount-set-state.js index a5705ef2df..ad70c3eb7b 100644 --- a/lib/rules/no-did-mount-set-state.js +++ b/lib/rules/no-did-mount-set-state.js @@ -7,4 +7,5 @@ const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); +/** @type {import('eslint').Rule.RuleModule} */ module.exports = makeNoMethodSetStateRule('componentDidMount'); diff --git a/lib/rules/no-did-update-set-state.js b/lib/rules/no-did-update-set-state.js index 5c76a1ff5b..d297fb2648 100644 --- a/lib/rules/no-did-update-set-state.js +++ b/lib/rules/no-did-update-set-state.js @@ -7,4 +7,5 @@ const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); +/** @type {import('eslint').Rule.RuleModule} */ module.exports = makeNoMethodSetStateRule('componentDidUpdate'); diff --git a/lib/rules/no-direct-mutation-state.js b/lib/rules/no-direct-mutation-state.js index 08d77e9bca..9349a85fe5 100644 --- a/lib/rules/no-direct-mutation-state.js +++ b/lib/rules/no-direct-mutation-state.js @@ -21,6 +21,7 @@ const messages = { noDirectMutation: 'Do not mutate state directly. Use setState().', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index 31b9ef0d4e..76b3fa0af7 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -19,6 +19,7 @@ const messages = { onlyOneComponent: 'Declare only one React component per file', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js index 64bbc8d521..d7559f5ebd 100644 --- a/lib/rules/no-namespace.js +++ b/lib/rules/no-namespace.js @@ -18,6 +18,7 @@ const messages = { noNamespace: 'React component {{name}} must not be in a namespace, as React does not support them', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-object-type-as-default-prop.js b/lib/rules/no-object-type-as-default-prop.js index 2ba25889d8..7be98cb4b3 100644 --- a/lib/rules/no-object-type-as-default-prop.js +++ b/lib/rules/no-object-type-as-default-prop.js @@ -78,6 +78,7 @@ function verifyDefaultPropsDestructuring(context, properties) { }); } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-redundant-should-component-update.js b/lib/rules/no-redundant-should-component-update.js index bf7601a2c4..849191d176 100644 --- a/lib/rules/no-redundant-should-component-update.js +++ b/lib/rules/no-redundant-should-component-update.js @@ -17,6 +17,7 @@ const messages = { noShouldCompUpdate: '{{component}} does not need shouldComponentUpdate when extending React.PureComponent.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-set-state.js b/lib/rules/no-set-state.js index 486eb211ff..c88db30d7e 100644 --- a/lib/rules/no-set-state.js +++ b/lib/rules/no-set-state.js @@ -19,6 +19,7 @@ const messages = { noSetState: 'Do not use setState', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-string-refs.js b/lib/rules/no-string-refs.js index 2decbbe6bb..466a214579 100644 --- a/lib/rules/no-string-refs.js +++ b/lib/rules/no-string-refs.js @@ -18,6 +18,7 @@ const messages = { stringInRefDeprecated: 'Using string literals in ref attributes is deprecated.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-this-in-sfc.js b/lib/rules/no-this-in-sfc.js index 9c1535ef0b..cf9eb99bd4 100644 --- a/lib/rules/no-this-in-sfc.js +++ b/lib/rules/no-this-in-sfc.js @@ -16,6 +16,7 @@ const messages = { noThisInSFC: 'Stateless functional components should not use `this`', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index ba826d523d..3d79d40b18 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -28,6 +28,7 @@ const messages = { noReactBinding: '`\'react\'` imported without a local `React` binding.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js index 3eaa7cd757..a449ad5206 100644 --- a/lib/rules/no-unescaped-entities.js +++ b/lib/rules/no-unescaped-entities.js @@ -35,6 +35,7 @@ const messages = { unescapedEntityAlts: '`{{entity}}` can be escaped with {{alts}}.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-unsafe.js b/lib/rules/no-unsafe.js index 3cfa48968e..66d828338a 100644 --- a/lib/rules/no-unsafe.js +++ b/lib/rules/no-unsafe.js @@ -19,6 +19,7 @@ const messages = { unsafeMethod: '{{method}} is unsafe for use in async rendering. Update the component to use {{newMethod}} instead. {{details}}', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-unstable-nested-components.js b/lib/rules/no-unstable-nested-components.js index 9b60e496bf..2ef635efab 100644 --- a/lib/rules/no-unstable-nested-components.js +++ b/lib/rules/no-unstable-nested-components.js @@ -265,6 +265,7 @@ function resolveComponentName(node) { // Rule Definition // ------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index a3cf352aac..f72188201a 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -22,6 +22,7 @@ const messages = { unusedPropType: '\'{{name}}\' PropType is defined but prop is never used', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/no-will-update-set-state.js b/lib/rules/no-will-update-set-state.js index 4f84ee6724..16e6be43b9 100644 --- a/lib/rules/no-will-update-set-state.js +++ b/lib/rules/no-will-update-set-state.js @@ -8,6 +8,7 @@ const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); const testReactVersion = require('../util/version').testReactVersion; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = makeNoMethodSetStateRule( 'componentWillUpdate', (context) => testReactVersion(context, '>= 16.3.0') diff --git a/lib/rules/prefer-es6-class.js b/lib/rules/prefer-es6-class.js index 5ca020ebba..a7c1ff56fc 100644 --- a/lib/rules/prefer-es6-class.js +++ b/lib/rules/prefer-es6-class.js @@ -18,6 +18,7 @@ const messages = { shouldUseCreateClass: 'Component should use createClass instead of es6 class', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/prefer-exact-props.js b/lib/rules/prefer-exact-props.js index 037fa29914..4ad088e9ee 100644 --- a/lib/rules/prefer-exact-props.js +++ b/lib/rules/prefer-exact-props.js @@ -20,6 +20,7 @@ const messages = { flow: 'Component flow props should be set with exact objects.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/prefer-read-only-props.js b/lib/rules/prefer-read-only-props.js index 31d7114643..c00b09f9e4 100644 --- a/lib/rules/prefer-read-only-props.js +++ b/lib/rules/prefer-read-only-props.js @@ -47,6 +47,7 @@ const messages = { readOnlyProp: 'Prop \'{{name}}\' should be read-only.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index 2743ea38aa..07c435747f 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -24,6 +24,7 @@ const messages = { componentShouldBePure: 'Component should be written as a pure function', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index 9ab3640938..07adca104a 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -22,6 +22,7 @@ const messages = { missingPropType: '\'{{name}}\' is missing in props validation', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/react-in-jsx-scope.js b/lib/rules/react-in-jsx-scope.js index e1ee1b6d8c..1af398deae 100644 --- a/lib/rules/react-in-jsx-scope.js +++ b/lib/rules/react-in-jsx-scope.js @@ -18,6 +18,7 @@ const messages = { notInScope: '\'{{name}}\' must be in scope when using JSX', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/require-default-props.js b/lib/rules/require-default-props.js index c1b83bcff0..d1b27eefe1 100644 --- a/lib/rules/require-default-props.js +++ b/lib/rules/require-default-props.js @@ -24,6 +24,7 @@ const messages = { destructureInSignature: 'Must destructure props in the function signature to initialize an optional prop.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/require-optimization.js b/lib/rules/require-optimization.js index 05bd071fc6..9d440b19ee 100644 --- a/lib/rules/require-optimization.js +++ b/lib/rules/require-optimization.js @@ -16,6 +16,7 @@ const messages = { noShouldComponentUpdate: 'Component is not optimized. Please add a shouldComponentUpdate method.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js index cb06206952..d79c9406b7 100644 --- a/lib/rules/require-render-return.js +++ b/lib/rules/require-render-return.js @@ -21,6 +21,7 @@ const messages = { noRenderReturn: 'Your render method should have a return statement', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/self-closing-comp.js b/lib/rules/self-closing-comp.js index 2144db599e..fe5fda4d49 100644 --- a/lib/rules/self-closing-comp.js +++ b/lib/rules/self-closing-comp.js @@ -19,6 +19,7 @@ const messages = { notSelfClosing: 'Empty components are self-closing', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/sort-comp.js b/lib/rules/sort-comp.js index b56187a298..fb0bf8f614 100644 --- a/lib/rules/sort-comp.js +++ b/lib/rules/sort-comp.js @@ -86,6 +86,7 @@ const messages = { unsortedProps: '{{propA}} should be placed {{position}} {{propB}}', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 4a79aa5407..8c3574af4a 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -53,6 +53,7 @@ function toLowerCase(item) { return String(item).toLowerCase(); } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/state-in-constructor.js b/lib/rules/state-in-constructor.js index e6cc87b283..c518b1713a 100644 --- a/lib/rules/state-in-constructor.js +++ b/lib/rules/state-in-constructor.js @@ -19,6 +19,7 @@ const messages = { stateInitClassProp: 'State initialization should be in a class property', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { diff --git a/lib/rules/static-property-placement.js b/lib/rules/static-property-placement.js index 59f559121b..efc50da3bd 100644 --- a/lib/rules/static-property-placement.js +++ b/lib/rules/static-property-placement.js @@ -55,6 +55,7 @@ const messages = { declareOutsideClass: '\'{{name}}\' should be declared outside the class body.', }; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { From 4467db503e38b9356517cf6926d11be544ccf4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sat, 16 Mar 2024 12:54:58 +0900 Subject: [PATCH 027/174] [Fix] `boolean-prop-naming`: avoid a crash with a non-TSTypeReference type --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 6 ++++- tests/lib/rules/boolean-prop-naming.js | 33 ++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394add2dee..03815418ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-unknown-property`]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#3707][] @ljharb) * [`no-unknown-property`]: only match `data-*` attributes containing `-` ([#3713][] @silverwind) * [`checked-requires-onchange-or-readonly`]: correct options that were behaving opposite ([#3715][] @jaesoekjjang) +* [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi) ### Changed * [`boolean-prop-naming`]: improve error message (@ljharb) +[#3718]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3718 [#3715]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3715 [#3713]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3713 [#3707]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3707 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 9570b04054..173b154354 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -387,7 +387,11 @@ module.exports = { } else if (annotation.type === 'TSTypeReference') { propType = objectTypeAnnotations.get(annotation.typeName.name); } else if (annotation.type === 'TSIntersectionType') { - propType = flatMap(annotation.types, (type) => objectTypeAnnotations.get(type.typeName.name)); + propType = flatMap(annotation.types, (type) => ( + type.type === 'TSTypeReference' + ? objectTypeAnnotations.get(type.typeName.name) + : type + )); } if (propType) { diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 393c04a508..2dec7ed70c 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -1312,10 +1312,10 @@ ruleTester.run('boolean-prop-naming', rule, { code: ` type Props = { enabled: boolean - } + }; type BaseProps = { semi: boolean - } + }; const Hello = (props: Props & BaseProps) =>
; `, @@ -1338,5 +1338,34 @@ ruleTester.run('boolean-prop-naming', rule, { }, ], }, + { + code: ` + type Props = { + enabled: boolean + }; + + const Hello = (props: Props & { + semi: boolean + }) =>
; + `, + options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], + features: ['ts', 'no-babel', 'no-ts-old'], + errors: [ + { + messageId: 'patternMismatch', + data: { + propName: 'enabled', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, + }, + { + messageId: 'patternMismatch', + data: { + propName: 'semi', + pattern: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + }, + }, + ], + }, ]), }); From d97e3ed96afe77a56fdc6fc7bdec11c28bc256e2 Mon Sep 17 00:00:00 2001 From: Akul Srivastava Date: Fri, 3 May 2024 12:32:46 +0530 Subject: [PATCH 028/174] [Fix] `jsx-no-leaked-render`: invalid report if left side is boolean --- CHANGELOG.md | 2 ++ lib/rules/jsx-no-leaked-render.js | 12 ++++++++ tests/lib/rules/jsx-no-leaked-render.js | 40 ++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a358c59def..a6971a072f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi) +* [`jsx-no-leaked-render`]: invalid report if left side is boolean ([#3746][] @akulsr0) +[#3746]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3746 [#3718]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3718 ## [7.34.1] - 2024.03.15 diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index a6bf54b72a..e20524c8d2 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -10,6 +10,7 @@ const from = require('es-iterator-helpers/Iterator.from'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const variableUtil = require('../util/variable'); const testReactVersion = require('../util/version').testReactVersion; const isParenthesized = require('../util/ast').isParenthesized; @@ -160,6 +161,17 @@ module.exports = { if (isCoerceValidLeftSide || getIsCoerceValidNestedLogicalExpression(leftSide)) { return; } + const variables = variableUtil.variablesInScope(context); + const leftSideVar = variableUtil.getVariable(variables, leftSide.name); + if (leftSideVar) { + const leftSideValue = leftSideVar.defs + && leftSideVar.defs.length + && leftSideVar.defs[0].node.init + && leftSideVar.defs[0].node.init.value; + if (typeof leftSideValue === 'boolean') { + return; + } + } } if (testReactVersion(context, '>= 18') && leftSide.type === 'Literal' && leftSide.value === '') { diff --git a/tests/lib/rules/jsx-no-leaked-render.js b/tests/lib/rules/jsx-no-leaked-render.js index f5729bf059..a6742a29cd 100644 --- a/tests/lib/rules/jsx-no-leaked-render.js +++ b/tests/lib/rules/jsx-no-leaked-render.js @@ -195,6 +195,24 @@ ruleTester.run('jsx-no-leaked-render', rule, { `, options: [{ validStrategies: ['coerce'] }], }, + { + code: ` + const isOpen = true; + const Component = () => { + return 0} /> + } + `, + options: [{ validStrategies: ['coerce'] }], + }, + { + code: ` + const isOpen = false; + const Component = () => { + return 0} /> + } + `, + options: [{ validStrategies: ['coerce'] }], + }, ]) || [], invalid: parsers.all([].concat( @@ -972,6 +990,26 @@ ruleTester.run('jsx-no-leaked-render', rule, { line: 5, column: 16, }], - } : [] + } : [], + { + code: ` + const isOpen = 0; + const Component = () => { + return 0} /> + } + `, + output: ` + const isOpen = 0; + const Component = () => { + return 0} /> + } + `, + options: [{ validStrategies: ['coerce'] }], + errors: [{ + message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes', + line: 4, + column: 33, + }], + } )), }); From 26765f93e8be24f1e8fc6edcece70ad3f2eeaa02 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 029/174] [Refactor] create `getSourceCode` helper, since `context.getSourceCode` 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/boolean-prop-naming.js | 7 ++++--- lib/rules/destructuring-assignment.js | 5 ++++- lib/rules/forbid-elements.js | 5 +++-- lib/rules/forbid-prop-types.js | 3 ++- lib/rules/function-component-definition.js | 3 ++- lib/rules/hook-use-state.js | 7 ++++--- lib/rules/jsx-closing-bracket-location.js | 7 ++++--- lib/rules/jsx-curly-brace-presence.js | 9 +++++---- lib/rules/jsx-curly-newline.js | 3 ++- lib/rules/jsx-curly-spacing.js | 13 +++++++------ lib/rules/jsx-equals-spacing.js | 3 ++- lib/rules/jsx-fragments.js | 5 +++-- lib/rules/jsx-handler-names.js | 3 ++- lib/rules/jsx-indent-props.js | 3 ++- lib/rules/jsx-indent.js | 11 ++++++----- lib/rules/jsx-key.js | 3 ++- lib/rules/jsx-max-props-per-line.js | 5 +++-- lib/rules/jsx-newline.js | 3 ++- lib/rules/jsx-no-comment-textnodes.js | 3 ++- lib/rules/jsx-no-leaked-render.js | 3 ++- lib/rules/jsx-no-literals.js | 3 ++- lib/rules/jsx-no-undef.js | 3 ++- lib/rules/jsx-no-useless-fragment.js | 3 ++- lib/rules/jsx-one-expression-per-line.js | 13 +++++++++---- lib/rules/jsx-props-no-multi-spaces.js | 9 +++++---- lib/rules/jsx-sort-default-props.js | 5 +++-- lib/rules/jsx-sort-props.js | 5 +++-- lib/rules/jsx-space-before-closing.js | 3 ++- lib/rules/jsx-tag-spacing.js | 9 +++++---- lib/rules/jsx-wrap-multilines.js | 7 ++++--- lib/rules/no-arrow-function-lifecycle.js | 3 ++- lib/rules/no-deprecated.js | 3 ++- lib/rules/no-unescaped-entities.js | 5 +++-- lib/rules/no-unknown-property.js | 3 ++- lib/rules/prefer-exact-props.js | 3 ++- lib/rules/prefer-stateless-function.js | 5 ++++- lib/rules/prop-types.js | 6 +++--- lib/rules/sort-default-props.js | 5 +++-- lib/rules/sort-prop-types.js | 5 +++-- lib/util/Components.js | 5 ++++- lib/util/ast.js | 9 ++++++--- lib/util/componentUtil.js | 7 +++++-- lib/util/defaultProps.js | 3 ++- lib/util/eslint.js | 9 +++++++++ lib/util/pragma.js | 4 +++- lib/util/propTypes.js | 7 +++++-- lib/util/propTypesSort.js | 5 +++-- lib/util/usedPropTypes.js | 5 ++++- tests/util/jsx.js | 1 + 49 files changed, 166 insertions(+), 91 deletions(-) create mode 100644 lib/util/eslint.js diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 173b154354..78ed249805 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -13,6 +13,7 @@ const propsUtil = require('../util/props'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -115,7 +116,7 @@ module.exports = { // we can't get the name of the Flow object key name. So we have // to hack around it for now. if (node.type === 'ObjectTypeProperty') { - return context.getSourceCode().getFirstToken(node).value; + return getSourceCode(context).getFirstToken(node).value; } return node.key.name; @@ -308,7 +309,7 @@ module.exports = { && node.value.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction( context, - context.getSourceCode().getText(node.value.callee) + getSourceCode(context).getText(node.value.callee) ) ) { checkPropWrapperArguments(node, node.value.arguments); @@ -334,7 +335,7 @@ module.exports = { right.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction( context, - context.getSourceCode().getText(right.callee) + getSourceCode(context).getText(right.callee) ) ) { checkPropWrapperArguments(component.node, right.arguments); diff --git a/lib/rules/destructuring-assignment.js b/lib/rules/destructuring-assignment.js index e1a98d30a5..40f3a0ac1f 100644 --- a/lib/rules/destructuring-assignment.js +++ b/lib/rules/destructuring-assignment.js @@ -6,9 +6,12 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); +const eslintUtil = require('../util/eslint'); const isAssignmentLHS = require('../util/ast').isAssignmentLHS; const report = require('../util/report'); +const getSourceCode = eslintUtil.getSourceCode; + const DEFAULT_OPTION = 'always'; function createSFCParams() { @@ -269,7 +272,7 @@ module.exports = { param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1], ]; return [ - fixer.replaceTextRange(replaceRange, context.getSourceCode().getText(node.id)), + fixer.replaceTextRange(replaceRange, getSourceCode(context).getText(node.id)), fixer.remove(node.parent), ]; }, diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index c7f978a164..6f2979a1a0 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -7,6 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const isCreateElement = require('../util/isCreateElement'); const report = require('../util/report'); @@ -90,7 +91,7 @@ module.exports = { return { JSXOpeningElement(node) { - reportIfForbidden(context.getSourceCode().getText(node.name), node.name); + reportIfForbidden(getSourceCode(context).getText(node.name), node.name); }, CallExpression(node) { @@ -110,7 +111,7 @@ module.exports = { } else if (argType === 'Literal' && /^[a-z][^.]*$/.test(argument.value)) { reportIfForbidden(argument.value, argument); } else if (argType === 'MemberExpression') { - reportIfForbidden(context.getSourceCode().getText(argument), argument); + reportIfForbidden(getSourceCode(context).getText(argument), argument); } }, }; diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js index 92f0f0f51f..8161d6b45d 100644 --- a/lib/rules/forbid-prop-types.js +++ b/lib/rules/forbid-prop-types.js @@ -10,6 +10,7 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Constants @@ -171,7 +172,7 @@ module.exports = { case 'CallExpression': { const innerNode = node.arguments && node.arguments[0]; if ( - propWrapperUtil.isPropWrapperFunction(context, context.getSourceCode().getText(node.callee)) + propWrapperUtil.isPropWrapperFunction(context, getSourceCode(context).getText(node.callee)) && innerNode ) { checkNode(innerNode); diff --git a/lib/rules/function-component-definition.js b/lib/rules/function-component-definition.js index 43e3154873..9f2c3b6fb2 100644 --- a/lib/rules/function-component-definition.js +++ b/lib/rules/function-component-definition.js @@ -9,6 +9,7 @@ const arrayIncludes = require('array-includes'); const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const reportC = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -181,7 +182,7 @@ module.exports = { ); function getFixer(node, options) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const source = sourceCode.getText(); const typeAnnotation = getTypeAnnotation(node, source); diff --git a/lib/rules/hook-use-state.js b/lib/rules/hook-use-state.js index a9deed4fd6..a0c46e338e 100644 --- a/lib/rules/hook-use-state.js +++ b/lib/rules/hook-use-state.js @@ -9,6 +9,7 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const getMessageData = require('../util/message'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -160,14 +161,14 @@ module.exports = { fix: (fixer) => [ // Add useMemo import, if necessary useStateReactImportSpecifier - && (!useMemoReactImportSpecifier || defaultReactImportName) - && fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'), + && (!useMemoReactImportSpecifier || defaultReactImportName) + && fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'), // Convert single-value destructure to simple assignment fixer.replaceTextRange(node.parent.id.range, valueVariableName), // Convert useState call to useMemo + arrow function + dependency array fixer.replaceTextRange( node.range, - `${useMemoCode}(() => ${context.getSourceCode().getText(node.arguments[0])}, [])` + `${useMemoCode}(() => ${getSourceCode(context).getText(node.arguments[0])}, [])` ), ].filter(Boolean), } diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index 4816fd0068..9b79223088 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -7,6 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -170,11 +171,11 @@ module.exports = { let spaces = []; switch (expectedLocation) { case 'props-aligned': - indentation = /^\s*/.exec(context.getSourceCode().lines[tokens.lastProp.firstLine - 1])[0]; + indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.lastProp.firstLine - 1])[0]; break; case 'tag-aligned': case 'line-aligned': - indentation = /^\s*/.exec(context.getSourceCode().lines[tokens.opening.line - 1])[0]; + indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.opening.line - 1])[0]; break; default: indentation = ''; @@ -194,7 +195,7 @@ module.exports = { * prop and start of opening line. */ function getTokensLocations(node) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const opening = sourceCode.getFirstToken(node).loc.start; const closing = sourceCode.getLastTokens(node, node.selfClosing ? 2 : 1)[0].loc.start; const tag = sourceCode.getFirstToken(node.name).loc.start; diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 3eac3b47ea..64be524499 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -11,6 +11,7 @@ const arrayIncludes = require('array-includes'); const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Constants @@ -176,7 +177,7 @@ module.exports = { let textToReplace; if (jsxUtil.isJSX(expression)) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); textToReplace = sourceCode.getText(expression); } else { const expressionType = expression && expression.type; @@ -188,7 +189,7 @@ module.exports = { : expression.raw.slice(1, -1) }"`; } else if (jsxUtil.isJSX(expression)) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); textToReplace = sourceCode.getText(expression); } else { @@ -207,7 +208,7 @@ module.exports = { node: literalNode, fix(fixer) { if (jsxUtil.isJSX(literalNode)) { - return fixer.replaceText(literalNode, `{${context.getSourceCode().getText(literalNode)}}`); + return fixer.replaceText(literalNode, `{${getSourceCode(context).getText(literalNode)}}`); } // If a HTML entity name is found, bail out because it can be fixed @@ -251,7 +252,7 @@ module.exports = { const expression = JSXExpressionNode.expression; const expressionType = expression.type; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); // Curly braces containing comments are necessary if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0) { return; diff --git a/lib/rules/jsx-curly-newline.js b/lib/rules/jsx-curly-newline.js index 068a7103da..f2a4550f71 100644 --- a/lib/rules/jsx-curly-newline.js +++ b/lib/rules/jsx-curly-newline.js @@ -5,6 +5,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -77,7 +78,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const option = getNormalizedOption(context); // ---------------------------------------------------------------------- diff --git a/lib/rules/jsx-curly-spacing.js b/lib/rules/jsx-curly-spacing.js index 094e612c7d..8d504f1d20 100644 --- a/lib/rules/jsx-curly-spacing.js +++ b/lib/rules/jsx-curly-spacing.js @@ -13,6 +13,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -175,7 +176,7 @@ module.exports = { * @returns {Object|*|{range, text}} */ function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing) { - let replacementText = context.getSourceCode().text.slice(fromLoc, toLoc); + let replacementText = getSourceCode(context).text.slice(fromLoc, toLoc); if (mode === 'start') { replacementText = replacementText.replace(/^\s+/gm, ''); } else { @@ -206,7 +207,7 @@ module.exports = { token: token.value, }, fix(fixer) { - const nextToken = context.getSourceCode().getTokenAfter(token); + const nextToken = getSourceCode(context).getTokenAfter(token); return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start', spacing); }, }); @@ -227,7 +228,7 @@ module.exports = { token: token.value, }, fix(fixer) { - const previousToken = context.getSourceCode().getTokenBefore(token); + const previousToken = getSourceCode(context).getTokenBefore(token); return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end', spacing); }, }); @@ -247,7 +248,7 @@ module.exports = { token: token.value, }, fix(fixer) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const nextToken = sourceCode.getTokenAfter(token); let nextComment; @@ -284,7 +285,7 @@ module.exports = { token: token.value, }, fix(fixer) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const previousToken = sourceCode.getTokenBefore(token); let previousComment; @@ -370,7 +371,7 @@ module.exports = { return; } - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const first = sourceCode.getFirstToken(node); const last = sourceCode.getLastToken(node); let second = sourceCode.getTokenAfter(first, { includeComments: true }); diff --git a/lib/rules/jsx-equals-spacing.js b/lib/rules/jsx-equals-spacing.js index e5eefd2bb5..3424961a9d 100644 --- a/lib/rules/jsx-equals-spacing.js +++ b/lib/rules/jsx-equals-spacing.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -59,7 +60,7 @@ module.exports = { return; } - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const equalToken = sourceCode.getTokenAfter(attrNode.name); const spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken); const spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value); diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js index 38b4dd8b4b..c075c47002 100644 --- a/lib/rules/jsx-fragments.js +++ b/lib/rules/jsx-fragments.js @@ -11,6 +11,7 @@ const variableUtil = require('../util/variable'); const testReactVersion = require('../util/version').testReactVersion; const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -65,7 +66,7 @@ module.exports = { } function getFixerToLong(jsxFragment) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); if (!jsxFragment.closingFragment || !jsxFragment.openingFragment) { // the old TS parser crashes here // TODO: FIXME: can we fake these two descriptors? @@ -83,7 +84,7 @@ module.exports = { } function getFixerToShort(jsxElement) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); return function fix(fixer) { let source = sourceCode.getText(); let lengthDiff; diff --git a/lib/rules/jsx-handler-names.js b/lib/rules/jsx-handler-names.js index 8ff56fb47a..8e2227e89f 100644 --- a/lib/rules/jsx-handler-names.js +++ b/lib/rules/jsx-handler-names.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -129,7 +130,7 @@ module.exports = { const propKey = typeof node.name === 'object' ? node.name.name : node.name; const expression = node.value.expression; - const propValue = context.getSourceCode() + const propValue = getSourceCode(context) .getText(checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression) .replace(/\s*/g, '') .replace(/^this\.|.*::/, ''); diff --git a/lib/rules/jsx-indent-props.js b/lib/rules/jsx-indent-props.js index 0d971ac7ac..7ba02450dd 100644 --- a/lib/rules/jsx-indent-props.js +++ b/lib/rules/jsx-indent-props.js @@ -32,6 +32,7 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const reportC = require('../util/report'); // ------------------------------------------------------------------------------ @@ -140,7 +141,7 @@ module.exports = { * @return {Number} Indent */ function getNodeIndent(node) { - let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart); + let src = getSourceCode(context).getText(node, node.loc.start.column + extraColumnStart); const lines = src.split('\n'); src = lines[0]; diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index 9a86e6c03a..7f9e8bf2df 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -36,6 +36,7 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const reportC = require('../util/report'); const jsxUtil = require('../util/jsx'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -116,7 +117,7 @@ module.exports = { } if (node.type === 'ReturnStatement') { - const raw = context.getSourceCode().getText(node); + const raw = getSourceCode(context).getText(node); const lines = raw.split('\n'); if (lines.length > 1) { return function fix(fixer) { @@ -168,7 +169,7 @@ module.exports = { * @return {Number} Indent */ function getNodeIndent(node, byLastLine, excludeCommas) { - let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart); + let src = getSourceCode(context).getText(node, node.loc.start.column + extraColumnStart); const lines = src.split('\n'); if (byLastLine) { src = lines[lines.length - 1]; @@ -215,7 +216,7 @@ module.exports = { && node.parent.parent && node.parent.parent.type === 'ConditionalExpression' && node.parent.parent.alternate === node.parent - && context.getSourceCode().getTokenBefore(node).value !== '(' + && getSourceCode(context).getTokenBefore(node).value !== '(' ); } @@ -334,7 +335,7 @@ module.exports = { } function handleOpeningElement(node) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); let prevToken = sourceCode.getTokenBefore(node); if (!prevToken) { return; @@ -377,7 +378,7 @@ module.exports = { return; } const nameIndent = getNodeIndent(node.name); - const lastToken = context.getSourceCode().getLastToken(node.value); + const lastToken = getSourceCode(context).getLastToken(node.value); const firstInLine = astUtil.getFirstNodeInLine(context, lastToken); const indent = node.name.loc.start.line === firstInLine.loc.start.line ? 0 : nameIndent; checkNodesIndent(firstInLine, indent); diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 7ea874d0ae..70c745daf1 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -12,6 +12,7 @@ const docsUrl = require('../util/docsUrl'); const pragmaUtil = require('../util/pragma'); const report = require('../util/report'); const astUtil = require('../util/ast'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -213,7 +214,7 @@ module.exports = { } } else { keys.forEach((attr) => { - const value = context.getSourceCode().getText(attr.value); + const value = getSourceCode(context).getText(attr.value); if (!map[value]) { map[value] = []; } map[value].push(attr); diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js index 95d7942f9a..3f609ca1cf 100644 --- a/lib/rules/jsx-max-props-per-line.js +++ b/lib/rules/jsx-max-props-per-line.js @@ -6,11 +6,12 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); function getPropName(context, propNode) { if (propNode.type === 'JSXSpreadAttribute') { - return context.getSourceCode().getText(propNode.argument); + return getSourceCode(context).getText(propNode.argument); } return propNode.name.name; } @@ -87,7 +88,7 @@ module.exports = { }; function generateFixFunction(line, max) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const output = []; const front = line[0].range[0]; const back = line[line.length - 1].range[1]; diff --git a/lib/rules/jsx-newline.js b/lib/rules/jsx-newline.js index 966bf6a34b..1b87383bd6 100644 --- a/lib/rules/jsx-newline.js +++ b/lib/rules/jsx-newline.js @@ -7,6 +7,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -71,7 +72,7 @@ module.exports = { }, create(context) { const jsxElementParents = new Set(); - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); function isBlockCommentInCurlyBraces(element) { const elementRawValue = sourceCode.getText(element); diff --git a/lib/rules/jsx-no-comment-textnodes.js b/lib/rules/jsx-no-comment-textnodes.js index 2a90467d3f..d2428bccca 100644 --- a/lib/rules/jsx-no-comment-textnodes.js +++ b/lib/rules/jsx-no-comment-textnodes.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -18,7 +19,7 @@ const messages = { function checkText(node, context) { // since babel-eslint has the wrong node.raw, we'll get the source text - const rawValue = context.getSourceCode().getText(node); + const rawValue = getSourceCode(context).getText(node); if (/^\s*\/(\/|\*)/m.test(rawValue)) { // inside component, e.g.
literal
if ( diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index e20524c8d2..9833996c14 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -8,6 +8,7 @@ const find = require('es-iterator-helpers/Iterator.prototype.find'); const from = require('es-iterator-helpers/Iterator.from'); +const getSourceCode = require('../util/eslint').getSourceCode; const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const variableUtil = require('../util/variable'); @@ -55,7 +56,7 @@ function extractExpressionBetweenLogicalAnds(node) { } function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const rightSideText = sourceCode.getText(rightNode); if (fixStrategy === COERCE_STRATEGY) { diff --git a/lib/rules/jsx-no-literals.js b/lib/rules/jsx-no-literals.js index ca4f3a8d46..3fa8ab03d5 100644 --- a/lib/rules/jsx-no-literals.js +++ b/lib/rules/jsx-no-literals.js @@ -11,6 +11,7 @@ const map = require('es-iterator-helpers/Iterator.prototype.map'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -149,7 +150,7 @@ module.exports = { report(context, messages[messageId], messageId, { node, data: { - text: context.getSourceCode().getText(node).trim(), + text: getSourceCode(context).getText(node).trim(), }, }); } diff --git a/lib/rules/jsx-no-undef.js b/lib/rules/jsx-no-undef.js index 3a1f4dbc5f..656602c69b 100644 --- a/lib/rules/jsx-no-undef.js +++ b/lib/rules/jsx-no-undef.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const eslintUtil = require('../util/eslint'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); @@ -51,7 +52,7 @@ module.exports = { */ function checkIdentifierInJSX(node) { let scope = context.getScope(); - const sourceCode = context.getSourceCode(); + const sourceCode = eslintUtil.getSourceCode(context); const sourceType = sourceCode.ast.sourceType; const scopeUpperBound = !allowGlobals && sourceType === 'module' ? 'module' : 'global'; let variables = scope.variables; diff --git a/lib/rules/jsx-no-useless-fragment.js b/lib/rules/jsx-no-useless-fragment.js index 303b9719e5..11516c5a22 100644 --- a/lib/rules/jsx-no-useless-fragment.js +++ b/lib/rules/jsx-no-useless-fragment.js @@ -10,6 +10,7 @@ const pragmaUtil = require('../util/pragma'); const jsxUtil = require('../util/jsx'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; function isJSXText(node) { return !!node && (node.type === 'JSXText' || node.type === 'Literal'); @@ -216,7 +217,7 @@ module.exports = { const opener = node.type === 'JSXFragment' ? node.openingFragment : node.openingElement; const closer = node.type === 'JSXFragment' ? node.closingFragment : node.closingElement; - const childrenText = opener.selfClosing ? '' : context.getSourceCode().getText().slice(opener.range[1], closer.range[0]); + const childrenText = opener.selfClosing ? '' : getSourceCode(context).getText().slice(opener.range[1], closer.range[0]); return fixer.replaceText(node, trimLikeReact(childrenText)); }; diff --git a/lib/rules/jsx-one-expression-per-line.js b/lib/rules/jsx-one-expression-per-line.js index f18fa919e1..3e2fe4c0da 100644 --- a/lib/rules/jsx-one-expression-per-line.js +++ b/lib/rules/jsx-one-expression-per-line.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const jsxUtil = require('../util/jsx'); const report = require('../util/report'); @@ -55,8 +56,12 @@ module.exports = { return `${node.loc.start.line},${node.loc.start.column}`; } + /** + * @param {ASTNode} n + * @returns {string} + */ function nodeDescriptor(n) { - return n.openingElement ? n.openingElement.name.name : context.getSourceCode().getText(n).replace(/\n/g, ''); + return n.openingElement ? n.openingElement.name.name : getSourceCode(context).getText(n).replace(/\n/g, ''); } function handleJSX(node) { @@ -163,20 +168,20 @@ module.exports = { function spaceBetweenPrev() { return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && / $/.test(prevChild.raw)) || ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw)) - || context.getSourceCode().isSpaceBetweenTokens(prevChild, child); + || getSourceCode(context).isSpaceBetweenTokens(prevChild, child); } function spaceBetweenNext() { return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && /^ /.test(nextChild.raw)) || ((child.type === 'Literal' || child.type === 'JSXText') && / $/.test(child.raw)) - || context.getSourceCode().isSpaceBetweenTokens(child, nextChild); + || getSourceCode(context).isSpaceBetweenTokens(child, nextChild); } if (!prevChild && !nextChild) { return; } - const source = context.getSourceCode().getText(child); + const source = getSourceCode(context).getText(child); const leadingSpace = !!(prevChild && spaceBetweenPrev()); const trailingSpace = !!(nextChild && spaceBetweenNext()); const leadingNewLine = !!prevChild; diff --git a/lib/rules/jsx-props-no-multi-spaces.js b/lib/rules/jsx-props-no-multi-spaces.js index edd122c413..a3a8acaa59 100644 --- a/lib/rules/jsx-props-no-multi-spaces.js +++ b/lib/rules/jsx-props-no-multi-spaces.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -34,12 +35,12 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); function getPropName(propNode) { switch (propNode.type) { case 'JSXSpreadAttribute': - return context.getSourceCode().getText(propNode.argument); + return sourceCode.getText(propNode.argument); case 'JSXIdentifier': return propNode.name; case 'JSXMemberExpression': @@ -47,7 +48,7 @@ module.exports = { default: return propNode.name ? propNode.name.name - : `${context.getSourceCode().getText(propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser + : `${sourceCode.getText(propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser } } @@ -82,7 +83,7 @@ module.exports = { return; } - const between = context.getSourceCode().text.slice(prev.range[1], node.range[0]); + const between = getSourceCode(context).text.slice(prev.range[1], node.range[0]); if (between !== ' ') { report(context, messages.onlyOneSpace, 'onlyOneSpace', { diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index f35e40dc5f..312b8d404e 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -10,6 +10,7 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const log = require('../util/log'); +const getSourceCode = require('../util/eslint').getSourceCode; let isWarnedForDeprecation = false; @@ -65,7 +66,7 @@ module.exports = { // (babel-eslint@5 does not expose property name so we have to rely on tokens) } if (node.type === 'ClassProperty') { - const tokens = context.getSourceCode().getFirstTokens(node, 2); + const tokens = getSourceCode(context).getFirstTokens(node, 2); return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; } return ''; @@ -82,7 +83,7 @@ module.exports = { } function getKey(node) { - return context.getSourceCode().getText(node.key || node.argument); + return getSourceCode(context).getText(node.key || node.argument); } /** diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js index 3ca1724ebe..fc8565191f 100644 --- a/lib/rules/jsx-sort-props.js +++ b/lib/rules/jsx-sort-props.js @@ -12,6 +12,7 @@ const toSorted = require('array.prototype.tosorted'); const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -141,7 +142,7 @@ function contextCompare(a, b, options) { * @return {Array>} */ function getGroupsOfSortableAttributes(attributes, context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const sortableAttributeGroups = []; let groupCount = 0; @@ -212,7 +213,7 @@ function getGroupsOfSortableAttributes(attributes, context) { } function generateFixerFunction(node, context, reservedList) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const attributes = node.attributes.slice(0); const configuration = context.options[0] || {}; const ignoreCase = configuration.ignoreCase || false; diff --git a/lib/rules/jsx-space-before-closing.js b/lib/rules/jsx-space-before-closing.js index f1e1e61d96..e923adfbd3 100644 --- a/lib/rules/jsx-space-before-closing.js +++ b/lib/rules/jsx-space-before-closing.js @@ -10,6 +10,7 @@ const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBrack const docsUrl = require('../util/docsUrl'); const log = require('../util/log'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; let isWarnedForDeprecation = false; @@ -54,7 +55,7 @@ module.exports = { return; } - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const leftToken = getTokenBeforeClosingBracket(node); const closingSlash = sourceCode.getTokenAfter(leftToken); diff --git a/lib/rules/jsx-tag-spacing.js b/lib/rules/jsx-tag-spacing.js index 9cc18e8c37..ee61c098f4 100644 --- a/lib/rules/jsx-tag-spacing.js +++ b/lib/rules/jsx-tag-spacing.js @@ -8,6 +8,7 @@ const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; const messages = { selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`', @@ -29,7 +30,7 @@ const messages = { // ------------------------------------------------------------------------------ function validateClosingSlash(context, node, option) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); let adjacent; @@ -97,7 +98,7 @@ function validateClosingSlash(context, node, option) { } function validateBeforeSelfClosing(context, node, option) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const leftToken = getTokenBeforeClosingBracket(node); const closingSlash = sourceCode.getTokenAfter(leftToken); @@ -141,7 +142,7 @@ function validateBeforeSelfClosing(context, node, option) { } function validateAfterOpening(context, node, option) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const openingToken = sourceCode.getTokenBefore(node.name); if (option === 'allow-multiline') { @@ -182,7 +183,7 @@ function validateAfterOpening(context, node, option) { function validateBeforeClosing(context, node, option) { // Don't enforce this rule for self closing tags if (!node.selfClosing) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const leftToken = option === 'proportional-always' ? getTokenBeforeClosingBracket(node) : sourceCode.getLastTokens(node, 2)[0]; diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index 59fa5f294b..c2da2a7882 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -7,6 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const jsxUtil = require('../util/jsx'); const reportC = require('../util/report'); const isParenthesized = require('../util/ast').isParenthesized; @@ -93,7 +94,7 @@ module.exports = { } function needsOpeningNewLine(node) { - const previousToken = context.getSourceCode().getTokenBefore(node); + const previousToken = getSourceCode(context).getTokenBefore(node); if (!isParenthesized(context, node)) { return false; @@ -107,7 +108,7 @@ module.exports = { } function needsClosingNewLine(node) { - const nextToken = context.getSourceCode().getTokenAfter(node); + const nextToken = getSourceCode(context).getTokenAfter(node); if (!isParenthesized(context, node)) { return false; @@ -143,7 +144,7 @@ module.exports = { return; } - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const option = getOption(type); if ((option === true || option === 'parens') && !isParenthesized(context, node) && isMultilines(node)) { diff --git a/lib/rules/no-arrow-function-lifecycle.js b/lib/rules/no-arrow-function-lifecycle.js index a1de789b81..71c8389522 100644 --- a/lib/rules/no-arrow-function-lifecycle.js +++ b/lib/rules/no-arrow-function-lifecycle.js @@ -13,6 +13,7 @@ const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); const lifecycleMethods = require('../util/lifecycleMethods'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; function getText(node) { const params = node.value.params.map((p) => p.name); @@ -67,7 +68,7 @@ module.exports = { if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) { const body = node.value.body; const isBlockBody = body.type === 'BlockStatement'; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); let nextComment = []; let previousComment = []; diff --git a/lib/rules/no-deprecated.js b/lib/rules/no-deprecated.js index d37b55bbfd..1541942b86 100644 --- a/lib/rules/no-deprecated.js +++ b/lib/rules/no-deprecated.js @@ -14,6 +14,7 @@ const docsUrl = require('../util/docsUrl'); const pragmaUtil = require('../util/pragma'); const testReactVersion = require('../util/version').testReactVersion; const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Constants @@ -217,7 +218,7 @@ module.exports = { return { MemberExpression(node) { - checkDeprecation(node, context.getSourceCode().getText(node)); + checkDeprecation(node, getSourceCode(context).getText(node)); }, ImportDeclaration(node) { diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js index a449ad5206..ecf1abc5e1 100644 --- a/lib/rules/no-unescaped-entities.js +++ b/lib/rules/no-unescaped-entities.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const jsxUtil = require('../util/jsx'); const report = require('../util/report'); @@ -83,9 +84,9 @@ module.exports = { const entities = configuration.forbid || DEFAULTS; // HTML entities are already escaped in node.value (as well as node.raw), - // so pull the raw text from context.getSourceCode() + // so pull the raw text from getSourceCode(context) for (let i = node.loc.start.line; i <= node.loc.end.line; i++) { - let rawLine = context.getSourceCode().lines[i - 1]; + let rawLine = getSourceCode(context).lines[i - 1]; let start = 0; let end = rawLine.length; if (i === node.loc.start.line) { diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index 9491f9c658..c8071c4a43 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -7,6 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; const testReactVersion = require('../util/version').testReactVersion; const report = require('../util/report'); @@ -555,7 +556,7 @@ module.exports = { return { JSXAttribute(node) { const ignoreNames = getIgnoreConfig(); - const actualName = context.getSourceCode().getText(node.name); + const actualName = getSourceCode(context).getText(node.name); if (ignoreNames.indexOf(actualName) >= 0) { return; } diff --git a/lib/rules/prefer-exact-props.js b/lib/rules/prefer-exact-props.js index 4ad088e9ee..d9970115f6 100644 --- a/lib/rules/prefer-exact-props.js +++ b/lib/rules/prefer-exact-props.js @@ -10,6 +10,7 @@ const propsUtil = require('../util/props'); const propWrapperUtil = require('../util/propWrapper'); const variableUtil = require('../util/variable'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ----------------------------------------------------------------------------- // Rule Definition @@ -36,7 +37,7 @@ module.exports = { create: Components.detect((context, components, utils) => { const typeAliases = {}; const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context); - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); function getPropTypesErrorMessage() { const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers); diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index 07c435747f..c2477a8dd5 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -15,6 +15,9 @@ const astUtil = require('../util/ast'); const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -332,7 +335,7 @@ module.exports = { // Mark `ref` usage JSXAttribute(node) { - const name = context.getSourceCode().getText(node.name); + const name = getSourceCode(context).getText(node.name); if (name !== 'ref') { return; } diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index 07adca104a..eb6bab1c09 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -74,11 +74,11 @@ module.exports = { /** * Checks if the component must be validated * @param {Object} component The component to process - * @returns {Boolean} True if the component must be validated, false if not. + * @returns {boolean} True if the component must be validated, false if not. */ function mustBeValidated(component) { const isSkippedByConfig = skipUndeclared && typeof component.declaredPropTypes === 'undefined'; - return Boolean( + return !!( component && component.usedPropTypes && !component.ignorePropsValidation @@ -90,7 +90,7 @@ module.exports = { * Internal: Checks if the prop is declared * @param {Object} declaredPropTypes Description of propTypes declared in the current component * @param {String[]} keyList Dot separated name of the prop to check. - * @returns {Boolean} True if the prop is declared, false if not. + * @returns {boolean} True if the prop is declared, false if not. */ function internalIsDeclaredInComponent(declaredPropTypes, keyList) { for (let i = 0, j = keyList.length; i < j; i++) { diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js index aae419ba18..dfb4aeff7c 100644 --- a/lib/rules/sort-default-props.js +++ b/lib/rules/sort-default-props.js @@ -9,6 +9,7 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -60,7 +61,7 @@ module.exports = { // (babel-eslint@5 does not expose property name so we have to rely on tokens) } if (node.type === 'ClassProperty') { - const tokens = context.getSourceCode().getFirstTokens(node, 2); + const tokens = getSourceCode(context).getFirstTokens(node, 2); return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; } return ''; @@ -77,7 +78,7 @@ module.exports = { } function getKey(node) { - return context.getSourceCode().getText(node.key || node.argument); + return getSourceCode(context).getText(node.key || node.argument); } /** diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 8c3574af4a..d4e0863046 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -10,6 +10,7 @@ const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const propTypesSortUtil = require('../util/propTypesSort'); const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -23,12 +24,12 @@ const messages = { function getKey(context, node) { if (node.type === 'ObjectTypeProperty') { - return context.getSourceCode().getFirstToken(node).value; + return getSourceCode(context).getFirstToken(node).value; } if (node.key && node.key.value) { return node.key.value; } - return context.getSourceCode().getText(node.key || node.argument); + return getSourceCode(context).getText(node.key || node.argument); } function getValueName(node) { diff --git a/lib/util/Components.js b/lib/util/Components.js index a31989702f..097f373efd 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -21,6 +21,9 @@ const usedPropTypesUtil = require('./usedPropTypes'); const defaultPropsUtil = require('./defaultProps'); const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport'); +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; function getId(node) { return node ? `${node.range[0]}:${node.range[1]}` : ''; @@ -282,7 +285,7 @@ function mergeRules(rules) { function componentRule(rule, context) { const pragma = pragmaUtil.getFromContext(context); - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const components = new Components(); const wrapperFunctions = getWrapperFunctions(context, pragma); diff --git a/lib/util/ast.js b/lib/util/ast.js index fd6019a3f2..1384a44cee 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -5,6 +5,9 @@ 'use strict'; const estraverse = require('estraverse'); +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; // const pragmaUtil = require('./pragma'); /** @@ -186,7 +189,7 @@ function getComponentProperties(node) { * @return {ASTNode} the first node in the line */ function getFirstNodeInLine(context, node) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); let token = node; let lines; do { @@ -284,7 +287,7 @@ function stripQuotes(string) { */ function getKeyValue(context, node) { if (node.type === 'ObjectTypeProperty') { - const tokens = context.getSourceCode().getFirstTokens(node, 2); + const tokens = getSourceCode(context).getFirstTokens(node, 2); return (tokens[0].value === '+' || tokens[0].value === '-' ? tokens[1].value : stripQuotes(tokens[0].value) @@ -311,7 +314,7 @@ function getKeyValue(context, node) { * @returns {boolean} */ function isParenthesized(context, node) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const previousToken = sourceCode.getTokenBefore(node); const nextToken = sourceCode.getTokenAfter(node); diff --git a/lib/util/componentUtil.js b/lib/util/componentUtil.js index 35d54edcc5..29720c124f 100644 --- a/lib/util/componentUtil.js +++ b/lib/util/componentUtil.js @@ -2,6 +2,9 @@ const doctrine = require('doctrine'); const pragmaUtil = require('./pragma'); +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; // eslint-disable-next-line valid-jsdoc /** @@ -57,7 +60,7 @@ function isES5Component(node, context) { * @returns {boolean} */ function isExplicitComponent(node, context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); let comment; // Sometimes the passed node may not have been parsed yet by eslint, and this function call crashes. // Can be removed when eslint sets "parent" property for all nodes on initial AST traversal: https://github.com/eslint/eslint-scope/issues/27 @@ -156,7 +159,7 @@ function getParentES6Component(context) { */ function isPureComponent(node, context) { const pragma = getPragma(context); - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); if (node.superClass) { return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(sourceCode.getText(node.superClass)); } diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js index 44b42454ce..c747030ba3 100644 --- a/lib/util/defaultProps.js +++ b/lib/util/defaultProps.js @@ -10,11 +10,12 @@ const componentUtil = require('./componentUtil'); const propsUtil = require('./props'); const variableUtil = require('./variable'); const propWrapperUtil = require('./propWrapper'); +const getSourceCode = require('./eslint').getSourceCode; const QUOTES_REGEX = /^["']|["']$/g; module.exports = function defaultPropsInstructions(context, components, utils) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); /** * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not diff --git a/lib/util/eslint.js b/lib/util/eslint.js new file mode 100644 index 0000000000..eb0a66d864 --- /dev/null +++ b/lib/util/eslint.js @@ -0,0 +1,9 @@ +'use strict'; + +function getSourceCode(context) { + return context.getSourceCode ? context.getSourceCode() : context.sourceCode; +} + +module.exports = { + getSourceCode, +}; diff --git a/lib/util/pragma.js b/lib/util/pragma.js index 2bde47fb37..4114442d05 100644 --- a/lib/util/pragma.js +++ b/lib/util/pragma.js @@ -5,6 +5,8 @@ 'use strict'; +const getSourceCode = require('./eslint').getSourceCode; + const JSX_ANNOTATION_REGEX = /@jsx\s+([^\s]+)/; // Does not check for reserved keywords or unicode characters const JS_IDENTIFIER_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/; @@ -48,7 +50,7 @@ function getFragmentFromContext(context) { function getFromContext(context) { let pragma = 'React'; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const pragmaNode = sourceCode.getAllComments().find((node) => JSX_ANNOTATION_REGEX.test(node.value)); if (pragmaNode) { diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index a189f871ca..854de13f35 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -13,6 +13,9 @@ const testFlowVersion = require('./version').testFlowVersion; const propWrapperUtil = require('./propWrapper'); const astUtil = require('./ast'); const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; /** * Check if node is function type. @@ -578,7 +581,7 @@ module.exports = function propTypesInstructions(context, components, utils) { this.declaredPropTypes = declaredPropTypes; this.foundDeclaredPropertiesList = []; this.referenceNameMap = new Set(); - this.sourceCode = context.getSourceCode(); + this.sourceCode = getSourceCode(context); this.shouldIgnorePropTypes = false; this.visitTSNode(this.propTypes); this.endAndStructDeclaredPropTypes(); @@ -949,7 +952,7 @@ module.exports = function propTypesInstructions(context, components, utils) { if ( propWrapperUtil.isPropWrapperFunction( context, - context.getSourceCode().getText(propTypes.callee) + getSourceCode(context).getText(propTypes.callee) ) && propTypes.arguments && propTypes.arguments[0] ) { diff --git a/lib/util/propTypesSort.js b/lib/util/propTypesSort.js index 505f346a3b..c9f31ad1ce 100644 --- a/lib/util/propTypesSort.js +++ b/lib/util/propTypesSort.js @@ -7,6 +7,7 @@ const toSorted = require('array.prototype.tosorted'); const astUtil = require('./ast'); +const getSourceCode = require('./eslint').getSourceCode; /** * Returns the value name of a node. @@ -136,7 +137,7 @@ function fixPropTypesSort( ) { function sortInSource(allNodes, source) { const originalSource = source; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); for (let i = 0; i < allNodes.length; i++) { const node = allNodes[i]; let commentAfter = []; @@ -199,7 +200,7 @@ function fixPropTypesSort( return source; } - const source = sortInSource(declarations, context.getSourceCode().getText()); + const source = sortInSource(declarations, getSourceCode(context).getText()); const rangeStart = commentnodeMap.get(declarations[0]).start; const rangeEnd = commentnodeMap.get(declarations[declarations.length - 1]).end; diff --git a/lib/util/usedPropTypes.js b/lib/util/usedPropTypes.js index 6a0a650333..70ceb9e208 100644 --- a/lib/util/usedPropTypes.js +++ b/lib/util/usedPropTypes.js @@ -10,6 +10,9 @@ const astUtil = require('./ast'); const componentUtil = require('./componentUtil'); const testReactVersion = require('./version').testReactVersion; const ast = require('./ast'); +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; // ------------------------------------------------------------------------------ // Constants @@ -211,7 +214,7 @@ function isThisDotProps(node) { * @returns {Boolean} True if the prop has spread operator, false if not. */ function hasSpreadOperator(context, node) { - const tokens = context.getSourceCode().getTokens(node); + const tokens = getSourceCode(context).getTokens(node); return tokens.length && tokens[0].value === '...'; } diff --git a/tests/util/jsx.js b/tests/util/jsx.js index c8009fc005..e16adc9b84 100644 --- a/tests/util/jsx.js +++ b/tests/util/jsx.js @@ -21,6 +21,7 @@ const parseCode = (code) => { }; const mockContext = { + getSourceCode() { return { getScope: mockContext.getScope }; }, getScope() { return { type: 'global', From 6be3329603164efca220091f0ebcc6631ae02bfa 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 030/174] [Refactor] create `getAncestors` util, since `context.getAncestors` 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/jsx-no-bind.js | 3 ++- lib/rules/no-is-mounted.js | 3 ++- lib/rules/require-render-return.js | 3 ++- lib/util/eslint.js | 6 ++++++ lib/util/makeNoMethodSetStateRule.js | 3 ++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index 17e56e2e04..4d6e349d25 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -11,6 +11,7 @@ const propName = require('jsx-ast-utils/propName'); const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); +const getAncestors = require('../util/eslint').getAncestors; // ----------------------------------------------------------------------------- // Rule Definition @@ -123,7 +124,7 @@ module.exports = { } function getBlockStatementAncestors(node) { - return context.getAncestors(node).filter( + return getAncestors(context, node).filter( (ancestor) => ancestor.type === 'BlockStatement' ).reverse(); } diff --git a/lib/rules/no-is-mounted.js b/lib/rules/no-is-mounted.js index c15b6e81a4..fd99d5b683 100644 --- a/lib/rules/no-is-mounted.js +++ b/lib/rules/no-is-mounted.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const getAncestors = require('../util/eslint').getAncestors; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -40,7 +41,7 @@ module.exports = { if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'isMounted') { return; } - const ancestors = context.getAncestors(callee); + const ancestors = getAncestors(context, node); for (let i = 0, j = ancestors.length; i < j; i++) { if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') { report(context, messages.noIsMounted, 'noIsMounted', { diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js index d79c9406b7..c46613e420 100644 --- a/lib/rules/require-render-return.js +++ b/lib/rules/require-render-return.js @@ -12,6 +12,7 @@ const astUtil = require('../util/ast'); const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const getAncestors = require('../util/eslint').getAncestors; // ------------------------------------------------------------------------------ // Rule Definition @@ -61,7 +62,7 @@ module.exports = { return { ReturnStatement(node) { - const ancestors = context.getAncestors(node).reverse(); + const ancestors = getAncestors(context, node).reverse(); let depth = 0; ancestors.forEach((ancestor) => { if (/Function(Expression|Declaration)$/.test(ancestor.type)) { diff --git a/lib/util/eslint.js b/lib/util/eslint.js index eb0a66d864..ce4ea886fe 100644 --- a/lib/util/eslint.js +++ b/lib/util/eslint.js @@ -4,6 +4,12 @@ function getSourceCode(context) { return context.getSourceCode ? context.getSourceCode() : context.sourceCode; } +function getAncestors(context, node) { + const sourceCode = getSourceCode(context); + return sourceCode.getAncestors ? sourceCode.getAncestors(node) : context.getAncestors(); +} + module.exports = { + getAncestors, getSourceCode, }; diff --git a/lib/util/makeNoMethodSetStateRule.js b/lib/util/makeNoMethodSetStateRule.js index 1225092802..faee6de163 100644 --- a/lib/util/makeNoMethodSetStateRule.js +++ b/lib/util/makeNoMethodSetStateRule.js @@ -9,6 +9,7 @@ const findLast = require('array.prototype.findlast'); const docsUrl = require('./docsUrl'); const report = require('./report'); +const getAncestors = require('./eslint').getAncestors; const testReactVersion = require('./version').testReactVersion; // ------------------------------------------------------------------------------ @@ -93,7 +94,7 @@ module.exports = function makeNoMethodSetStateRule(methodName, shouldCheckUnsafe ) { return; } - const ancestors = context.getAncestors(callee); + const ancestors = getAncestors(context, node); let depth = 0; findLast(ancestors, (ancestor) => { // ancestors.some((ancestor) => { From 90068a4fabd68b49bc22315915faba14e065c8ae 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 031/174] [Refactor] create `markVariableAsUsed` util, `context.markVariableAsUsed` 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/jsx-uses-react.js | 13 +++++++++---- lib/rules/jsx-uses-vars.js | 3 ++- lib/util/eslint.js | 8 ++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/rules/jsx-uses-react.js b/lib/rules/jsx-uses-react.js index bf06f508f8..5d64f76cd8 100644 --- a/lib/rules/jsx-uses-react.js +++ b/lib/rules/jsx-uses-react.js @@ -7,6 +7,7 @@ const pragmaUtil = require('../util/pragma'); const docsUrl = require('../util/docsUrl'); +const markVariableAsUsed = require('../util/eslint').markVariableAsUsed; // ------------------------------------------------------------------------------ // Rule Definition @@ -29,8 +30,12 @@ module.exports = { const pragma = pragmaUtil.getFromContext(context); const fragment = pragmaUtil.getFragmentFromContext(context); - function handleOpeningElement() { - context.markVariableAsUsed(pragma); + /** + * @param {ASTNode} node + * @returns {void} + */ + function handleOpeningElement(node) { + markVariableAsUsed(pragma, node, context); } // -------------------------------------------------------------------------- // Public @@ -39,8 +44,8 @@ module.exports = { return { JSXOpeningElement: handleOpeningElement, JSXOpeningFragment: handleOpeningElement, - JSXFragment() { - context.markVariableAsUsed(fragment); + JSXFragment(node) { + markVariableAsUsed(fragment, node, context); }, }; }, diff --git a/lib/rules/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js index 9ea9ab001e..6f5f4f6cc0 100644 --- a/lib/rules/jsx-uses-vars.js +++ b/lib/rules/jsx-uses-vars.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const markVariableAsUsed = require('../util/eslint').markVariableAsUsed; // ------------------------------------------------------------------------------ // Rule Definition @@ -53,7 +54,7 @@ module.exports = { return; } - context.markVariableAsUsed(name); + markVariableAsUsed(name, node, context); }, }; diff --git a/lib/util/eslint.js b/lib/util/eslint.js index ce4ea886fe..ee14d8fcdd 100644 --- a/lib/util/eslint.js +++ b/lib/util/eslint.js @@ -9,7 +9,15 @@ function getAncestors(context, node) { return sourceCode.getAncestors ? sourceCode.getAncestors(node) : context.getAncestors(); } +function markVariableAsUsed(name, node, context) { + const sourceCode = getSourceCode(context); + return sourceCode.markVariableAsUsed + ? sourceCode.markVariableAsUsed(name, node) + : context.markVariableAsUsed(name); +} + module.exports = { getAncestors, getSourceCode, + markVariableAsUsed, }; From 0c804bb93a747275b0014da86c96bb9de9ad7d02 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 16 May 2024 16:57:45 -0700 Subject: [PATCH 032/174] [Refactor] context comes first --- lib/rules/button-has-type.js | 2 +- lib/rules/checked-requires-onchange-or-readonly.js | 2 +- lib/rules/forbid-elements.js | 2 +- lib/rules/iframe-missing-sandbox.js | 2 +- lib/rules/jsx-indent.js | 2 +- lib/rules/jsx-no-comment-textnodes.js | 11 ++++++++--- lib/rules/jsx-sort-default-props.js | 2 +- lib/rules/no-adjacent-inline-elements.js | 2 +- lib/rules/no-children-prop.js | 2 +- lib/rules/no-namespace.js | 2 +- lib/rules/no-unstable-nested-components.js | 2 +- lib/rules/sort-default-props.js | 2 +- lib/rules/sort-prop-types.js | 2 +- lib/rules/style-prop-object.js | 2 +- lib/rules/void-dom-elements-no-children.js | 2 +- lib/util/Components.js | 11 ++++++++--- lib/util/isCreateElement.js | 8 ++++---- lib/util/isDestructuredFromPragmaImport.js | 6 +++--- lib/util/jsx.js | 6 +++--- lib/util/propTypesSort.js | 4 ++-- lib/util/usedPropTypes.js | 6 +++--- tests/util/jsx.js | 2 +- 22 files changed, 46 insertions(+), 36 deletions(-) diff --git a/lib/rules/button-has-type.js b/lib/rules/button-has-type.js index 204a33c43e..d40596067d 100644 --- a/lib/rules/button-has-type.js +++ b/lib/rules/button-has-type.js @@ -135,7 +135,7 @@ module.exports = { checkValue(node, propValue); }, CallExpression(node) { - if (!isCreateElement(node, context) || node.arguments.length < 1) { + if (!isCreateElement(context, node) || node.arguments.length < 1) { return; } diff --git a/lib/rules/checked-requires-onchange-or-readonly.js b/lib/rules/checked-requires-onchange-or-readonly.js index 420611fee5..c719644e1c 100644 --- a/lib/rules/checked-requires-onchange-or-readonly.js +++ b/lib/rules/checked-requires-onchange-or-readonly.js @@ -115,7 +115,7 @@ module.exports = { checkAttributesAndReport(node, propSet); }, CallExpression(node) { - if (!isCreateElement(node, context)) { + if (!isCreateElement(context, node)) { return; } diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index 6f2979a1a0..8170aaf380 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -95,7 +95,7 @@ module.exports = { }, CallExpression(node) { - if (!isCreateElement(node, context)) { + if (!isCreateElement(context, node)) { return; } diff --git a/lib/rules/iframe-missing-sandbox.js b/lib/rules/iframe-missing-sandbox.js index 810e30b52b..7d8f234d97 100644 --- a/lib/rules/iframe-missing-sandbox.js +++ b/lib/rules/iframe-missing-sandbox.js @@ -131,7 +131,7 @@ module.exports = { }, CallExpression(node) { - if (isCreateElement(node, context) && node.arguments && node.arguments.length > 0) { + if (isCreateElement(context, node) && node.arguments && node.arguments.length > 0) { const tag = node.arguments[0]; if (tag.type === 'Literal' && tag.value === 'iframe') { checkProps(context, node); diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index 7f9e8bf2df..aeb5c38aa4 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -425,7 +425,7 @@ module.exports = { } if ( !fn - || !jsxUtil.isReturningJSX(node, context, true) + || !jsxUtil.isReturningJSX(context, node, true) ) { return; } diff --git a/lib/rules/jsx-no-comment-textnodes.js b/lib/rules/jsx-no-comment-textnodes.js index d2428bccca..5d7139c211 100644 --- a/lib/rules/jsx-no-comment-textnodes.js +++ b/lib/rules/jsx-no-comment-textnodes.js @@ -17,7 +17,12 @@ const messages = { putCommentInBraces: 'Comments inside children section of tag should be placed inside braces', }; -function checkText(node, context) { +/** + * @param {Context} context + * @param {ASTNode} node + * @returns {void} + */ +function checkText(context, node) { // since babel-eslint has the wrong node.raw, we'll get the source text const rawValue = getSourceCode(context).getText(node); if (/^\s*\/(\/|\*)/m.test(rawValue)) { @@ -56,10 +61,10 @@ module.exports = { return { Literal(node) { - checkText(node, context); + checkText(context, node); }, JSXText(node) { - checkText(node, context); + checkText(context, node); }, }; }, diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index 312b8d404e..f66f174996 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -112,7 +112,7 @@ module.exports = { */ function checkSorted(declarations) { // function fix(fixer) { - // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase); + // return propTypesSortUtil.fixPropTypesSort(context, fixer, declarations, ignoreCase); // } declarations.reduce((prev, curr, idx, decls) => { diff --git a/lib/rules/no-adjacent-inline-elements.js b/lib/rules/no-adjacent-inline-elements.js index 60e404ba22..5d7fdb8d6e 100644 --- a/lib/rules/no-adjacent-inline-elements.js +++ b/lib/rules/no-adjacent-inline-elements.js @@ -111,7 +111,7 @@ module.exports = { validate(node, node.children); }, CallExpression(node) { - if (!isCreateElement(node, context)) { + if (!isCreateElement(context, node)) { return; } if (node.arguments.length < 2 || !node.arguments[2]) { diff --git a/lib/rules/no-children-prop.js b/lib/rules/no-children-prop.js index 440e61e945..508488e4d9 100644 --- a/lib/rules/no-children-prop.js +++ b/lib/rules/no-children-prop.js @@ -21,7 +21,7 @@ const report = require('../util/report'); * object literal, False if not. */ function isCreateElementWithProps(node, context) { - return isCreateElement(node, context) + return isCreateElement(context, node) && node.arguments.length > 1 && node.arguments[1].type === 'ObjectExpression'; } diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js index d7559f5ebd..20ca5d9324 100644 --- a/lib/rules/no-namespace.js +++ b/lib/rules/no-namespace.js @@ -36,7 +36,7 @@ module.exports = { create(context) { return { CallExpression(node) { - if (isCreateElement(node, context) && node.arguments.length > 0 && node.arguments[0].type === 'Literal') { + if (isCreateElement(context, node) && node.arguments.length > 0 && node.arguments[0].type === 'Literal') { const name = node.arguments[0].value; if (typeof name !== 'string' || name.indexOf(':') === -1) return undefined; report(context, messages.noNamespace, 'noNamespace', { diff --git a/lib/rules/no-unstable-nested-components.js b/lib/rules/no-unstable-nested-components.js index 2ef635efab..90da77a5d6 100644 --- a/lib/rules/no-unstable-nested-components.js +++ b/lib/rules/no-unstable-nested-components.js @@ -68,7 +68,7 @@ function isCreateElementMatcher(node, context) { return ( node && node.type === 'CallExpression' - && isCreateElement(node, context) + && isCreateElement(context, node) ); } diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js index dfb4aeff7c..6034cbfa7c 100644 --- a/lib/rules/sort-default-props.js +++ b/lib/rules/sort-default-props.js @@ -107,7 +107,7 @@ module.exports = { */ function checkSorted(declarations) { // function fix(fixer) { - // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase); + // return propTypesSortUtil.fixPropTypesSort(context, fixer, declarations, ignoreCase); // } declarations.reduce((prev, curr, idx, decls) => { diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index d4e0863046..2a9d307bf9 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -119,8 +119,8 @@ module.exports = { function fix(fixer) { return propTypesSortUtil.fixPropTypesSort( - fixer, context, + fixer, declarations, ignoreCase, requiredFirst, diff --git a/lib/rules/style-prop-object.js b/lib/rules/style-prop-object.js index 4d6684a6f1..d38cae17ed 100644 --- a/lib/rules/style-prop-object.js +++ b/lib/rules/style-prop-object.js @@ -77,7 +77,7 @@ module.exports = { return { CallExpression(node) { if ( - isCreateElement(node, context) + isCreateElement(context, node) && node.arguments.length > 1 ) { if (node.arguments[0].name) { diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js index 721ffae872..a8a6ba0149 100644 --- a/lib/rules/void-dom-elements-no-children.js +++ b/lib/rules/void-dom-elements-no-children.js @@ -108,7 +108,7 @@ module.exports = { return; } - if (!isCreateElement(node, context)) { + if (!isCreateElement(context, node)) { return; } diff --git a/lib/util/Components.js b/lib/util/Components.js index 097f373efd..a927c44e5c 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -298,15 +298,20 @@ function componentRule(rule, context) { * @returns {Boolean} True if createElement is destructured from the pragma */ isDestructuredFromPragmaImport(variable) { - return isDestructuredFromPragmaImport(variable, context); + return isDestructuredFromPragmaImport(context, variable); }, + /** + * @param {ASTNode} ASTNode + * @param {boolean=} strict + * @returns {boolean} + */ isReturningJSX(ASTNode, strict) { - return jsxUtil.isReturningJSX(ASTNode, context, strict, true); + return jsxUtil.isReturningJSX(context, ASTNode, strict, true); }, isReturningJSXOrNull(ASTNode, strict) { - return jsxUtil.isReturningJSX(ASTNode, context, strict); + return jsxUtil.isReturningJSX(context, ASTNode, strict); }, isReturningOnlyNull(ASTNode) { diff --git a/lib/util/isCreateElement.js b/lib/util/isCreateElement.js index c28dc563fb..3681cd91b4 100644 --- a/lib/util/isCreateElement.js +++ b/lib/util/isCreateElement.js @@ -5,11 +5,11 @@ const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport /** * Checks if the node is a createElement call - * @param {ASTNode} node - The AST node being checked. * @param {Context} context - The AST node being checked. - * @returns {Boolean} - True if node is a createElement call object literal, False if not. + * @param {ASTNode} node - The AST node being checked. + * @returns {boolean} - True if node is a createElement call object literal, False if not. */ -module.exports = function isCreateElement(node, context) { +module.exports = function isCreateElement(context, node) { if ( node.callee && node.callee.type === 'MemberExpression' @@ -24,7 +24,7 @@ module.exports = function isCreateElement(node, context) { node && node.callee && node.callee.name === 'createElement' - && isDestructuredFromPragmaImport('createElement', context) + && isDestructuredFromPragmaImport(context, 'createElement') ) { return true; } diff --git a/lib/util/isDestructuredFromPragmaImport.js b/lib/util/isDestructuredFromPragmaImport.js index 6f8deb08bc..2d75f3f087 100644 --- a/lib/util/isDestructuredFromPragmaImport.js +++ b/lib/util/isDestructuredFromPragmaImport.js @@ -6,11 +6,11 @@ const variableUtil = require('./variable'); /** * Check if variable is destructured from pragma import * - * @param {string} variable The variable name to check * @param {Context} context eslint context - * @returns {Boolean} True if createElement is destructured from the pragma + * @param {string} variable The variable name to check + * @returns {boolean} True if createElement is destructured from the pragma */ -module.exports = function isDestructuredFromPragmaImport(variable, context) { +module.exports = function isDestructuredFromPragmaImport(context, variable) { const pragma = pragmaUtil.getFromContext(context); const variables = variableUtil.variablesInScope(context); const variableInScope = variableUtil.getVariable(variables, variable); diff --git a/lib/util/jsx.js b/lib/util/jsx.js index 55073bfe1e..330b6d994e 100644 --- a/lib/util/jsx.js +++ b/lib/util/jsx.js @@ -86,13 +86,13 @@ function isWhiteSpaces(value) { /** * Check if the node is returning JSX or null * - * @param {ASTNode} ASTnode The AST node being checked * @param {Context} context The context of `ASTNode`. + * @param {ASTNode} ASTnode The AST node being checked * @param {Boolean} [strict] If true, in a ternary condition the node must return JSX in both cases * @param {Boolean} [ignoreNull] If true, null return values will be ignored * @returns {Boolean} True if the node is returning JSX or null, false if not */ -function isReturningJSX(ASTnode, context, strict, ignoreNull) { +function isReturningJSX(context, ASTnode, strict, ignoreNull) { const isJSXValue = (node) => { if (!node) { return false; @@ -114,7 +114,7 @@ function isReturningJSX(ASTnode, context, strict, ignoreNull) { case 'JSXFragment': return true; case 'CallExpression': - return isCreateElement(node, context); + return isCreateElement(context, node); case 'Literal': if (!ignoreNull && node.value === null) { return true; diff --git a/lib/util/propTypesSort.js b/lib/util/propTypesSort.js index c9f31ad1ce..1284b163ca 100644 --- a/lib/util/propTypesSort.js +++ b/lib/util/propTypesSort.js @@ -115,8 +115,8 @@ const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start a /** * Fixes sort order of prop types. * + * @param {Context} context the second element to compare. * @param {Fixer} fixer the first element to compare. - * @param {Object} context the second element to compare. * @param {Array} declarations The context of the two nodes. * @param {Boolean=} ignoreCase whether or not to ignore case when comparing the two elements. * @param {Boolean=} requiredFirst whether or not to sort required elements first. @@ -126,8 +126,8 @@ const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start a * @returns {Object|*|{range, text}} the sort order of the two elements. */ function fixPropTypesSort( - fixer, context, + fixer, declarations, ignoreCase, requiredFirst, diff --git a/lib/util/usedPropTypes.js b/lib/util/usedPropTypes.js index 70ceb9e208..2ce808feb2 100644 --- a/lib/util/usedPropTypes.js +++ b/lib/util/usedPropTypes.js @@ -253,13 +253,13 @@ function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafe /** * Retrieve the name of a property node - * @param {ASTNode} node The AST node with the property. * @param {Context} context + * @param {ASTNode} node The AST node with the property. * @param {Object} utils * @param {boolean} checkAsyncSafeLifeCycles * @return {string|undefined} the name of the property or undefined if not found */ -function getPropertyName(node, context, utils, checkAsyncSafeLifeCycles) { +function getPropertyName(context, node, utils, checkAsyncSafeLifeCycles) { const property = node.property; if (property) { switch (property.type) { @@ -312,7 +312,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils) switch (node.type) { case 'OptionalMemberExpression': case 'MemberExpression': - name = getPropertyName(node, context, utils, checkAsyncSafeLifeCycles); + name = getPropertyName(context, node, utils, checkAsyncSafeLifeCycles); if (name) { allNames = parentNames.concat(name); if ( diff --git a/tests/util/jsx.js b/tests/util/jsx.js index e16adc9b84..7be16d5410 100644 --- a/tests/util/jsx.js +++ b/tests/util/jsx.js @@ -35,7 +35,7 @@ const mockContext = { describe('jsxUtil', () => { describe('isReturningJSX', () => { const assertValid = (codeStr) => assert( - isReturningJSX(parseCode(codeStr), mockContext) + isReturningJSX(mockContext, parseCode(codeStr)) ); it('Works when returning JSX', () => { From 65f10f9fac07ba35fb2de37280b985fd19d6ef76 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 18 May 2024 09:40:07 -0700 Subject: [PATCH 033/174] [Refactor] `getFirstTokens`: context -> sourceCode --- lib/rules/jsx-sort-default-props.js | 7 +++++-- lib/rules/jsx-tag-spacing.js | 7 +++++-- lib/rules/sort-default-props.js | 7 +++++-- lib/util/annotations.js | 4 +++- lib/util/ast.js | 3 ++- lib/util/eslint.js | 6 ++++++ lib/util/propTypes.js | 3 ++- 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index f66f174996..3fd4c70b41 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -10,7 +10,10 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const log = require('../util/log'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getFirstTokens = eslintUtil.getFirstTokens; +const getSourceCode = eslintUtil.getSourceCode; let isWarnedForDeprecation = false; @@ -66,7 +69,7 @@ module.exports = { // (babel-eslint@5 does not expose property name so we have to rely on tokens) } if (node.type === 'ClassProperty') { - const tokens = getSourceCode(context).getFirstTokens(node, 2); + const tokens = getFirstTokens(context, node, 2); return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; } return ''; diff --git a/lib/rules/jsx-tag-spacing.js b/lib/rules/jsx-tag-spacing.js index ee61c098f4..ac7c693278 100644 --- a/lib/rules/jsx-tag-spacing.js +++ b/lib/rules/jsx-tag-spacing.js @@ -8,7 +8,10 @@ const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getFirstTokens = eslintUtil.getFirstTokens; +const getSourceCode = eslintUtil.getSourceCode; const messages = { selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`', @@ -65,7 +68,7 @@ function validateClosingSlash(context, node, option) { }); } } else { - const firstTokens = sourceCode.getFirstTokens(node, 2); + const firstTokens = getFirstTokens(context, node, 2); adjacent = !sourceCode.isSpaceBetweenTokens(firstTokens[0], firstTokens[1]); diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js index 6034cbfa7c..d46ef66f86 100644 --- a/lib/rules/sort-default-props.js +++ b/lib/rules/sort-default-props.js @@ -9,7 +9,10 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getFirstTokens = eslintUtil.getFirstTokens; +const getSourceCode = eslintUtil.getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -61,7 +64,7 @@ module.exports = { // (babel-eslint@5 does not expose property name so we have to rely on tokens) } if (node.type === 'ClassProperty') { - const tokens = getSourceCode(context).getFirstTokens(node, 2); + const tokens = getFirstTokens(context, node, 2); return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; } return ''; diff --git a/lib/util/annotations.js b/lib/util/annotations.js index 60aaef8cd0..24b18074c0 100644 --- a/lib/util/annotations.js +++ b/lib/util/annotations.js @@ -6,6 +6,8 @@ 'use strict'; +const getFirstTokens = require('./eslint').getFirstTokens; + /** * Checks if we are declaring a `props` argument with a flow type annotation. * @param {ASTNode} node The AST node being checked. @@ -19,7 +21,7 @@ function isAnnotatedFunctionPropsDeclaration(node, context) { const typeNode = node.params[0].type === 'AssignmentPattern' ? node.params[0].left : node.params[0]; - const tokens = context.getFirstTokens(typeNode, 2); + const tokens = getFirstTokens(context, typeNode, 2); const isAnnotated = typeNode.typeAnnotation; const isDestructuredProps = typeNode.type === 'ObjectPattern'; const isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props'); diff --git a/lib/util/ast.js b/lib/util/ast.js index 1384a44cee..4d76036d6a 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -7,6 +7,7 @@ const estraverse = require('estraverse'); const eslintUtil = require('./eslint'); +const getFirstTokens = eslintUtil.getFirstTokens; const getSourceCode = eslintUtil.getSourceCode; // const pragmaUtil = require('./pragma'); @@ -287,7 +288,7 @@ function stripQuotes(string) { */ function getKeyValue(context, node) { if (node.type === 'ObjectTypeProperty') { - const tokens = getSourceCode(context).getFirstTokens(node, 2); + const tokens = getFirstTokens(context, node, 2); return (tokens[0].value === '+' || tokens[0].value === '-' ? tokens[1].value : stripQuotes(tokens[0].value) diff --git a/lib/util/eslint.js b/lib/util/eslint.js index ee14d8fcdd..bb56f65e2a 100644 --- a/lib/util/eslint.js +++ b/lib/util/eslint.js @@ -16,8 +16,14 @@ function markVariableAsUsed(name, node, context) { : context.markVariableAsUsed(name); } +function getFirstTokens(context, node, count) { + const sourceCode = getSourceCode(context); + return sourceCode.getFirstTokens ? sourceCode.getFirstTokens(node, count) : context.getFirstTokens(node, count); +} + module.exports = { getAncestors, + getFirstTokens, getSourceCode, markVariableAsUsed, }; diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 854de13f35..dc22ad4964 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -15,6 +15,7 @@ const astUtil = require('./ast'); const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); const eslintUtil = require('./eslint'); +const getFirstTokens = eslintUtil.getFirstTokens; const getSourceCode = eslintUtil.getSourceCode; /** @@ -1112,7 +1113,7 @@ module.exports = function propTypesInstructions(context, components, utils) { */ function isAnnotatedClassPropsDeclaration(node) { if (node && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')) { - const tokens = context.getFirstTokens(node, 2); + const tokens = getFirstTokens(context, node, 2); if ( node.typeAnnotation && ( tokens[0].value === 'props' From d6e9059b01a5220321ddfca8506b7c19cbcbe1a7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 18 May 2024 20:14:55 -0700 Subject: [PATCH 034/174] [Refactor] `context.getSource` -> `sourceCode.getText` --- lib/rules/boolean-prop-naming.js | 9 ++++++--- lib/rules/destructuring-assignment.js | 4 ++-- lib/rules/forbid-elements.js | 6 +++--- lib/rules/forbid-prop-types.js | 4 ++-- lib/rules/function-component-definition.js | 5 ++--- lib/rules/hook-use-state.js | 4 ++-- lib/rules/jsx-curly-brace-presence.js | 14 +++++++------- lib/rules/jsx-curly-newline.js | 11 ++++++----- lib/rules/jsx-fragments.js | 18 ++++++++---------- lib/rules/jsx-handler-names.js | 10 +++++----- lib/rules/jsx-indent-props.js | 4 ++-- lib/rules/jsx-indent.js | 9 ++++++--- lib/rules/jsx-key.js | 4 ++-- lib/rules/jsx-max-props-per-line.js | 9 ++++----- lib/rules/jsx-newline.js | 11 ++++------- lib/rules/jsx-no-comment-textnodes.js | 4 ++-- lib/rules/jsx-no-leaked-render.js | 9 ++++----- lib/rules/jsx-no-literals.js | 4 ++-- lib/rules/jsx-no-useless-fragment.js | 4 ++-- lib/rules/jsx-one-expression-per-line.js | 9 ++++++--- lib/rules/jsx-props-no-multi-spaces.js | 9 ++++++--- lib/rules/jsx-sort-default-props.js | 4 ++-- lib/rules/jsx-sort-props.js | 8 +++++--- lib/rules/jsx-wrap-multilines.js | 15 +++++++++------ lib/rules/no-arrow-function-lifecycle.js | 15 +++++++++------ lib/rules/no-deprecated.js | 4 ++-- lib/rules/no-unknown-property.js | 4 ++-- lib/rules/prefer-exact-props.js | 5 ++--- lib/rules/prefer-stateless-function.js | 4 ++-- lib/rules/sort-default-props.js | 4 ++-- lib/rules/sort-prop-types.js | 7 +++++-- lib/util/Components.js | 5 ++--- lib/util/componentUtil.js | 4 ++-- lib/util/defaultProps.js | 6 ++---- lib/util/eslint.js | 7 +++++++ lib/util/propTypes.js | 5 +++-- lib/util/propTypesSort.js | 9 ++++++--- 37 files changed, 145 insertions(+), 122 deletions(-) diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index 78ed249805..f64da93242 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -13,7 +13,10 @@ const propsUtil = require('../util/props'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -309,7 +312,7 @@ module.exports = { && node.value.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction( context, - getSourceCode(context).getText(node.value.callee) + getText(context, node.value.callee) ) ) { checkPropWrapperArguments(node, node.value.arguments); @@ -335,7 +338,7 @@ module.exports = { right.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction( context, - getSourceCode(context).getText(right.callee) + getText(context, right.callee) ) ) { checkPropWrapperArguments(component.node, right.arguments); diff --git a/lib/rules/destructuring-assignment.js b/lib/rules/destructuring-assignment.js index 40f3a0ac1f..9cac8d5889 100644 --- a/lib/rules/destructuring-assignment.js +++ b/lib/rules/destructuring-assignment.js @@ -10,7 +10,7 @@ const eslintUtil = require('../util/eslint'); const isAssignmentLHS = require('../util/ast').isAssignmentLHS; const report = require('../util/report'); -const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; const DEFAULT_OPTION = 'always'; @@ -272,7 +272,7 @@ module.exports = { param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1], ]; return [ - fixer.replaceTextRange(replaceRange, getSourceCode(context).getText(node.id)), + fixer.replaceTextRange(replaceRange, getText(context, node.id)), fixer.remove(node.parent), ]; }, diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index 8170aaf380..dd3ffa8f5d 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -7,7 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const isCreateElement = require('../util/isCreateElement'); const report = require('../util/report'); @@ -91,7 +91,7 @@ module.exports = { return { JSXOpeningElement(node) { - reportIfForbidden(getSourceCode(context).getText(node.name), node.name); + reportIfForbidden(getText(context, node.name), node.name); }, CallExpression(node) { @@ -111,7 +111,7 @@ module.exports = { } else if (argType === 'Literal' && /^[a-z][^.]*$/.test(argument.value)) { reportIfForbidden(argument.value, argument); } else if (argType === 'MemberExpression') { - reportIfForbidden(getSourceCode(context).getText(argument), argument); + reportIfForbidden(getText(context, argument), argument); } }, }; diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js index 8161d6b45d..89f5c4b2d8 100644 --- a/lib/rules/forbid-prop-types.js +++ b/lib/rules/forbid-prop-types.js @@ -10,7 +10,7 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Constants @@ -172,7 +172,7 @@ module.exports = { case 'CallExpression': { const innerNode = node.arguments && node.arguments[0]; if ( - propWrapperUtil.isPropWrapperFunction(context, getSourceCode(context).getText(node.callee)) + propWrapperUtil.isPropWrapperFunction(context, getText(context, node.callee)) && innerNode ) { checkNode(innerNode); diff --git a/lib/rules/function-component-definition.js b/lib/rules/function-component-definition.js index 9f2c3b6fb2..24ad3f48d7 100644 --- a/lib/rules/function-component-definition.js +++ b/lib/rules/function-component-definition.js @@ -9,7 +9,7 @@ const arrayIncludes = require('array-includes'); const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const reportC = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -182,8 +182,7 @@ module.exports = { ); function getFixer(node, options) { - const sourceCode = getSourceCode(context); - const source = sourceCode.getText(); + const source = getText(context); const typeAnnotation = getTypeAnnotation(node, source); diff --git a/lib/rules/hook-use-state.js b/lib/rules/hook-use-state.js index a0c46e338e..938802d82b 100644 --- a/lib/rules/hook-use-state.js +++ b/lib/rules/hook-use-state.js @@ -9,7 +9,7 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const getMessageData = require('../util/message'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -168,7 +168,7 @@ module.exports = { // Convert useState call to useMemo + arrow function + dependency array fixer.replaceTextRange( node.range, - `${useMemoCode}(() => ${getSourceCode(context).getText(node.arguments[0])}, [])` + `${useMemoCode}(() => ${getText(context, node.arguments[0])}, [])` ), ].filter(Boolean), } diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 64be524499..083a60a0e3 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -11,7 +11,10 @@ const arrayIncludes = require('array-includes'); const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Constants @@ -177,8 +180,7 @@ module.exports = { let textToReplace; if (jsxUtil.isJSX(expression)) { - const sourceCode = getSourceCode(context); - textToReplace = sourceCode.getText(expression); + textToReplace = getText(context, expression); } else { const expressionType = expression && expression.type; const parentType = JSXExpressionNode.parent.type; @@ -189,9 +191,7 @@ module.exports = { : expression.raw.slice(1, -1) }"`; } else if (jsxUtil.isJSX(expression)) { - const sourceCode = getSourceCode(context); - - textToReplace = sourceCode.getText(expression); + textToReplace = getText(context, expression); } else { textToReplace = expressionType === 'TemplateLiteral' ? expression.quasis[0].value.cooked : expression.value; @@ -208,7 +208,7 @@ module.exports = { node: literalNode, fix(fixer) { if (jsxUtil.isJSX(literalNode)) { - return fixer.replaceText(literalNode, `{${getSourceCode(context).getText(literalNode)}}`); + return fixer.replaceText(literalNode, `{${getText(context, literalNode)}}`); } // If a HTML entity name is found, bail out because it can be fixed diff --git a/lib/rules/jsx-curly-newline.js b/lib/rules/jsx-curly-newline.js index f2a4550f71..046ff79582 100644 --- a/lib/rules/jsx-curly-newline.js +++ b/lib/rules/jsx-curly-newline.js @@ -5,9 +5,12 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); const report = require('../util/report'); +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -131,8 +134,7 @@ module.exports = { report(context, messages.unexpectedAfter, 'unexpectedAfter', { node: leftCurly, fix(fixer) { - return sourceCode - .getText() + return getText(context) .slice(leftCurly.range[1], tokenAfterLeftCurly.range[0]) .trim() ? null // If there is a comment between the { and the first element, don't do a fix. @@ -150,8 +152,7 @@ module.exports = { report(context, messages.unexpectedBefore, 'unexpectedBefore', { node: rightCurly, fix(fixer) { - return sourceCode - .getText() + return getText(context) .slice(tokenBeforeRightCurly.range[1], rightCurly.range[0]) .trim() ? null // If there is a comment between the last element and the }, don't do a fix. diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js index c075c47002..770cbd087f 100644 --- a/lib/rules/jsx-fragments.js +++ b/lib/rules/jsx-fragments.js @@ -11,7 +11,7 @@ const variableUtil = require('../util/variable'); const testReactVersion = require('../util/version').testReactVersion; const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -66,36 +66,34 @@ module.exports = { } function getFixerToLong(jsxFragment) { - const sourceCode = getSourceCode(context); if (!jsxFragment.closingFragment || !jsxFragment.openingFragment) { // the old TS parser crashes here // TODO: FIXME: can we fake these two descriptors? return null; } return function fix(fixer) { - let source = sourceCode.getText(); + let source = getText(context); source = replaceNode(source, jsxFragment.closingFragment, closeFragLong); source = replaceNode(source, jsxFragment.openingFragment, openFragLong); - const lengthDiff = openFragLong.length - sourceCode.getText(jsxFragment.openingFragment).length - + closeFragLong.length - sourceCode.getText(jsxFragment.closingFragment).length; + const lengthDiff = openFragLong.length - getText(context, jsxFragment.openingFragment).length + + closeFragLong.length - getText(context, jsxFragment.closingFragment).length; const range = jsxFragment.range; return fixer.replaceTextRange(range, source.slice(range[0], range[1] + lengthDiff)); }; } function getFixerToShort(jsxElement) { - const sourceCode = getSourceCode(context); return function fix(fixer) { - let source = sourceCode.getText(); + let source = getText(context); let lengthDiff; if (jsxElement.closingElement) { source = replaceNode(source, jsxElement.closingElement, closeFragShort); source = replaceNode(source, jsxElement.openingElement, openFragShort); - lengthDiff = sourceCode.getText(jsxElement.openingElement).length - openFragShort.length - + sourceCode.getText(jsxElement.closingElement).length - closeFragShort.length; + lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length + + getText(context, jsxElement.closingElement).length - closeFragShort.length; } else { source = replaceNode(source, jsxElement.openingElement, `${openFragShort}${closeFragShort}`); - lengthDiff = sourceCode.getText(jsxElement.openingElement).length - openFragShort.length + lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length - closeFragShort.length; } diff --git a/lib/rules/jsx-handler-names.js b/lib/rules/jsx-handler-names.js index 8e2227e89f..d839073a52 100644 --- a/lib/rules/jsx-handler-names.js +++ b/lib/rules/jsx-handler-names.js @@ -6,7 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -130,10 +130,10 @@ module.exports = { const propKey = typeof node.name === 'object' ? node.name.name : node.name; const expression = node.value.expression; - const propValue = getSourceCode(context) - .getText(checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression) - .replace(/\s*/g, '') - .replace(/^this\.|.*::/, ''); + const propValue = getText( + context, + checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression + ).replace(/\s*/g, '').replace(/^this\.|.*::/, ''); if (propKey === 'ref') { return; diff --git a/lib/rules/jsx-indent-props.js b/lib/rules/jsx-indent-props.js index 7ba02450dd..050e721df3 100644 --- a/lib/rules/jsx-indent-props.js +++ b/lib/rules/jsx-indent-props.js @@ -32,7 +32,7 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const reportC = require('../util/report'); // ------------------------------------------------------------------------------ @@ -141,7 +141,7 @@ module.exports = { * @return {Number} Indent */ function getNodeIndent(node) { - let src = getSourceCode(context).getText(node, node.loc.start.column + extraColumnStart); + let src = getText(context, node, node.loc.start.column + extraColumnStart); const lines = src.split('\n'); src = lines[0]; diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index aeb5c38aa4..27397172c8 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -36,7 +36,10 @@ const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const reportC = require('../util/report'); const jsxUtil = require('../util/jsx'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -117,7 +120,7 @@ module.exports = { } if (node.type === 'ReturnStatement') { - const raw = getSourceCode(context).getText(node); + const raw = getText(context, node); const lines = raw.split('\n'); if (lines.length > 1) { return function fix(fixer) { @@ -169,7 +172,7 @@ module.exports = { * @return {Number} Indent */ function getNodeIndent(node, byLastLine, excludeCommas) { - let src = getSourceCode(context).getText(node, node.loc.start.column + extraColumnStart); + let src = getText(context, node, node.loc.start.column + extraColumnStart); const lines = src.split('\n'); if (byLastLine) { src = lines[lines.length - 1]; diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index 70c745daf1..feee7ad5a0 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -12,7 +12,7 @@ const docsUrl = require('../util/docsUrl'); const pragmaUtil = require('../util/pragma'); const report = require('../util/report'); const astUtil = require('../util/ast'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -214,7 +214,7 @@ module.exports = { } } else { keys.forEach((attr) => { - const value = getSourceCode(context).getText(attr.value); + const value = getText(context, attr.value); if (!map[value]) { map[value] = []; } map[value].push(attr); diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js index 3f609ca1cf..eebf7f0f33 100644 --- a/lib/rules/jsx-max-props-per-line.js +++ b/lib/rules/jsx-max-props-per-line.js @@ -6,12 +6,12 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const report = require('../util/report'); function getPropName(context, propNode) { if (propNode.type === 'JSXSpreadAttribute') { - return getSourceCode(context).getText(propNode.argument); + return getText(context, propNode.argument); } return propNode.name.name; } @@ -88,7 +88,6 @@ module.exports = { }; function generateFixFunction(line, max) { - const sourceCode = getSourceCode(context); const output = []; const front = line[0].range[0]; const back = line[line.length - 1].range[1]; @@ -97,9 +96,9 @@ module.exports = { const nodes = line.slice(i, i + max); output.push(nodes.reduce((prev, curr) => { if (prev === '') { - return sourceCode.getText(curr); + return getText(context, curr); } - return `${prev} ${sourceCode.getText(curr)}`; + return `${prev} ${getText(context, curr)}`; }, '')); } diff --git a/lib/rules/jsx-newline.js b/lib/rules/jsx-newline.js index 1b87383bd6..7fd395c72f 100644 --- a/lib/rules/jsx-newline.js +++ b/lib/rules/jsx-newline.js @@ -7,7 +7,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -72,10 +72,9 @@ module.exports = { }, create(context) { const jsxElementParents = new Set(); - const sourceCode = getSourceCode(context); function isBlockCommentInCurlyBraces(element) { - const elementRawValue = sourceCode.getText(element); + const elementRawValue = getText(context, element); return /^\s*{\/\*/.test(elementRawValue); } @@ -123,8 +122,7 @@ module.exports = { fix(fixer) { return fixer.replaceText( firstAdjacentSibling, - sourceCode.getText(firstAdjacentSibling) - .replace(regex, replacement) + getText(context, firstAdjacentSibling).replace(regex, replacement) ); }, }); @@ -152,8 +150,7 @@ module.exports = { return fixer.replaceText( firstAdjacentSibling, // double or remove the last newline - sourceCode.getText(firstAdjacentSibling) - .replace(regex, replacement) + getText(context, firstAdjacentSibling).replace(regex, replacement) ); }, }); diff --git a/lib/rules/jsx-no-comment-textnodes.js b/lib/rules/jsx-no-comment-textnodes.js index 5d7139c211..de4d288c37 100644 --- a/lib/rules/jsx-no-comment-textnodes.js +++ b/lib/rules/jsx-no-comment-textnodes.js @@ -6,7 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const report = require('../util/report'); // ------------------------------------------------------------------------------ @@ -24,7 +24,7 @@ const messages = { */ function checkText(context, node) { // since babel-eslint has the wrong node.raw, we'll get the source text - const rawValue = getSourceCode(context).getText(node); + const rawValue = getText(context, node); if (/^\s*\/(\/|\*)/m.test(rawValue)) { // inside component, e.g.
literal
if ( diff --git a/lib/rules/jsx-no-leaked-render.js b/lib/rules/jsx-no-leaked-render.js index 9833996c14..2cbe705561 100644 --- a/lib/rules/jsx-no-leaked-render.js +++ b/lib/rules/jsx-no-leaked-render.js @@ -8,7 +8,7 @@ const find = require('es-iterator-helpers/Iterator.prototype.find'); const from = require('es-iterator-helpers/Iterator.from'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const variableUtil = require('../util/variable'); @@ -56,13 +56,12 @@ function extractExpressionBetweenLogicalAnds(node) { } function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) { - const sourceCode = getSourceCode(context); - const rightSideText = sourceCode.getText(rightNode); + const rightSideText = getText(context, rightNode); if (fixStrategy === COERCE_STRATEGY) { const expressions = extractExpressionBetweenLogicalAnds(leftNode); const newText = expressions.map((node) => { - let nodeText = sourceCode.getText(node); + let nodeText = getText(context, node); if (isParenthesized(context, node)) { nodeText = `(${nodeText})`; } @@ -100,7 +99,7 @@ function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNod } if (fixStrategy === TERNARY_STRATEGY) { - let leftSideText = sourceCode.getText(trimLeftNode(leftNode)); + let leftSideText = getText(context, trimLeftNode(leftNode)); if (isParenthesized(context, leftNode)) { leftSideText = `(${leftSideText})`; } diff --git a/lib/rules/jsx-no-literals.js b/lib/rules/jsx-no-literals.js index 3fa8ab03d5..d7f43ab05c 100644 --- a/lib/rules/jsx-no-literals.js +++ b/lib/rules/jsx-no-literals.js @@ -11,7 +11,7 @@ const map = require('es-iterator-helpers/Iterator.prototype.map'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -150,7 +150,7 @@ module.exports = { report(context, messages[messageId], messageId, { node, data: { - text: getSourceCode(context).getText(node).trim(), + text: getText(context, node).trim(), }, }); } diff --git a/lib/rules/jsx-no-useless-fragment.js b/lib/rules/jsx-no-useless-fragment.js index 11516c5a22..4c2886469f 100644 --- a/lib/rules/jsx-no-useless-fragment.js +++ b/lib/rules/jsx-no-useless-fragment.js @@ -10,7 +10,7 @@ const pragmaUtil = require('../util/pragma'); const jsxUtil = require('../util/jsx'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; function isJSXText(node) { return !!node && (node.type === 'JSXText' || node.type === 'Literal'); @@ -217,7 +217,7 @@ module.exports = { const opener = node.type === 'JSXFragment' ? node.openingFragment : node.openingElement; const closer = node.type === 'JSXFragment' ? node.closingFragment : node.closingElement; - const childrenText = opener.selfClosing ? '' : getSourceCode(context).getText().slice(opener.range[1], closer.range[0]); + const childrenText = opener.selfClosing ? '' : getText(context).slice(opener.range[1], closer.range[0]); return fixer.replaceText(node, trimLikeReact(childrenText)); }; diff --git a/lib/rules/jsx-one-expression-per-line.js b/lib/rules/jsx-one-expression-per-line.js index 3e2fe4c0da..3ae61e3d96 100644 --- a/lib/rules/jsx-one-expression-per-line.js +++ b/lib/rules/jsx-one-expression-per-line.js @@ -6,10 +6,13 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -61,7 +64,7 @@ module.exports = { * @returns {string} */ function nodeDescriptor(n) { - return n.openingElement ? n.openingElement.name.name : getSourceCode(context).getText(n).replace(/\n/g, ''); + return n.openingElement ? n.openingElement.name.name : getText(context, n).replace(/\n/g, ''); } function handleJSX(node) { @@ -181,7 +184,7 @@ module.exports = { return; } - const source = getSourceCode(context).getText(child); + const source = getText(context, child); const leadingSpace = !!(prevChild && spaceBetweenPrev()); const trailingSpace = !!(nextChild && spaceBetweenNext()); const leadingNewLine = !!prevChild; diff --git a/lib/rules/jsx-props-no-multi-spaces.js b/lib/rules/jsx-props-no-multi-spaces.js index a3a8acaa59..ce80338cd8 100644 --- a/lib/rules/jsx-props-no-multi-spaces.js +++ b/lib/rules/jsx-props-no-multi-spaces.js @@ -6,9 +6,12 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); const report = require('../util/report'); +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -40,7 +43,7 @@ module.exports = { function getPropName(propNode) { switch (propNode.type) { case 'JSXSpreadAttribute': - return sourceCode.getText(propNode.argument); + return getText(context, propNode.argument); case 'JSXIdentifier': return propNode.name; case 'JSXMemberExpression': @@ -48,7 +51,7 @@ module.exports = { default: return propNode.name ? propNode.name.name - : `${sourceCode.getText(propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser + : `${getText(context, propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser } } diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index 3fd4c70b41..f65981ec64 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -13,7 +13,7 @@ const log = require('../util/log'); const eslintUtil = require('../util/eslint'); const getFirstTokens = eslintUtil.getFirstTokens; -const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; let isWarnedForDeprecation = false; @@ -86,7 +86,7 @@ module.exports = { } function getKey(node) { - return getSourceCode(context).getText(node.key || node.argument); + return getText(context, node.key || node.argument); } /** diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js index fc8565191f..f811e16cf7 100644 --- a/lib/rules/jsx-sort-props.js +++ b/lib/rules/jsx-sort-props.js @@ -12,7 +12,10 @@ const toSorted = require('array.prototype.tosorted'); const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getText = eslintUtil.getText; +const getSourceCode = eslintUtil.getSourceCode; // ------------------------------------------------------------------------------ // Rule Definition @@ -213,7 +216,6 @@ function getGroupsOfSortableAttributes(attributes, context) { } function generateFixerFunction(node, context, reservedList) { - const sourceCode = getSourceCode(context); const attributes = node.attributes.slice(0); const configuration = context.options[0] || {}; const ignoreCase = configuration.ignoreCase || false; @@ -246,7 +248,7 @@ function generateFixerFunction(node, context, reservedList) { return function fixFunction(fixer) { const fixers = []; - let source = sourceCode.getText(); + let source = getText(context); sortableAttributeGroups.forEach((sortableGroup, ii) => { sortableGroup.forEach((attr, jj) => { diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index c2da2a7882..17a9812465 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -7,11 +7,14 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); const jsxUtil = require('../util/jsx'); const reportC = require('../util/report'); const isParenthesized = require('../util/ast').isParenthesized; +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + // ------------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------------ @@ -148,7 +151,7 @@ module.exports = { const option = getOption(type); if ((option === true || option === 'parens') && !isParenthesized(context, node) && isMultilines(node)) { - report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(${sourceCode.getText(node)})`)); + report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(${getText(context, node)})`)); } if (option === 'parens-new-line' && isMultilines(node)) { @@ -163,18 +166,18 @@ module.exports = { 'missingParens', (fixer) => fixer.replaceTextRange( [tokenBefore.range[0], tokenAfter && (tokenAfter.value === ';' || tokenAfter.value === '}') ? tokenAfter.range[0] : node.range[1]], - `${trimTokenBeforeNewline(node, tokenBefore)}(\n${start.column > 0 ? ' '.repeat(start.column) : ''}${sourceCode.getText(node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})` + `${trimTokenBeforeNewline(node, tokenBefore)}(\n${start.column > 0 ? ' '.repeat(start.column) : ''}${getText(context, node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})` ) ); } else { - report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(\n${sourceCode.getText(node)}\n)`)); + report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(\n${getText(context, node)}\n)`)); } } else { const needsOpening = needsOpeningNewLine(node); const needsClosing = needsClosingNewLine(node); if (needsOpening || needsClosing) { report(node, 'parensOnNewLines', (fixer) => { - const text = sourceCode.getText(node); + const text = getText(context, node); let fixed = text; if (needsOpening) { fixed = `\n${fixed}`; @@ -193,7 +196,7 @@ module.exports = { const tokenAfter = sourceCode.getTokenAfter(node); report(node, 'extraParens', (fixer) => fixer.replaceTextRange( [tokenBefore.range[0], tokenAfter.range[1]], - sourceCode.getText(node) + getText(context, node) )); } } diff --git a/lib/rules/no-arrow-function-lifecycle.js b/lib/rules/no-arrow-function-lifecycle.js index 71c8389522..e56cb78601 100644 --- a/lib/rules/no-arrow-function-lifecycle.js +++ b/lib/rules/no-arrow-function-lifecycle.js @@ -13,9 +13,12 @@ const componentUtil = require('../util/componentUtil'); const docsUrl = require('../util/docsUrl'); const lifecycleMethods = require('../util/lifecycleMethods'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); -function getText(node) { +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +function getRuleText(node) { const params = node.value.params.map((p) => p.name); if (node.type === 'Property') { @@ -104,7 +107,7 @@ module.exports = { node.key.range[1], (previousComment.length > 0 ? previousComment[0] : body).range[0], ]; - const hasSemi = node.value.expression && sourceCode.getText(node).slice(node.value.range[1] - node.range[0]) === ';'; + const hasSemi = node.value.expression && getText(context, node).slice(node.value.range[1] - node.range[0]) === ';'; report( context, @@ -118,13 +121,13 @@ module.exports = { fix(fixer) { if (!sourceCode.getCommentsAfter) { // eslint 3.x - return isBlockBody && fixer.replaceTextRange(headRange, getText(node)); + return isBlockBody && fixer.replaceTextRange(headRange, getRuleText(node)); } return [].concat( - fixer.replaceTextRange(headRange, getText(node)), + fixer.replaceTextRange(headRange, getRuleText(node)), isBlockBody ? [] : fixer.replaceTextRange( [bodyRange[0], bodyRange[1] + (hasSemi ? 1 : 0)], - `{ return ${previousComment.map((x) => sourceCode.getText(x)).join('')}${sourceCode.getText(body)}${nextComment.map((x) => sourceCode.getText(x)).join('')}; }` + `{ return ${previousComment.map((x) => getText(context, x)).join('')}${getText(context, body)}${nextComment.map((x) => getText(context, x)).join('')}; }` ) ); }, diff --git a/lib/rules/no-deprecated.js b/lib/rules/no-deprecated.js index 1541942b86..462bd4c756 100644 --- a/lib/rules/no-deprecated.js +++ b/lib/rules/no-deprecated.js @@ -14,7 +14,7 @@ const docsUrl = require('../util/docsUrl'); const pragmaUtil = require('../util/pragma'); const testReactVersion = require('../util/version').testReactVersion; const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ------------------------------------------------------------------------------ // Constants @@ -218,7 +218,7 @@ module.exports = { return { MemberExpression(node) { - checkDeprecation(node, getSourceCode(context).getText(node)); + checkDeprecation(node, getText(context, node)); }, ImportDeclaration(node) { diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index c8071c4a43..3e8c6de111 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -7,7 +7,7 @@ const has = require('object.hasown/polyfill')(); const docsUrl = require('../util/docsUrl'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; const testReactVersion = require('../util/version').testReactVersion; const report = require('../util/report'); @@ -556,7 +556,7 @@ module.exports = { return { JSXAttribute(node) { const ignoreNames = getIgnoreConfig(); - const actualName = getSourceCode(context).getText(node.name); + const actualName = getText(context, node.name); if (ignoreNames.indexOf(actualName) >= 0) { return; } diff --git a/lib/rules/prefer-exact-props.js b/lib/rules/prefer-exact-props.js index d9970115f6..0bd4dd8b06 100644 --- a/lib/rules/prefer-exact-props.js +++ b/lib/rules/prefer-exact-props.js @@ -10,7 +10,7 @@ const propsUtil = require('../util/props'); const propWrapperUtil = require('../util/propWrapper'); const variableUtil = require('../util/variable'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const getText = require('../util/eslint').getText; // ----------------------------------------------------------------------------- // Rule Definition @@ -37,7 +37,6 @@ module.exports = { create: Components.detect((context, components, utils) => { const typeAliases = {}; const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context); - const sourceCode = getSourceCode(context); function getPropTypesErrorMessage() { const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers); @@ -84,7 +83,7 @@ module.exports = { return ( node && node.type === 'CallExpression' - && !propWrapperUtil.isExactPropWrapperFunction(context, sourceCode.getText(node.callee)) + && !propWrapperUtil.isExactPropWrapperFunction(context, getText(context, node.callee)) ); } diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index c2477a8dd5..35e471ff9c 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -17,7 +17,7 @@ const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); const eslintUtil = require('../util/eslint'); -const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -335,7 +335,7 @@ module.exports = { // Mark `ref` usage JSXAttribute(node) { - const name = getSourceCode(context).getText(node.name); + const name = getText(context, node.name); if (name !== 'ref') { return; } diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js index d46ef66f86..7f7243f6b4 100644 --- a/lib/rules/sort-default-props.js +++ b/lib/rules/sort-default-props.js @@ -12,7 +12,7 @@ const report = require('../util/report'); const eslintUtil = require('../util/eslint'); const getFirstTokens = eslintUtil.getFirstTokens; -const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -81,7 +81,7 @@ module.exports = { } function getKey(node) { - return getSourceCode(context).getText(node.key || node.argument); + return getText(context, node.key || node.argument); } /** diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 2a9d307bf9..5d44bca744 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -10,7 +10,10 @@ const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); const propTypesSortUtil = require('../util/propTypesSort'); const report = require('../util/report'); -const getSourceCode = require('../util/eslint').getSourceCode; +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // ------------------------------------------------------------------------------ // Rule Definition @@ -29,7 +32,7 @@ function getKey(context, node) { if (node.key && node.key.value) { return node.key.value; } - return getSourceCode(context).getText(node.key || node.argument); + return getText(context, node.key || node.argument); } function getValueName(node) { diff --git a/lib/util/Components.js b/lib/util/Components.js index a927c44e5c..e1803b26e3 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -23,7 +23,7 @@ const isFirstLetterCapitalized = require('./isFirstLetterCapitalized'); const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport'); const eslintUtil = require('./eslint'); -const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; function getId(node) { return node ? `${node.range[0]}:${node.range[1]}` : ''; @@ -285,7 +285,6 @@ function mergeRules(rules) { function componentRule(rule, context) { const pragma = pragmaUtil.getFromContext(context); - const sourceCode = getSourceCode(context); const components = new Components(); const wrapperFunctions = getWrapperFunctions(context, pragma); @@ -684,7 +683,7 @@ function componentRule(rule, context) { if (refId.parent && refId.parent.type === 'MemberExpression') { refId = refId.parent; } - if (sourceCode.getText(refId) !== componentName) { + if (getText(context, refId) !== componentName) { return false; } if (refId.type === 'MemberExpression') { diff --git a/lib/util/componentUtil.js b/lib/util/componentUtil.js index 29720c124f..17249c33f5 100644 --- a/lib/util/componentUtil.js +++ b/lib/util/componentUtil.js @@ -5,6 +5,7 @@ const pragmaUtil = require('./pragma'); const eslintUtil = require('./eslint'); const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; // eslint-disable-next-line valid-jsdoc /** @@ -159,9 +160,8 @@ function getParentES6Component(context) { */ function isPureComponent(node, context) { const pragma = getPragma(context); - const sourceCode = getSourceCode(context); if (node.superClass) { - return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(sourceCode.getText(node.superClass)); + return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(getText(context, node.superClass)); } return false; } diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js index c747030ba3..7cfdc92d52 100644 --- a/lib/util/defaultProps.js +++ b/lib/util/defaultProps.js @@ -10,13 +10,11 @@ const componentUtil = require('./componentUtil'); const propsUtil = require('./props'); const variableUtil = require('./variable'); const propWrapperUtil = require('./propWrapper'); -const getSourceCode = require('./eslint').getSourceCode; +const getText = require('./eslint').getText; const QUOTES_REGEX = /^["']|["']$/g; module.exports = function defaultPropsInstructions(context, components, utils) { - const sourceCode = getSourceCode(context); - /** * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not * an Identifier, then the node is simply returned. @@ -52,7 +50,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) { } return objectExpression.properties.map((defaultProp) => ({ - name: sourceCode.getText(defaultProp.key).replace(QUOTES_REGEX, ''), + name: getText(context, defaultProp.key).replace(QUOTES_REGEX, ''), node: defaultProp, })); } diff --git a/lib/util/eslint.js b/lib/util/eslint.js index bb56f65e2a..edecd9b317 100644 --- a/lib/util/eslint.js +++ b/lib/util/eslint.js @@ -21,9 +21,16 @@ function getFirstTokens(context, node, count) { return sourceCode.getFirstTokens ? sourceCode.getFirstTokens(node, count) : context.getFirstTokens(node, count); } +function getText(context) { + const sourceCode = getSourceCode(context); + const args = Array.prototype.slice.call(arguments, 1); + return sourceCode.getText ? sourceCode.getText.apply(sourceCode, args) : context.getSource.apply(context, args); +} + module.exports = { getAncestors, getFirstTokens, getSourceCode, + getText, markVariableAsUsed, }; diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index dc22ad4964..1fb81e9942 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -17,6 +17,7 @@ const eslintUtil = require('./eslint'); const getFirstTokens = eslintUtil.getFirstTokens; const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; /** * Check if node is function type. @@ -907,7 +908,7 @@ module.exports = function propTypesInstructions(context, components, utils) { ignorePropsValidation = true; break; } - const parentProp = context.getSource(propTypes.parent.left.object).replace(/^.*\.propTypes\./, ''); + const parentProp = getText(context, propTypes.parent.left.object).replace(/^.*\.propTypes\./, ''); const types = buildReactDeclarationTypes( propTypes.parent.right, parentProp @@ -953,7 +954,7 @@ module.exports = function propTypesInstructions(context, components, utils) { if ( propWrapperUtil.isPropWrapperFunction( context, - getSourceCode(context).getText(propTypes.callee) + getText(context, propTypes.callee) ) && propTypes.arguments && propTypes.arguments[0] ) { diff --git a/lib/util/propTypesSort.js b/lib/util/propTypesSort.js index 1284b163ca..12982430b3 100644 --- a/lib/util/propTypesSort.js +++ b/lib/util/propTypesSort.js @@ -7,7 +7,10 @@ const toSorted = require('array.prototype.tosorted'); const astUtil = require('./ast'); -const getSourceCode = require('./eslint').getSourceCode; +const eslintUtil = require('./eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; /** * Returns the value name of a node. @@ -179,9 +182,9 @@ function fixPropTypesSort( (a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) ); + const sourceCodeText = getText(context); source = nodes.reduceRight((acc, attr, index) => { const sortedAttr = sortedAttributes[index]; - const sourceCodeText = sourceCode.getText(); const commentNode = commentnodeMap.get(sortedAttr); let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end); if (sortShapeProp && isShapeProp(sortedAttr.value)) { @@ -200,7 +203,7 @@ function fixPropTypesSort( return source; } - const source = sortInSource(declarations, getSourceCode(context).getText()); + const source = sortInSource(declarations, getText(context)); const rangeStart = commentnodeMap.get(declarations[0]).start; const rangeEnd = commentnodeMap.get(declarations[declarations.length - 1]).end; 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 035/174] [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; From 6c4b6fbecbfc146911467eb5630ed25f9e29dab5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 20 May 2024 21:18:22 -0700 Subject: [PATCH 036/174] [meta] consistently indent .eslintrc with tabs --- .eslintrc | 158 +++++++++++++++++++++++++++--------------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4991f200f2..d10247d296 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,82 +1,82 @@ { - "root": true, - "extends": ["airbnb-base", "plugin:eslint-plugin/recommended"], - "plugins": ["eslint-plugin"], - "env": { - "es6": true, - "node": true - }, - "parserOptions": { - "ecmaVersion": 6, - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "script", - }, - "ignorePatterns": [ - "coverage/", - ".nyc_output/", - ], - "rules": { - "comma-dangle": [2, "always-multiline"], - "object-shorthand": [2, "always", { - "ignoreConstructors": false, - "avoidQuotes": false, // this is the override vs airbnb - }], - "max-len": [2, 120, { - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - }], - "consistent-return": 0, + "root": true, + "extends": ["airbnb-base", "plugin:eslint-plugin/recommended"], + "plugins": ["eslint-plugin"], + "env": { + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 6, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "script", + }, + "ignorePatterns": [ + "coverage/", + ".nyc_output/", + ], + "rules": { + "comma-dangle": [2, "always-multiline"], + "object-shorthand": [2, "always", { + "ignoreConstructors": false, + "avoidQuotes": false, // this is the override vs airbnb + }], + "max-len": [2, 120, { + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreComments": true, + }], + "consistent-return": 0, - "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }], - "prefer-object-spread": 0, // until node 8 is required - "prefer-rest-params": 0, // until node 6 is required - "prefer-spread": 0, // until node 6 is required - "function-call-argument-newline": 1, // TODO: enable - "function-paren-newline": 0, - "no-plusplus": [2, {"allowForLoopAfterthoughts": true}], - "no-param-reassign": 1, - "no-restricted-syntax": [2, { - "selector": "ObjectPattern", - "message": "Object destructuring is not compatible with Node v4" - }], - "strict": [2, "safe"], - "valid-jsdoc": [2, { - "requireReturn": false, - "requireParamDescription": false, - "requireReturnDescription": false, - }], + "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }], + "prefer-object-spread": 0, // until node 8 is required + "prefer-rest-params": 0, // until node 6 is required + "prefer-spread": 0, // until node 6 is required + "function-call-argument-newline": 1, // TODO: enable + "function-paren-newline": 0, + "no-plusplus": [2, {"allowForLoopAfterthoughts": true}], + "no-param-reassign": 1, + "no-restricted-syntax": [2, { + "selector": "ObjectPattern", + "message": "Object destructuring is not compatible with Node v4" + }], + "strict": [2, "safe"], + "valid-jsdoc": [2, { + "requireReturn": false, + "requireParamDescription": false, + "requireReturnDescription": false, + }], - "eslint-plugin/consistent-output": 0, - "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }], - "eslint-plugin/require-meta-schema": 0, - "eslint-plugin/require-meta-type": 0 - }, - "overrides": [ - { - "files": "tests/**", - "rules": { - "no-template-curly-in-string": 1, - }, - }, - { - "files": "markdown.config.js", - "rules": { - "no-console": 0, - }, - }, - { - "files": ".github/workflows/*.js", - "parserOptions": { - "ecmaVersion": 2019, - }, - "rules": { - "camelcase": 0, - "no-console": 0, - "no-restricted-syntax": 0, - }, - }, - ], -} + "eslint-plugin/consistent-output": 0, + "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }], + "eslint-plugin/require-meta-schema": 0, + "eslint-plugin/require-meta-type": 0 + }, + "overrides": [ + { + "files": "tests/**", + "rules": { + "no-template-curly-in-string": 1, + }, + }, + { + "files": "markdown.config.js", + "rules": { + "no-console": 0, + }, + }, + { + "files": ".github/workflows/*.js", + "parserOptions": { + "ecmaVersion": 2019, + }, + "rules": { + "camelcase": 0, + "no-console": 0, + "no-restricted-syntax": 0, + }, + }, + ], + } From 3533e26c4edd2b624a368fe1303aca8acd2ca095 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 20 May 2024 07:09:50 +0200 Subject: [PATCH 037/174] [Refactor] `jsx-no-constructed-context-values`: set `schema: false` --- lib/rules/jsx-no-constructed-context-values.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/jsx-no-constructed-context-values.js b/lib/rules/jsx-no-constructed-context-values.js index 140bcde42c..2e9ef93422 100644 --- a/lib/rules/jsx-no-constructed-context-values.js +++ b/lib/rules/jsx-no-constructed-context-values.js @@ -140,7 +140,7 @@ module.exports = { url: docsUrl('jsx-no-constructed-context-values'), }, messages, - schema: {}, + schema: false, }, // eslint-disable-next-line arrow-body-style From fbb1e0f082cc61e3dedeb5260ee58e54f9f94b14 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 20 May 2024 07:40:22 +0200 Subject: [PATCH 038/174] [Tests] `jsx-no-useless-fragment`, `jsx-fragments`, `function-component-definition`: use `output: null` to assert no autofix --- .../rules/function-component-definition.js | 26 +++---------------- tests/lib/rules/jsx-fragments.js | 2 +- tests/lib/rules/jsx-no-useless-fragment.js | 2 +- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/tests/lib/rules/function-component-definition.js b/tests/lib/rules/function-component-definition.js index 8f0e96ee17..f5282bf6f7 100644 --- a/tests/lib/rules/function-component-definition.js +++ b/tests/lib/rules/function-component-definition.js @@ -807,11 +807,7 @@ ruleTester.run('function-component-definition', rule, { return
; } `, - output: ` - var Hello: React.FC = function(props) { - return
; - } - `, + output: null, options: [{ namedComponents: 'function-declaration' }], errors: [{ messageId: 'function-declaration' }], features: ['types'], @@ -822,11 +818,7 @@ ruleTester.run('function-component-definition', rule, { return
; }; `, - output: ` - var Hello: React.FC = (props) => { - return
; - }; - `, + output: null, options: [{ namedComponents: 'function-declaration' }], errors: [{ messageId: 'function-declaration' }], features: ['types'], @@ -852,11 +844,7 @@ ruleTester.run('function-component-definition', rule, { return
; } `, - output: ` - function Hello(props: Test) { - return
; - } - `, + output: null, options: [{ namedComponents: 'arrow-function' }], errors: [{ messageId: 'arrow-function' }], features: ['types'], @@ -963,13 +951,7 @@ ruleTester.run('function-component-definition', rule, { } } `, - output: ` - function wrap(Component) { - return function(props) { - return
- } - } - `, + output: null, errors: [{ messageId: 'arrow-function' }], options: [{ unnamedComponents: 'arrow-function' }], features: ['types'], diff --git a/tests/lib/rules/jsx-fragments.js b/tests/lib/rules/jsx-fragments.js index 1ee6fb3fa2..2807a15f75 100644 --- a/tests/lib/rules/jsx-fragments.js +++ b/tests/lib/rules/jsx-fragments.js @@ -142,7 +142,7 @@ ruleTester.run('jsx-fragments', rule, { }, { code: '<>', - output: '<>', // should get '', but the old TS parser lacks opening/closing Fragment info + output: null, // should get '', but the old TS parser lacks opening/closing Fragment info features: ['fragment', 'no-babel', 'ts', 'no-ts-new'], options: ['element'], settings, diff --git a/tests/lib/rules/jsx-no-useless-fragment.js b/tests/lib/rules/jsx-no-useless-fragment.js index 22594aa6bf..d1b599a8b2 100644 --- a/tests/lib/rules/jsx-no-useless-fragment.js +++ b/tests/lib/rules/jsx-no-useless-fragment.js @@ -194,7 +194,7 @@ ruleTester.run('jsx-no-useless-fragment', rule, { }, { code: '
<>{"a"}{"b"}
', - output: '
<>{"a"}{"b"}
', + output: null, errors: [{ messageId: 'ChildOfHtmlElement', type: 'JSXFragment' }], features: ['fragment', 'ts-old', 'no-ts-new', 'no-babel', 'no-default'], }, From 23b0a4986806e206f55c0448ddece005c8e55f29 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 20 May 2024 14:11:29 +0200 Subject: [PATCH 039/174] [Fix] `jsx-closing-bracket-location`: message shows `{{details}}` when there are no details --- CHANGELOG.md | 2 + lib/rules/jsx-closing-bracket-location.js | 5 +- .../lib/rules/jsx-closing-bracket-location.js | 65 +++++++++++++++---- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6971a072f..56a0c34334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi) * [`jsx-no-leaked-render`]: invalid report if left side is boolean ([#3746][] @akulsr0) +* [`jsx-closing-bracket-location`]: message shows `{{details}}` when there are no details ([#3759][] @mdjermanovic) +[#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759 [#3746]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3746 [#3718]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3718 diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index 9b79223088..26556d6117 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -267,7 +267,10 @@ module.exports = { return; } - const data = { location: MESSAGE_LOCATION[expectedLocation] }; + const data = { + location: MESSAGE_LOCATION[expectedLocation], + details: '', + }; const correctColumn = getCorrectColumn(tokens, expectedLocation); if (correctColumn !== null) { diff --git a/tests/lib/rules/jsx-closing-bracket-location.js b/tests/lib/rules/jsx-closing-bracket-location.js index c23ec1943a..120f1e3638 100644 --- a/tests/lib/rules/jsx-closing-bracket-location.js +++ b/tests/lib/rules/jsx-closing-bracket-location.js @@ -419,7 +419,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_TAG }, + data: { + location: MESSAGE_AFTER_TAG, + details: '', + }, }, ], }, @@ -434,7 +437,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -449,7 +455,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -536,7 +545,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -578,7 +590,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -644,7 +659,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -686,7 +704,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -1098,7 +1119,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_TAG }, + data: { + location: MESSAGE_AFTER_TAG, + details: '', + }, }, ], }, @@ -1216,7 +1240,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -1258,7 +1285,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -1324,7 +1354,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -1366,7 +1399,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_PROPS }, + data: { + location: MESSAGE_AFTER_PROPS, + details: '', + }, }, ], }, @@ -1778,7 +1814,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { errors: [ { messageId: 'bracketLocation', - data: { location: MESSAGE_AFTER_TAG }, + data: { + location: MESSAGE_AFTER_TAG, + details: '', + }, }, ], }, From 014ee05f3897689b5c7a45f4318c6902ea724c27 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 20 May 2024 23:56:18 +0200 Subject: [PATCH 040/174] [Fix] `no-invalid-html-attribute`: ensure error messages are correct --- CHANGELOG.md | 1 + lib/rules/no-invalid-html-attribute.js | 120 +++++++++++++------ tests/lib/rules/no-invalid-html-attribute.js | 117 ++++++++++++++++-- 3 files changed, 193 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a0c34334..a2c437523f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi) * [`jsx-no-leaked-render`]: invalid report if left side is boolean ([#3746][] @akulsr0) * [`jsx-closing-bracket-location`]: message shows `{{details}}` when there are no details ([#3759][] @mdjermanovic) +* [`no-invalid-html-attribute`]: ensure error messages are correct ([#3759][] @mdjermanovic, @ljharb) [#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759 [#3746]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3746 diff --git a/lib/rules/no-invalid-html-attribute.js b/lib/rules/no-invalid-html-attribute.js index 1dd4d0f44b..98c33fbc3f 100644 --- a/lib/rules/no-invalid-html-attribute.js +++ b/lib/rules/no-invalid-html-attribute.js @@ -238,8 +238,8 @@ const messages = { suggestRemoveDefault: '"remove {{attributeName}}"', suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"', suggestRemoveInvalid: '“remove invalid attribute {{reportingValue}}”', - suggestRemoveWhitespaces: 'remove whitespaces in “{{reportingValue}}”', - suggestRemoveNonString: 'remove non-string value in “{{reportingValue}}”', + suggestRemoveWhitespaces: 'remove whitespaces in “{{attributeName}}”', + suggestRemoveNonString: 'remove non-string value in “{{attributeName}}”', }; function splitIntoRangedParts(node, regex) { @@ -259,13 +259,18 @@ function splitIntoRangedParts(node, regex) { function checkLiteralValueNode(context, attributeName, node, parentNode, parentNodeName) { if (typeof node.value !== 'string') { + const data = { attributeName, reportingValue: node.value }; + report(context, messages.onlyStrings, 'onlyStrings', { node, - data: { attributeName }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString), - { fix(fixer) { return fixer.remove(parentNode); } } + { + data, + fix(fixer) { return fixer.remove(parentNode); }, + } ), ], }); @@ -273,13 +278,18 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN } if (!node.value.trim()) { + const data = { attributeName, reportingValue: node.value }; + report(context, messages.noEmpty, 'noEmpty', { node, - data: { attributeName }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), - { fix(fixer) { return fixer.remove(node.parent); } } + { + data, + fix(fixer) { return fixer.remove(node.parent); }, + } ), ], }); @@ -291,32 +301,44 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN const allowedTags = VALID_VALUES.get(attributeName).get(singlePart.value); const reportingValue = singlePart.reportingValue; - const suggest = [ - Object.assign( - getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), - { fix(fixer) { return fixer.removeRange(singlePart.range); } } - ), - ]; - if (!allowedTags) { const data = { attributeName, reportingValue, }; + report(context, messages.neverValid, 'neverValid', { node, data, - suggest, + suggest: [ + Object.assign( + getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), + { + data, + fix(fixer) { return fixer.removeRange(singlePart.range); }, + } + ), + ], }); } else if (!allowedTags.has(parentNodeName)) { + const data = { + attributeName, + reportingValue, + elementName: parentNodeName, + }; + report(context, messages.notValidFor, 'notValidFor', { node, - data: { - attributeName, - reportingValue, - elementName: parentNodeName, - }, - suggest, + data, + suggest: [ + Object.assign( + getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), + { + data, + fix(fixer) { return fixer.removeRange(singlePart.range); }, + } + ), + ], }); } } @@ -360,7 +382,10 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN suggest: [ Object.assign( getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces), - { fix(fixer) { return fixer.removeRange(whitespacePart.range); } } + { + data: { attributeName }, + fix(fixer) { return fixer.removeRange(whitespacePart.range); }, + } ), ], }); @@ -371,7 +396,10 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN suggest: [ Object.assign( getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces), - { fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); } } + { + data: { attributeName }, + fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); }, + } ), ], }); @@ -390,16 +418,21 @@ function checkAttribute(context, node) { COMPONENT_ATTRIBUTE_MAP.get(attribute).values(), (tagName) => `"<${tagName}>"` ).join(', '); + const data = { + attributeName: attribute, + tagNames, + }; + report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { node: node.name, - data: { - attributeName: attribute, - tagNames, - }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), - { fix(fixer) { return fixer.remove(node); } } + { + data, + fix(fixer) { return fixer.remove(node); }, + } ), ], }); @@ -409,13 +442,15 @@ function checkAttribute(context, node) { function fix(fixer) { return fixer.remove(node); } if (!node.value) { + const data = { attributeName: attribute }; + report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', { node: node.name, - data: { attributeName: attribute }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), - { fix } + { data, fix } ), ], }); @@ -435,24 +470,28 @@ function checkAttribute(context, node) { } if (node.value.expression.type === 'ObjectExpression') { + const data = { attributeName: attribute }; + report(context, messages.onlyStrings, 'onlyStrings', { node: node.value, - data: { attributeName: attribute }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), - { fix } + { data, fix } ), ], }); } else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') { + const data = { attributeName: attribute }; + report(context, messages.onlyStrings, 'onlyStrings', { node: node.value, - data: { attributeName: attribute }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), - { fix } + { data, fix } ), ], }); @@ -476,16 +515,21 @@ function checkPropValidValue(context, node, value, attribute) { const validTagSet = validTags.get(value.value); if (!validTagSet) { + const data = { + attributeName: attribute, + reportingValue: value.value, + }; + report(context, messages.neverValid, 'neverValid', { node: value, - data: { - attributeName: attribute, - reportingValue: value.value, - }, + data, suggest: [ Object.assign( getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), - { fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); } } + { + data, + fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); }, + } ), ], }); diff --git a/tests/lib/rules/no-invalid-html-attribute.js b/tests/lib/rules/no-invalid-html-attribute.js index f1e20aa9a6..b171d823a4 100644 --- a/tests/lib/rules/no-invalid-html-attribute.js +++ b/tests/lib/rules/no-invalid-html-attribute.js @@ -247,12 +247,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'alternatex', attributeName: 'rel', + reportingValue: 'alternatex', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex' }, output: '', }, ], @@ -266,12 +267,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'alternatex', attributeName: 'rel', + reportingValue: 'alternatex', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex' }, output: 'React.createElement("a", { rel: "" })', }, ], @@ -291,6 +293,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex' }, output: 'React.createElement("a", { rel: [""] })', }, ], @@ -304,12 +307,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'alternatex', attributeName: 'rel', + reportingValue: 'alternatex', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex' }, output: '', }, ], @@ -323,12 +327,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'alternatex alternate', attributeName: 'rel', + reportingValue: 'alternatex alternate', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex alternate' }, output: 'React.createElement("a", { rel: "" })', }, ], @@ -348,6 +353,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex alternate' }, output: 'React.createElement("a", { rel: [""] })', }, ], @@ -367,6 +373,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternatex' }, output: '', }, ], @@ -386,6 +393,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternate alternatex' }, output: 'React.createElement("a", { rel: "" })', }, ], @@ -405,6 +413,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternate alternatex' }, output: 'React.createElement("a", { rel: [""] })', }, ], @@ -424,6 +433,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveDefault', + data: { attributeName: 'rel' }, output: '', }, ], @@ -443,6 +453,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { // suggestions: [ // { // messageId: 'suggestRemoveDefault', + // data: { attributeName: 'rel' }, // output: 'React.createElement("html", { })', // }, // ], @@ -460,6 +471,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveEmpty', + data: { attributeName: 'rel' }, output: '', }, ], @@ -508,6 +520,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveDefault', + data: { attributeName: 'rel' }, output: '', }, ], @@ -524,6 +537,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveNonString', + data: { attributeName: 'rel' }, output: '', }, ], @@ -540,6 +554,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveNonString', + data: { attributeName: 'rel' }, output: '', }, ], @@ -552,10 +567,11 @@ ruleTester.run('no-invalid-html-attribute', rule, { errors: [ { messageId: 'onlyStrings', - data: { attributeName: 'rel' }, + data: { attributeName: 'rel', reportingValue: 'true' }, suggestions: [ { messageId: 'suggestRemoveNonString', + data: { attributeName: 'rel', reportingValue: 'true' }, output: '', }, ], @@ -572,6 +588,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveDefault', + data: { attributeName: 'rel' }, output: '', }, ], @@ -588,6 +605,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveDefault', + data: { attributeName: 'rel' }, output: '', }, ], @@ -607,6 +625,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'foobar' }, output: '', }, ], @@ -623,6 +642,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -639,6 +659,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -655,6 +676,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -674,6 +696,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'foobar' }, output: '', }, ], @@ -693,6 +716,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'foobar' }, output: 'React.createElement("a", { rel: ["noreferrer", "noopener", "" ] })', }, ], @@ -712,6 +736,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'foobar' }, output: '', }, ], @@ -731,6 +756,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'foobar' }, output: '', }, ], @@ -745,6 +771,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'batgo' }, output: '', }, ], @@ -753,6 +780,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'spaceDelimited', data: { attributeName: 'rel' }, + suggestions: [ + { + messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, + output: '', + }, + ], }, ], }, @@ -765,6 +799,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -781,6 +816,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -800,6 +836,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'batgo' }, output: '', }, ], @@ -808,6 +845,13 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'spaceDelimited', data: { attributeName: 'rel' }, + suggestions: [ + { + messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, + output: '', + }, + ], }, ], }, @@ -823,6 +867,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'batgo' }, output: '', }, ], @@ -839,6 +884,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -859,6 +905,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'canonical' }, output: '', }, ], @@ -879,6 +926,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'dns-prefetch' }, output: '', }, ], @@ -899,6 +947,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'icon' }, output: '', }, ], @@ -925,12 +974,16 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'foo', attributeName: 'rel', + reportingValue: 'foo', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { + attributeName: 'rel', + reportingValue: 'foo', + }, output: '', }, ], @@ -956,6 +1009,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -969,12 +1023,16 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'neverValid', data: { - reportingValue: 'foo', attributeName: 'rel', + reportingValue: 'foo', }, suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { + attributeName: 'rel', + reportingValue: 'foo', + }, output: '', }, ], @@ -983,6 +1041,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { { messageId: 'notAlone', data: { + attributeName: 'rel', reportingValue: 'shortcut', missingValue: 'icon', }, @@ -993,6 +1052,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveWhitespaces', + data: { attributeName: 'rel' }, output: '', }, ], @@ -1013,6 +1073,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'manifest' }, output: '', }, ], @@ -1033,6 +1094,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'modulepreload' }, output: '', }, ], @@ -1053,6 +1115,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'pingback' }, output: '', }, ], @@ -1073,6 +1136,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preconnect' }, output: '', }, ], @@ -1093,6 +1157,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prefetch' }, output: '', }, ], @@ -1113,6 +1178,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preload' }, output: '', }, ], @@ -1133,6 +1199,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prerender' }, output: '', }, ], @@ -1153,6 +1220,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'stylesheet' }, output: '', }, ], @@ -1173,6 +1241,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'canonical' }, output: '', }, ], @@ -1193,6 +1262,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'dns-prefetch' }, output: '', }, ], @@ -1213,6 +1283,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'icon' }, output: '', }, ], @@ -1233,6 +1304,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'manifest' }, output: '', }, ], @@ -1253,6 +1325,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'modulepreload' }, output: '', }, ], @@ -1273,6 +1346,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'pingback' }, output: '', }, ], @@ -1293,6 +1367,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preconnect' }, output: '', }, ], @@ -1313,6 +1388,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prefetch' }, output: '', }, ], @@ -1333,6 +1409,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preload' }, output: '', }, ], @@ -1353,6 +1430,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prerender' }, output: '', }, ], @@ -1373,6 +1451,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'stylesheet' }, output: '', }, ], @@ -1393,6 +1472,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'bookmark' }, output: '', }, ], @@ -1413,6 +1493,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'external' }, output: '', }, ], @@ -1433,6 +1514,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'nofollow' }, output: '', }, ], @@ -1453,6 +1535,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'noopener' }, output: '', }, ], @@ -1472,6 +1555,8 @@ ruleTester.run('no-invalid-html-attribute', rule, { }, suggestions: [ { + messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'noreferrer' }, output: '', }, ], @@ -1492,6 +1577,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'opener' }, output: '', }, ], @@ -1512,6 +1598,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'tag' }, output: '', }, ], @@ -1532,6 +1619,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'alternate' }, output: '
', }, ], @@ -1552,6 +1640,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'author' }, output: '
', }, ], @@ -1572,6 +1661,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'bookmark' }, output: '
', }, ], @@ -1592,6 +1682,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'canonical' }, output: '
', }, ], @@ -1612,6 +1703,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'dns-prefetch' }, output: '
', }, ], @@ -1632,6 +1724,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'icon' }, output: '
', }, ], @@ -1652,6 +1745,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'manifest' }, output: '
', }, ], @@ -1672,6 +1766,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'modulepreload' }, output: '
', }, ], @@ -1692,6 +1787,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'pingback' }, output: '
', }, ], @@ -1712,6 +1808,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preconnect' }, output: '
', }, ], @@ -1732,6 +1829,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prefetch' }, output: '
', }, ], @@ -1752,6 +1850,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'preload' }, output: '
', }, ], @@ -1772,6 +1871,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'prerender' }, output: '
', }, ], @@ -1792,6 +1892,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'stylesheet' }, output: '
', }, ], @@ -1812,6 +1913,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveInvalid', + data: { reportingValue: 'tag' }, output: '
', }, ], @@ -1832,6 +1934,7 @@ ruleTester.run('no-invalid-html-attribute', rule, { suggestions: [ { messageId: 'suggestRemoveEmpty', + data: { attributeName: 'rel' }, output: '
', }, ], From ec8600127315e8c650392eed95e8212eda445704 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 24 May 2024 09:40:42 -0700 Subject: [PATCH 041/174] [Dev Deps] update `@babel/core`, `@babel/eslint-parser`, `@babel/plugin-syntax-decorators`, `@babel/plugin-syntax-do-expressions`, `@babel/plugin-syntax-function-bind`, `@babel/preset-react` --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 685e65a222..025bf9ea83 100644 --- a/package.json +++ b/package.json @@ -45,12 +45,12 @@ "string.prototype.matchall": "^4.0.10" }, "devDependencies": { - "@babel/core": "^7.24.0", - "@babel/eslint-parser": "^7.23.10", - "@babel/plugin-syntax-decorators": "^7.24.0", - "@babel/plugin-syntax-do-expressions": "^7.23.3", - "@babel/plugin-syntax-function-bind": "^7.23.3", - "@babel/preset-react": "^7.23.3", + "@babel/core": "^7.24.6", + "@babel/eslint-parser": "^7.24.6", + "@babel/plugin-syntax-decorators": "^7.24.6", + "@babel/plugin-syntax-do-expressions": "^7.24.6", + "@babel/plugin-syntax-function-bind": "^7.24.6", + "@babel/preset-react": "^7.24.6", "@types/eslint": "=7.2.10", "@types/estree": "0.0.52", "@types/node": "^4.9.5", From d79605a796475e438b654f10c1c1c66ebc92886a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 24 May 2024 09:41:56 -0700 Subject: [PATCH 042/174] [Deps] update `array-includes`, `array.prototype.findlast`, `es-iterator-helpers`, `object.entries`, `object.fromentries`, `object.hasown`, `object.values`, `string.prototype.matchall` --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 025bf9ea83..1c8300d329 100644 --- a/package.json +++ b/package.json @@ -25,24 +25,24 @@ "homepage": "https://github.com/jsx-eslint/eslint-plugin-react", "bugs": "https://github.com/jsx-eslint/eslint-plugin-react/issues", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.4", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", "array.prototype.toreversed": "^1.1.2", "array.prototype.tosorted": "^1.1.3", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.17", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7", - "object.hasown": "^1.1.3", - "object.values": "^1.1.7", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.10" + "string.prototype.matchall": "^4.0.11" }, "devDependencies": { "@babel/core": "^7.24.6", From 417e1ca292788c75618dc994b084c3a57c483fce Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 27 May 2024 22:40:06 -0700 Subject: [PATCH 043/174] Update CHANGELOG and bump version --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2c437523f..a0d5cd3185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,18 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## [7.34.2] - 2024.05.24 + ### Fixed * [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi) * [`jsx-no-leaked-render`]: invalid report if left side is boolean ([#3746][] @akulsr0) * [`jsx-closing-bracket-location`]: message shows `{{details}}` when there are no details ([#3759][] @mdjermanovic) * [`no-invalid-html-attribute`]: ensure error messages are correct ([#3759][] @mdjermanovic, @ljharb) +### Changed + * [Refactor] create various eslint utils to fix eslint deprecations ([#3759][] @mdjermanovic, @ljharb) + +[7.34.2]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.1...v7.34.2 [#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759 [#3746]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3746 [#3718]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3718 diff --git a/package.json b/package.json index 1c8300d329..4f5155c832 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-react", - "version": "7.34.1", + "version": "7.34.2", "author": "Yannick Croissant ", "description": "React specific linting rules for ESLint", "main": "index.js", From e27ef8131f955fedddc3f3111a7073ea7a6f5e17 Mon Sep 17 00:00:00 2001 From: Ciaran Harvey Date: Thu, 30 May 2024 17:10:57 +0100 Subject: [PATCH 044/174] [Fix] `prop-types`: null-check rootNode before calling getScope --- CHANGELOG.md | 5 +++++ lib/util/propTypes.js | 3 ++- tests/lib/rules/prop-types.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0d5cd3185..e4bccfaa25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Fixed +* [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) + +[#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 + ## [7.34.2] - 2024.05.24 ### Fixed diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 6f879e2347..22063afb8e 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -372,7 +372,8 @@ module.exports = function propTypesInstructions(context, components, utils) { */ function resolveValueForIdentifierNode(node, rootNode, callback) { if ( - node + rootNode + && node && node.type === 'Identifier' ) { const scope = getScope(context, rootNode); diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index df762d74ef..97206b7d44 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -3361,6 +3361,20 @@ ruleTester.run('prop-types', rule, { `, features: ['ts', 'no-babel'], }, + { + code: ` + import React from "react"; + + const returnTypeProp = (someProp: any) => ({ someProp }); + + const SomeComponent: React.FunctionComponent< + ReturnType + > = ({ someProp }) => { + return
{someProp}
; + }; + `, + features: ['ts', 'no-babel'], + }, { code: ` export const EuiSuperSelectControl: ( @@ -7840,6 +7854,26 @@ ruleTester.run('prop-types', rule, { ], features: ['ts', 'no-babel'], }, + { + code: ` + import React from "react"; + + const returnTypeProp = (someProp: any) => ({ someProp }); + + const SomeComponent: React.FunctionComponent< + ReturnType + > = ({ someIncorrectProp }) => { + return
{someProp}
; + }; + `, + errors: [ + { + messageId: 'missingPropType', + data: { name: 'someIncorrectProp' }, + }, + ], + features: ['ts', 'no-babel'], + }, { code: ` import React from 'react'; From a79beb364f297571af9d368e1d5c35f56f84a8a7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 31 May 2024 16:46:05 -0700 Subject: [PATCH 045/174] [Fix] `boolean-prop-naming`: avoid a crash with a spread prop Fixes #3733 --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 2 +- tests/lib/rules/boolean-prop-naming.js | 22 +++++++++++++++------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bccfaa25..bef2238f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) +* [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb) [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 +[#3733]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3733 ## [7.34.2] - 2024.05.24 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index f64da93242..e6ea400025 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -399,7 +399,7 @@ module.exports = { } if (propType) { - [].concat(propType).forEach((prop) => { + [].concat(propType).filter(Boolean).forEach((prop) => { validatePropNaming( component.node, prop.properties || prop.members || prop.body diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index 2dec7ed70c..10ff9097f2 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -415,7 +415,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['ts'], - errors: [], }, { code: ` @@ -426,7 +425,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], }, { code: ` @@ -439,7 +437,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '(is|has)[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], }, { code: ` @@ -451,7 +448,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^is[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], }, { code: ` @@ -465,7 +461,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], }, { code: ` @@ -479,7 +474,6 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], }, { code: ` @@ -495,7 +489,21 @@ ruleTester.run('boolean-prop-naming', rule, { `, options: [{ rule: '^(is|has)[A-Z]([A-Za-z0-9]?)+' }], features: ['types'], - errors: [], + }, + { + code: ` + export const DataRow = (props: { label: string; value: string; } & React.HTMLAttributes) => { + const { label, value, ...otherProps } = props; + return ( +
+ {label} + {value} +
+ ); + }; + `, + options: [{ rule: '(^(is|has|should|without)[A-Z]([A-Za-z0-9]?)+|disabled|required|checked|defaultChecked)' }], + features: ['types'], }, ]), From a944aa519246d1f4c7a494ebd40e5b2c23601b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Ne=CC=81meth?= Date: Tue, 14 May 2024 10:54:14 +0200 Subject: [PATCH 046/174] [Fix] `jsx-boolean-value`: `assumeUndefinedIsFalse` with `never` must not allow explicit `true` value --- CHANGELOG.md | 2 ++ lib/rules/jsx-boolean-value.js | 1 - tests/lib/rules/jsx-boolean-value.js | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef2238f72..3b9d9ed001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) * [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb) +* [`jsx-boolean-value`]: `assumeUndefinedIsFalse` with `never` must not allow explicit `true` value ([#3757][] @6uliver) [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 +[#3757]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3757 [#3733]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3733 ## [7.34.2] - 2024.05.24 diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js index 1cff171147..4572e7ff8d 100644 --- a/lib/rules/jsx-boolean-value.js +++ b/lib/rules/jsx-boolean-value.js @@ -132,7 +132,6 @@ module.exports = { } if ( isNever(configuration, exceptions, propName) - && !configObject.assumeUndefinedIsFalse && value && value.type === 'JSXExpressionContainer' && value.expression.value === true diff --git a/tests/lib/rules/jsx-boolean-value.js b/tests/lib/rules/jsx-boolean-value.js index e8cbbb3e7f..f4216f2ce1 100644 --- a/tests/lib/rules/jsx-boolean-value.js +++ b/tests/lib/rules/jsx-boolean-value.js @@ -52,6 +52,10 @@ ruleTester.run('jsx-boolean-value', rule, { code: ';', options: ['never', { assumeUndefinedIsFalse: true }], }, + { + code: ';', + options: ['never', { assumeUndefinedIsFalse: false }], + }, { code: ';', options: ['never', { assumeUndefinedIsFalse: true, always: ['foo'] }], @@ -145,6 +149,21 @@ ruleTester.run('jsx-boolean-value', rule, { }, ], }, + { + code: ';', + output: ';', + options: ['never', { assumeUndefinedIsFalse: true }], + errors: [ + { + messageId: 'omitBoolean', + data: { propName: 'foo' }, + }, + { + messageId: 'omitPropAndBoolean', + data: { propName: 'bak' }, + }, + ], + }, { code: ';', output: ';', From 393bfa2fc071bfd08cef2327790e2ccc95507d72 Mon Sep 17 00:00:00 2001 From: Julien Rousseau Date: Wed, 12 Jun 2024 20:49:45 -0400 Subject: [PATCH 047/174] [Fix] `no-object-type-as-default-prop`: enable rule for components with many parameters --- CHANGELOG.md | 2 ++ lib/rules/no-object-type-as-default-prop.js | 2 +- .../rules/no-object-type-as-default-prop.js | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9d9ed001..cd3540427d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) * [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb) * [`jsx-boolean-value`]: `assumeUndefinedIsFalse` with `never` must not allow explicit `true` value ([#3757][] @6uliver) +* [`no-object-type-as-default-prop`]: enable rule for components with many parameters ([#3768][] @JulienR1) +[#3768]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3768 [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 [#3757]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3757 [#3733]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3733 diff --git a/lib/rules/no-object-type-as-default-prop.js b/lib/rules/no-object-type-as-default-prop.js index 7be98cb4b3..42012413e1 100644 --- a/lib/rules/no-object-type-as-default-prop.js +++ b/lib/rules/no-object-type-as-default-prop.js @@ -30,7 +30,7 @@ const messages = { function hasUsedObjectDestructuringSyntax(params) { return ( params != null - && params.length === 1 + && params.length >= 1 && params[0].type === 'ObjectPattern' ); } diff --git a/tests/lib/rules/no-object-type-as-default-prop.js b/tests/lib/rules/no-object-type-as-default-prop.js index e2660fb938..b515ef6a49 100644 --- a/tests/lib/rules/no-object-type-as-default-prop.js +++ b/tests/lib/rules/no-object-type-as-default-prop.js @@ -143,6 +143,11 @@ ruleTester.run('no-object-type-as-default-prop', rule, { return null; }; `, + ` + const Foo = ({bar = 1}, context) => { + return null; + }; + `, ` export default function NotAComponent({foo = {}}) {} ` @@ -183,6 +188,24 @@ ruleTester.run('no-object-type-as-default-prop', rule, { } `, errors: expectedViolations, + }, + { + code: ` + const Foo = ({ + a = {}, + b = ['one', 'two'], + c = /regex/i, + d = () => {}, + e = function() {}, + f = class {}, + g = new Thing(), + h = , + i = Symbol('foo') + }, context) => { + return null; + } + `, + errors: expectedViolations, } )), }); From 6dc7803acecac6e79d71d9619665403fe3073d79 Mon Sep 17 00:00:00 2001 From: akulsr0 Date: Tue, 18 Jun 2024 18:56:03 +0530 Subject: [PATCH 048/174] [Fix] `jsx-key`: incorrect behavior for checkKeyMustBeforeSpread with map callbacks --- CHANGELOG.md | 2 ++ lib/rules/jsx-key.js | 42 ++++++++++++++++++++++---------------- tests/lib/rules/jsx-key.js | 15 ++++++++++++++ 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd3540427d..46cf73592b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb) * [`jsx-boolean-value`]: `assumeUndefinedIsFalse` with `never` must not allow explicit `true` value ([#3757][] @6uliver) * [`no-object-type-as-default-prop`]: enable rule for components with many parameters ([#3768][] @JulienR1) +* [`jsx-key`]: incorrect behavior for checkKeyMustBeforeSpread with map callbacks ([#3769][] @akulsr0) +[#3769]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3769 [#3768]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3768 [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 [#3757]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3757 diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js index feee7ad5a0..825d21f4bb 100644 --- a/lib/rules/jsx-key.js +++ b/lib/rules/jsx-key.js @@ -73,11 +73,31 @@ module.exports = { const reactPragma = pragmaUtil.getFromContext(context); const fragmentPragma = pragmaUtil.getFragmentFromContext(context); + function isKeyAfterSpread(attributes) { + let hasFoundSpread = false; + return attributes.some((attribute) => { + if (attribute.type === 'JSXSpreadAttribute') { + hasFoundSpread = true; + return false; + } + if (attribute.type !== 'JSXAttribute') { + return false; + } + return hasFoundSpread && propName(attribute) === 'key'; + }); + } + function checkIteratorElement(node) { - if (node.type === 'JSXElement' && !hasProp(node.openingElement.attributes, 'key')) { - report(context, messages.missingIterKey, 'missingIterKey', { - node, - }); + if (node.type === 'JSXElement') { + if (!hasProp(node.openingElement.attributes, 'key')) { + report(context, messages.missingIterKey, 'missingIterKey', { node }); + } else { + const attrs = node.openingElement.attributes; + + if (checkKeyMustBeforeSpread && isKeyAfterSpread(attrs)) { + report(context, messages.keyBeforeSpread, 'keyBeforeSpread', { node }); + } + } } else if (checkFragmentShorthand && node.type === 'JSXFragment') { report(context, messages.missingIterKeyUsePrag, 'missingIterKeyUsePrag', { node, @@ -115,20 +135,6 @@ module.exports = { return returnStatements; } - function isKeyAfterSpread(attributes) { - let hasFoundSpread = false; - return attributes.some((attribute) => { - if (attribute.type === 'JSXSpreadAttribute') { - hasFoundSpread = true; - return false; - } - if (attribute.type !== 'JSXAttribute') { - return false; - } - return hasFoundSpread && propName(attribute) === 'key'; - }); - } - /** * Checks if the given node is a function expression or arrow function, * and checks if there is a missing key prop in return statement's arguments diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js index 17109e4215..748e9a2fd9 100644 --- a/tests/lib/rules/jsx-key.js +++ b/tests/lib/rules/jsx-key.js @@ -409,5 +409,20 @@ ruleTester.run('jsx-key', rule, { { messageId: 'missingIterKey' }, ], }, + { + code: ` + const TestCase = () => { + const list = [1, 2, 3, 4, 5]; + + return ( +
+ {list.map(x =>
)} +
+ ); + }; + `, + options: [{ checkKeyMustBeforeSpread: true }], + errors: [{ messageId: 'keyBeforeSpread' }], + }, ]), }); From 7d16666058e2b06193f08342c8b1f20a5468cf20 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 18 Jun 2024 12:44:51 -0700 Subject: [PATCH 049/174] [Dev Deps] update `@babel/core`, `@babel/eslint-parser`, `@babel/plugin-syntax-decorators`, `@babel/plugin-syntax-do-expressions`, `@babel/plugin-syntax-function-bind`, `@babel/preset-react`, `eslint-doc-generator`, `eslint-remote-tester-repositories` --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4f5155c832..e004c4fb91 100644 --- a/package.json +++ b/package.json @@ -45,12 +45,12 @@ "string.prototype.matchall": "^4.0.11" }, "devDependencies": { - "@babel/core": "^7.24.6", - "@babel/eslint-parser": "^7.24.6", - "@babel/plugin-syntax-decorators": "^7.24.6", - "@babel/plugin-syntax-do-expressions": "^7.24.6", - "@babel/plugin-syntax-function-bind": "^7.24.6", - "@babel/preset-react": "^7.24.6", + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@babel/plugin-syntax-decorators": "^7.24.7", + "@babel/plugin-syntax-do-expressions": "^7.24.7", + "@babel/plugin-syntax-function-bind": "^7.24.7", + "@babel/preset-react": "^7.24.7", "@types/eslint": "=7.2.10", "@types/estree": "0.0.52", "@types/node": "^4.9.5", @@ -59,11 +59,11 @@ "babel-eslint": "^8 || ^9 || ^10.1.0", "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint-config-airbnb-base": "^15.0.0", - "eslint-doc-generator": "^1.7.0", + "eslint-doc-generator": "^1.7.1", "eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", "eslint-plugin-import": "^2.29.1", "eslint-remote-tester": "^3.0.1", - "eslint-remote-tester-repositories": "^1.0.1", + "eslint-remote-tester-repositories": "^2.0.0", "eslint-scope": "^3.7.3", "espree": "^3.5.4", "glob": "=10.3.7", From eb56061c95d3e5e234d2f33def724c17aedc752e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 18 Jun 2024 12:45:35 -0700 Subject: [PATCH 050/174] [Deps] update `array.prototype.tosorted` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e004c4fb91..c6bec01eff 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", From cef8123ff92460ed2369d670e8c7b9539e5994bd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 18 Jun 2024 12:48:05 -0700 Subject: [PATCH 051/174] Update CHANGELOG and bump version --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cf73592b..42e7d214f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## [7.34.3] - 2024.06.18 + ### Fixed * [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv) * [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb) @@ -12,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-object-type-as-default-prop`]: enable rule for components with many parameters ([#3768][] @JulienR1) * [`jsx-key`]: incorrect behavior for checkKeyMustBeforeSpread with map callbacks ([#3769][] @akulsr0) +[7.34.3]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.2...v7.34.3 [#3769]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3769 [#3768]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3768 [#3762]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3762 diff --git a/package.json b/package.json index c6bec01eff..ba818e4eec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-react", - "version": "7.34.2", + "version": "7.34.3", "author": "Yannick Croissant ", "description": "React specific linting rules for ESLint", "main": "index.js", From b7474504fe5b9101dd7f607d9bc71aa2e61dfb37 Mon Sep 17 00:00:00 2001 From: akulsr0 Date: Mon, 6 May 2024 14:13:56 +0530 Subject: [PATCH 052/174] [Fix] `prop-types`: fix `className` missing in prop validation false negative --- CHANGELOG.md | 6 ++++++ lib/util/propTypes.js | 19 +++++++++++++++++++ tests/lib/rules/prop-types.js | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e7d214f0..5db11b6487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Fixed + +* [`prop-types`]: fix `className` missing in prop validation false negative ([#3749] @akulsr0) + +[#3749]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3749 + ## [7.34.3] - 2024.06.18 ### Fixed diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 22063afb8e..84f5479f15 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -645,6 +645,17 @@ module.exports = function propTypesInstructions(context, components, utils) { this.shouldSpecifyOptionalChildrenProps = true; const rightMostName = getRightMostTypeName(node.typeName); + if ( + leftMostName === 'React' + && ( + rightMostName === 'HTMLAttributes' + || rightMostName === 'HTMLElement' + || rightMostName === 'HTMLProps' + ) + ) { + this.shouldSpecifyClassNameProp = true; + } + const importedName = localToImportedMap[rightMostName]; const idx = genericTypeParamIndexWherePropsArePresent[ leftMostName !== rightMostName ? rightMostName : importedName @@ -835,6 +846,14 @@ module.exports = function propTypesInstructions(context, components, utils) { isRequired: false, }; } + if (this.shouldSpecifyClassNameProp) { + this.declaredPropTypes.className = { + fullName: 'className', + name: 'className', + isRequired: false, + }; + } + this.foundDeclaredPropertiesList.forEach((tsInterfaceBody) => { if (tsInterfaceBody && (tsInterfaceBody.type === 'TSPropertySignature' || tsInterfaceBody.type === 'TSMethodSignature')) { let accessor = 'name'; diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 97206b7d44..638861e2bb 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -4195,6 +4195,29 @@ ruleTester.run('prop-types', rule, { ); `, features: ['types'], + }, + { + code: ` + import React from "react" + + export function Heading({ className, ...props }: React.HTMLAttributes) { + return
+ } + `, + features: ['types'], + }, + { + code: ` + import React from 'react'; + type TDelIconProps = React.HTMLProps; + + const DelIcon: React.FC = ({className, ...rest}) => ( +
+ +
+ ); + `, + features: ['types'], } )), From 3c1d5203438965b3999911520a930306f6e9c58f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 23 Jun 2024 23:00:32 -0700 Subject: [PATCH 053/174] [Dev Deps] downgrade `eslint-remote-tester-repositories` See https://github.com/jsx-eslint/eslint-plugin-react/issues/3632#issuecomment-2184859577 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba818e4eec..6446e53be2 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", "eslint-plugin-import": "^2.29.1", "eslint-remote-tester": "^3.0.1", - "eslint-remote-tester-repositories": "^2.0.0", + "eslint-remote-tester-repositories": "^1.0.1", "eslint-scope": "^3.7.3", "espree": "^3.5.4", "glob": "=10.3.7", From eda77f32be2bb495ad7bc473da99323d025c91ec Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 3 Jul 2024 23:03:37 -0500 Subject: [PATCH 054/174] [Refactor] avoid making a holey array --- lib/rules/jsx-closing-bracket-location.js | 8 +++++--- lib/rules/jsx-closing-tag-location.js | 4 +++- lib/rules/jsx-indent-props.js | 5 ++++- lib/rules/jsx-indent.js | 3 ++- package.json | 6 ++++-- types/string.prototype.repeat/index.d.ts | 3 +++ 6 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 types/string.prototype.repeat/index.d.ts diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index 26556d6117..bb4658218a 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -6,6 +6,8 @@ 'use strict'; const has = require('object.hasown/polyfill')(); +const repeat = require('string.prototype.repeat'); + const docsUrl = require('../util/docsUrl'); const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); @@ -168,7 +170,7 @@ module.exports = { function getIndentation(tokens, expectedLocation, correctColumn) { const newColumn = correctColumn || 0; let indentation; - let spaces = []; + let spaces = ''; switch (expectedLocation) { case 'props-aligned': indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.lastProp.firstLine - 1])[0]; @@ -182,9 +184,9 @@ module.exports = { } if (indentation.length + 1 < newColumn) { // Non-whitespace characters were included in the column offset - spaces = new Array(+correctColumn + 1 - indentation.length); + spaces = repeat(' ', +correctColumn - indentation.length); } - return indentation + spaces.join(' '); + return indentation + spaces; } /** diff --git a/lib/rules/jsx-closing-tag-location.js b/lib/rules/jsx-closing-tag-location.js index 5d5a95cb7b..a2828d12ae 100644 --- a/lib/rules/jsx-closing-tag-location.js +++ b/lib/rules/jsx-closing-tag-location.js @@ -5,6 +5,8 @@ 'use strict'; +const repeat = require('string.prototype.repeat'); + const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); @@ -53,7 +55,7 @@ module.exports = { node, loc: node.loc, fix(fixer) { - const indent = Array(opening.loc.start.column + 1).join(' '); + const indent = repeat(' ', opening.loc.start.column); if (astUtil.isNodeFirstInLine(context, node)) { return fixer.replaceTextRange( [node.range[0] - node.loc.start.column, node.range[0]], diff --git a/lib/rules/jsx-indent-props.js b/lib/rules/jsx-indent-props.js index 050e721df3..10adb1b8d9 100644 --- a/lib/rules/jsx-indent-props.js +++ b/lib/rules/jsx-indent-props.js @@ -30,6 +30,8 @@ 'use strict'; +const repeat = require('string.prototype.repeat'); + const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const getText = require('../util/eslint').getText; @@ -130,7 +132,8 @@ module.exports = { data: msgContext, fix(fixer) { return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], - Array(needed + 1).join(indentType === 'space' ? ' ' : '\t')); + repeat(indentType === 'space' ? ' ' : '\t', needed) + ); }, }); } diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index 27397172c8..0bfd0507dd 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -31,6 +31,7 @@ 'use strict'; const matchAll = require('string.prototype.matchall'); +const repeat = require('string.prototype.repeat'); const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); @@ -109,7 +110,7 @@ module.exports = { * @private */ function getFixerFunction(node, needed) { - const indent = Array(needed + 1).join(indentChar); + const indent = repeat(indentChar, needed); if (node.type === 'JSXText' || node.type === 'Literal') { return function fix(fixer) { diff --git a/package.json b/package.json index 6446e53be2..570331196c 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "devDependencies": { "@babel/core": "^7.24.7", @@ -103,7 +104,8 @@ ".eslintrc", ".editorconfig", "tsconfig.json", - ".markdownlint*" + ".markdownlint*", + "types" ] } } diff --git a/types/string.prototype.repeat/index.d.ts b/types/string.prototype.repeat/index.d.ts new file mode 100644 index 0000000000..f240d9301f --- /dev/null +++ b/types/string.prototype.repeat/index.d.ts @@ -0,0 +1,3 @@ +declare module 'string.prototype.repeat' { + export = typeof Function.call.bind(String.prototype.repeat); +} From bcb987a67b36d160ad0daa1dcc9ef384b4d5a73c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 3 Jul 2024 23:04:29 -0500 Subject: [PATCH 055/174] [Refactor] use `hasown` instead of the larger `object.hasown` --- lib/rules/forbid-elements.js | 2 +- lib/rules/jsx-closing-bracket-location.js | 2 +- lib/rules/jsx-curly-spacing.js | 2 +- lib/rules/jsx-max-depth.js | 2 +- lib/rules/jsx-no-duplicate-props.js | 2 +- lib/rules/jsx-wrap-multilines.js | 2 +- lib/rules/no-array-index-key.js | 2 +- lib/rules/no-danger.js | 2 +- lib/rules/no-unknown-property.js | 2 +- lib/rules/sort-comp.js | 2 +- lib/rules/void-dom-elements-no-children.js | 2 +- package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index dd3ffa8f5d..4ad66eebe0 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const getText = require('../util/eslint').getText; const isCreateElement = require('../util/isCreateElement'); diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index bb4658218a..21f99ffc9c 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const repeat = require('string.prototype.repeat'); const docsUrl = require('../util/docsUrl'); diff --git a/lib/rules/jsx-curly-spacing.js b/lib/rules/jsx-curly-spacing.js index 8d504f1d20..71bf901904 100644 --- a/lib/rules/jsx-curly-spacing.js +++ b/lib/rules/jsx-curly-spacing.js @@ -11,7 +11,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const getSourceCode = require('../util/eslint').getSourceCode; const report = require('../util/report'); diff --git a/lib/rules/jsx-max-depth.js b/lib/rules/jsx-max-depth.js index 6b1db78189..be0f582264 100644 --- a/lib/rules/jsx-max-depth.js +++ b/lib/rules/jsx-max-depth.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const includes = require('array-includes'); const variableUtil = require('../util/variable'); const jsxUtil = require('../util/jsx'); diff --git a/lib/rules/jsx-no-duplicate-props.js b/lib/rules/jsx-no-duplicate-props.js index cf7737698a..77de5bde51 100644 --- a/lib/rules/jsx-no-duplicate-props.js +++ b/lib/rules/jsx-no-duplicate-props.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index 17a9812465..6f5ad50bbf 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const eslintUtil = require('../util/eslint'); const jsxUtil = require('../util/jsx'); diff --git a/lib/rules/no-array-index-key.js b/lib/rules/no-array-index-key.js index c79fd56e2c..47b8f47b57 100644 --- a/lib/rules/no-array-index-key.js +++ b/lib/rules/no-array-index-key.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); const pragma = require('../util/pragma'); diff --git a/lib/rules/no-danger.js b/lib/rules/no-danger.js index 1cb0273739..eecdd87e23 100644 --- a/lib/rules/no-danger.js +++ b/lib/rules/no-danger.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const fromEntries = require('object.fromentries/polyfill')(); const docsUrl = require('../util/docsUrl'); diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index 3e8c6de111..95359d10fd 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const getText = require('../util/eslint').getText; const testReactVersion = require('../util/version').testReactVersion; diff --git a/lib/rules/sort-comp.js b/lib/rules/sort-comp.js index fb0bf8f614..cf24961629 100644 --- a/lib/rules/sort-comp.js +++ b/lib/rules/sort-comp.js @@ -5,7 +5,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const entries = require('object.entries'); const values = require('object.values'); const arrayIncludes = require('array-includes'); diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js index a8a6ba0149..66db2a8ad3 100644 --- a/lib/rules/void-dom-elements-no-children.js +++ b/lib/rules/void-dom-elements-no-children.js @@ -6,7 +6,7 @@ 'use strict'; -const has = require('object.hasown/polyfill')(); +const has = require('hasown'); const docsUrl = require('../util/docsUrl'); const isCreateElement = require('../util/isCreateElement'); diff --git a/package.json b/package.json index 570331196c..259b8f0e97 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", From f1ae6edc4092f58ee8ba46c6cefc19fa316fbffb Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 3 Jul 2024 23:13:31 -0500 Subject: [PATCH 056/174] [Refactor] `sort-comp`: use Object.entries instead of for-in --- lib/rules/sort-comp.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/rules/sort-comp.js b/lib/rules/sort-comp.js index cf24961629..e35574a682 100644 --- a/lib/rules/sort-comp.js +++ b/lib/rules/sort-comp.js @@ -124,6 +124,7 @@ module.exports = { }, create: Components.detect((context, components) => { + /** @satisfies {Record} */ const errors = {}; const methodsOrder = getMethodsOrder(context.options[0]); @@ -287,18 +288,19 @@ module.exports = { * Dedupe errors, only keep the ones with the highest score and delete the others */ function dedupeErrors() { - for (const i in errors) { - if (has(errors, i)) { - const index = errors[i].closest.ref.index; - if (errors[index]) { - if (errors[i].score > errors[index].score) { - delete errors[index]; - } else { - delete errors[i]; - } + entries(errors).forEach((entry) => { + const i = entry[0]; + const error = entry[1]; + + const index = error.closest.ref.index; + if (errors[index]) { + if (error.score > errors[index].score) { + delete errors[index]; + } else { + delete errors[i]; } } - } + }); } /** From 51d342ba350ae7d7dabce1caa648e71926ef283f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 3 Jul 2024 23:18:32 -0500 Subject: [PATCH 057/174] [patch] make TS happy --- .eslintrc | 112 +++++++++++++++---------------- lib/rules/forbid-elements.js | 1 + lib/rules/no-array-index-key.js | 4 +- lib/rules/no-unknown-property.js | 6 +- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/.eslintrc b/.eslintrc index d10247d296..e609aa23be 100644 --- a/.eslintrc +++ b/.eslintrc @@ -22,61 +22,61 @@ "object-shorthand": [2, "always", { "ignoreConstructors": false, "avoidQuotes": false, // this is the override vs airbnb - }], - "max-len": [2, 120, { - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - }], - "consistent-return": 0, + }], + "max-len": [2, 140, { + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreComments": true, + }], + "consistent-return": 0, - "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }], - "prefer-object-spread": 0, // until node 8 is required - "prefer-rest-params": 0, // until node 6 is required - "prefer-spread": 0, // until node 6 is required - "function-call-argument-newline": 1, // TODO: enable - "function-paren-newline": 0, - "no-plusplus": [2, {"allowForLoopAfterthoughts": true}], - "no-param-reassign": 1, - "no-restricted-syntax": [2, { - "selector": "ObjectPattern", - "message": "Object destructuring is not compatible with Node v4" - }], - "strict": [2, "safe"], - "valid-jsdoc": [2, { - "requireReturn": false, - "requireParamDescription": false, - "requireReturnDescription": false, - }], + "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }], + "prefer-object-spread": 0, // until node 8 is required + "prefer-rest-params": 0, // until node 6 is required + "prefer-spread": 0, // until node 6 is required + "function-call-argument-newline": 1, // TODO: enable + "function-paren-newline": 0, + "no-plusplus": [2, {"allowForLoopAfterthoughts": true}], + "no-param-reassign": 1, + "no-restricted-syntax": [2, { + "selector": "ObjectPattern", + "message": "Object destructuring is not compatible with Node v4" + }], + "strict": [2, "safe"], + "valid-jsdoc": [2, { + "requireReturn": false, + "requireParamDescription": false, + "requireReturnDescription": false, + }], - "eslint-plugin/consistent-output": 0, - "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }], - "eslint-plugin/require-meta-schema": 0, - "eslint-plugin/require-meta-type": 0 - }, - "overrides": [ - { - "files": "tests/**", - "rules": { - "no-template-curly-in-string": 1, - }, - }, - { - "files": "markdown.config.js", - "rules": { - "no-console": 0, - }, - }, - { - "files": ".github/workflows/*.js", - "parserOptions": { - "ecmaVersion": 2019, - }, - "rules": { - "camelcase": 0, - "no-console": 0, - "no-restricted-syntax": 0, - }, - }, - ], - } + "eslint-plugin/consistent-output": 0, + "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }], + "eslint-plugin/require-meta-schema": 0, + "eslint-plugin/require-meta-type": 0 + }, + "overrides": [ + { + "files": "tests/**", + "rules": { + "no-template-curly-in-string": 1, + }, + }, + { + "files": "markdown.config.js", + "rules": { + "no-console": 0, + }, + }, + { + "files": ".github/workflows/*.js", + "parserOptions": { + "ecmaVersion": 2019, + }, + "rules": { + "camelcase": 0, + "no-console": 0, + "no-restricted-syntax": 0, + }, + }, + ], +} diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js index 4ad66eebe0..6d672cf70d 100644 --- a/lib/rules/forbid-elements.js +++ b/lib/rules/forbid-elements.js @@ -60,6 +60,7 @@ module.exports = { const configuration = context.options[0] || {}; const forbidConfiguration = configuration.forbid || []; + /** @type {Record} */ const indexedForbidConfigs = {}; forbidConfiguration.forEach((item) => { diff --git a/lib/rules/no-array-index-key.js b/lib/rules/no-array-index-key.js index 47b8f47b57..2a0ae41e6f 100644 --- a/lib/rules/no-array-index-key.js +++ b/lib/rules/no-array-index-key.js @@ -117,6 +117,8 @@ module.exports = { return null; } + const name = /** @type {keyof iteratorFunctionsToIndexParamPosition} */ (callee.property.name); + const callbackArg = isUsingReactChildren(node) ? node.arguments[1] : node.arguments[0]; @@ -131,7 +133,7 @@ module.exports = { const params = callbackArg.params; - const indexParamPosition = iteratorFunctionsToIndexParamPosition[callee.property.name]; + const indexParamPosition = iteratorFunctionsToIndexParamPosition[name]; if (params.length < indexParamPosition + 1) { return null; } diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index 95359d10fd..674e93071f 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -491,10 +491,10 @@ function tagNameHasDot(node) { */ function getStandardName(name, context) { if (has(DOM_ATTRIBUTE_NAMES, name)) { - return DOM_ATTRIBUTE_NAMES[name]; + return DOM_ATTRIBUTE_NAMES[/** @type {keyof DOM_ATTRIBUTE_NAMES} */ (name)]; } if (has(SVGDOM_ATTRIBUTE_NAMES, name)) { - return SVGDOM_ATTRIBUTE_NAMES[name]; + return SVGDOM_ATTRIBUTE_NAMES[/** @type {keyof SVGDOM_ATTRIBUTE_NAMES} */ (name)]; } const names = getDOMPropertyNames(context); // Let's find a possible attribute match with a case-insensitive search. @@ -592,7 +592,7 @@ module.exports = { // Let's dive deeper into tags that are HTML/DOM elements (`