diff --git a/node_modules/eslint-plugin-react/README.md b/node_modules/eslint-plugin-react/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3c3cd24e3ffd648dc949a08232a409ba6a08e11d --- /dev/null +++ b/node_modules/eslint-plugin-react/README.md @@ -0,0 +1,423 @@ +# `eslint-plugin-react` [![Version Badge][npm-version-svg]][package-url] + +=================== + +[![github actions][actions-image]][actions-url] +[![Maintenance Status][status-image]][status-url] +[![NPM version][npm-image]][npm-url] +[![Tidelift][tidelift-image]][tidelift-url] + +React specific linting rules for `eslint` + +## Installation + +```sh +npm install eslint eslint-plugin-react --save-dev +``` + +It is also possible to install ESLint globally rather than locally (using `npm install -g eslint`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case. + +## Configuration (legacy: `.eslintrc*`) + +Use [our preset](#recommended) to get reasonable defaults: + +```json + "extends": [ + "eslint:recommended", + "plugin:react/recommended" + ] +``` + +If you are using the [new JSX transform from React 17](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports), extend [`react/jsx-runtime`](https://github.com/jsx-eslint/eslint-plugin-react/blob/c8917b0885094b5e4cc2a6f613f7fb6f16fe932e/index.js#L163-L176) in your eslint config (add `"plugin:react/jsx-runtime"` to `"extends"`) to disable the relevant rules. + +You should also specify settings that will be shared across all the plugin rules. ([More about eslint shared settings](https://eslint.org/docs/latest/use/configure/configuration-files#configuring-shared-settings)) + +```json5 +{ + "settings": { + "react": { + "createClass": "createReactClass", // Regex for Component Factory to use, + // default to "createReactClass" + "pragma": "React", // Pragma to use, default to "React" + "fragment": "Fragment", // Fragment to use (may be a property of ), default to "Fragment" + "version": "detect", // React version. "detect" automatically picks the version you have installed. + // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. + // Defaults to the "defaultVersion" setting and warns if missing, and to "detect" in the future + "defaultVersion": "", // Default React version to use when the version you have installed cannot be detected. + // If not provided, defaults to the latest React version. + "flowVersion": "0.53" // Flow version + }, + "propWrapperFunctions": [ + // The names of any function used to wrap propTypes, e.g. `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped. + "forbidExtraProps", + {"property": "freeze", "object": "Object"}, + {"property": "myFavoriteWrapper"}, + // for rules that check exact prop wrappers + {"property": "forbidExtraProps", "exact": true} + ], + "componentWrapperFunctions": [ + // The name of any function used to wrap components, e.g. Mobx `observer` function. If this isn't set, components wrapped by these functions will be skipped. + "observer", // `property` + {"property": "styled"}, // `object` is optional + {"property": "observer", "object": "Mobx"}, + {"property": "observer", "object": ""} // sets `object` to whatever value `settings.react.pragma` is set to + ], + "formComponents": [ + // Components used as alternatives to
for forms, eg. + "CustomForm", + {"name": "SimpleForm", "formAttribute": "endpoint"}, + {"name": "Form", "formAttribute": ["registerEndpoint", "loginEndpoint"]}, // allows specifying multiple properties if necessary + ], + "linkComponents": [ + // Components used as alternatives to for linking, eg. + "Hyperlink", + {"name": "MyLink", "linkAttribute": "to"}, + {"name": "Link", "linkAttribute": ["to", "href"]}, // allows specifying multiple properties if necessary + ] + } +} +``` + +If you do not use a preset you will need to specify individual rules and add extra configuration. + +Add "react" to the plugins section. + +```json +{ + "plugins": [ + "react" + ] +} +``` + +Enable JSX support. + +With `eslint` 2+ + +```json +{ + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + } +} +``` + +Enable the rules that you would like to use. + +```json + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + } +``` + +### Shareable configs + +#### Recommended + +This plugin exports a `recommended` configuration that enforces React good practices. + +To enable this configuration use the `extends` property in your `.eslintrc` config file: + +```json +{ + "extends": ["eslint:recommended", "plugin:react/recommended"] +} +``` + +See [`eslint` documentation](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending configuration files. + +#### All + +This plugin also exports an `all` configuration that includes every available rule. +This pairs well with the `eslint:all` rule. + +```json +{ + "plugins": [ + "react" + ], + "extends": ["eslint:all", "plugin:react/all"] +} +``` + +**Note**: These configurations will import `eslint-plugin-react` and enable JSX in [parser options](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options). + +## Configuration (new: `eslint.config.js`) + +From [`v8.21.0`](https://github.com/eslint/eslint/releases/tag/v8.21.0), eslint announced a new config system. +In the new system, `.eslintrc*` is no longer used. `eslint.config.js` would be the default config file name. +In eslint `v8`, the legacy system (`.eslintrc*`) would still be supported, while in eslint `v9`, only the new system would be supported. + +And from [`v8.23.0`](https://github.com/eslint/eslint/releases/tag/v8.23.0), eslint CLI starts to look up `eslint.config.js`. +**So, if your eslint is `>=8.23.0`, you're 100% ready to use the new config system.** + +You might want to check out the official blog posts, + +- +- +- + +and the [official docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new). + +### Plugin + +The default export of `eslint-plugin-react` is a plugin object. + +```js +const react = require('eslint-plugin-react'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], + plugins: { + react, + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + }, + }, + rules: { + // ... any rules you want + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + }, + // ... others are omitted for brevity + }, + … +]; +``` + +### Configuring shared settings + +Refer to the [official docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuring-shared-settings). + +The schema of the `settings.react` object would be identical to that of what's already described above in the legacy config section. + + +### Flat Configs + +This plugin exports 3 flat configs: + +- `flat.all` +- `flat.recommended` +- `flat['jsx-runtime']` + +The flat configs are available via the root plugin import. They will configure the plugin under the `react/` namespace and enable JSX in [`languageOptions.parserOptions`](https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options). + +```js +const reactPlugin = require('eslint-plugin-react'); + +module.exports = [ + … + reactPlugin.configs.flat.recommended, // This is not a plugin object, but a shareable config object + reactPlugin.configs.flat['jsx-runtime'], // Add this if you are using React 17+ + … +]; +``` + +You can of course add/override some properties. + +**Note**: Our shareable configs does not preconfigure `files` or [`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). +For most of the cases, you probably want to configure some properties by yourself. + +```js +const reactPlugin = require('eslint-plugin-react'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + ...reactPlugin.configs.flat.recommended, + languageOptions: { + ...reactPlugin.configs.flat.recommended.languageOptions, + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + … +]; +``` + +The above example is same as the example below, as the new config system is based on chaining. + +```js +const reactPlugin = require('eslint-plugin-react'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + ...reactPlugin.configs.flat.recommended, + }, + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + languageOptions: { + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + … +]; +``` + +## List of supported rules + + + +💼 [Configurations](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs) enabled in.\ +🚫 [Configurations](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs) disabled in.\ +🏃 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/latest/use/core-concepts#rule-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 | | | | | | +| [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 | | | | | | +| [forward-ref-uses-ref](docs/rules/forward-ref-uses-ref.md) | Require all forwardRef components include a ref parameter | | | | 💡 | | +| [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-spread-multi](docs/rules/jsx-props-no-spread-multi.md) | Disallow JSX prop spreading the same identifier multiple times | | | | | | +| [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 | | | | | | + + + +## Other useful plugins + +- Rules of Hooks: [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks) +- JSX accessibility: [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) +- React Native: [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native) + +## License + +`eslint-plugin-react` is licensed under the [MIT License](https://opensource.org/licenses/mit-license.php). + +[npm-url]: https://npmjs.org/package/eslint-plugin-react +[npm-image]: https://img.shields.io/npm/v/eslint-plugin-react.svg + +[status-url]: https://github.com/jsx-eslint/eslint-plugin-react/pulse +[status-image]: https://img.shields.io/github/last-commit/jsx-eslint/eslint-plugin-react.svg + +[tidelift-url]: https://tidelift.com/subscription/pkg/npm-eslint-plugin-react?utm_source=npm-eslint-plugin-react&utm_medium=referral&utm_campaign=readme +[tidelift-image]: https://tidelift.com/badges/package/npm/eslint-plugin-react?style=flat + +[package-url]: https://npmjs.org/package/eslint-plugin-react +[npm-version-svg]: https://versionbadg.es/jsx-eslint/eslint-plugin-react.svg + +[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/jsx-eslint/eslint-plugin-react +[actions-url]: https://github.com/jsx-eslint/eslint-plugin-react/actions diff --git a/node_modules/eslint-plugin-react/configs/all.js b/node_modules/eslint-plugin-react/configs/all.js new file mode 100644 index 0000000000000000000000000000000000000000..3123c5bd5ddb97e74fea7d49fa8bed16980db176 --- /dev/null +++ b/node_modules/eslint-plugin-react/configs/all.js @@ -0,0 +1,13 @@ +'use strict'; + +const plugin = require('..'); + +const legacyConfig = plugin.configs.all; + +module.exports = { + plugins: { react: plugin }, + rules: legacyConfig.rules, + languageOptions: { parserOptions: legacyConfig.parserOptions }, +}; + +Object.defineProperty(module.exports, 'languageOptions', { enumerable: false }); diff --git a/node_modules/eslint-plugin-react/configs/jsx-runtime.js b/node_modules/eslint-plugin-react/configs/jsx-runtime.js new file mode 100644 index 0000000000000000000000000000000000000000..f5cd1785875141ef2e1fb1b19a67aea04a5f410e --- /dev/null +++ b/node_modules/eslint-plugin-react/configs/jsx-runtime.js @@ -0,0 +1,13 @@ +'use strict'; + +const plugin = require('..'); + +const legacyConfig = plugin.configs['jsx-runtime']; + +module.exports = { + plugins: { react: plugin }, + rules: legacyConfig.rules, + languageOptions: { parserOptions: legacyConfig.parserOptions }, +}; + +Object.defineProperty(module.exports, 'languageOptions', { enumerable: false }); diff --git a/node_modules/eslint-plugin-react/configs/recommended.js b/node_modules/eslint-plugin-react/configs/recommended.js new file mode 100644 index 0000000000000000000000000000000000000000..19cbda4cf4f48324ad3fed5330c577239b0a48fd --- /dev/null +++ b/node_modules/eslint-plugin-react/configs/recommended.js @@ -0,0 +1,13 @@ +'use strict'; + +const plugin = require('..'); + +const legacyConfig = plugin.configs.recommended; + +module.exports = { + plugins: { react: plugin }, + rules: legacyConfig.rules, + languageOptions: { parserOptions: legacyConfig.parserOptions }, +}; + +Object.defineProperty(module.exports, 'languageOptions', { enumerable: false }); diff --git a/node_modules/eslint-plugin-react/index.d.ts b/node_modules/eslint-plugin-react/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..54efa32708732dd7108704140877cf727d9fe9f4 --- /dev/null +++ b/node_modules/eslint-plugin-react/index.d.ts @@ -0,0 +1,187 @@ +export = plugin; +/** @typedef {{ plugins: { react: typeof plugin }, rules: import('eslint').Linter.RulesRecord, languageOptions: { parserOptions: import('eslint').Linter.ParserOptions } }} ReactFlatConfig */ +/** @type {{ deprecatedRules: typeof deprecatedRules, rules: typeof allRules, configs: typeof configs & { flat: Record }}} */ +declare const plugin: { + deprecatedRules: typeof deprecatedRules; + rules: typeof allRules; + configs: typeof configs & { + flat: Record; + }; +}; +declare namespace plugin { + export { ReactFlatConfig }; +} +/** @type {Partial} */ +declare const deprecatedRules: Partial; +declare const allRules: { + 'boolean-prop-naming': import("eslint").Rule.RuleModule; + 'button-has-type': import("eslint").Rule.RuleModule; + 'checked-requires-onchange-or-readonly': import("eslint").Rule.RuleModule; + 'default-props-match-prop-types': import("eslint").Rule.RuleModule; + 'destructuring-assignment': import("eslint").Rule.RuleModule; + 'display-name': import("eslint").Rule.RuleModule; + 'forbid-component-props': import("eslint").Rule.RuleModule; + 'forbid-dom-props': import("eslint").Rule.RuleModule; + 'forbid-elements': import("eslint").Rule.RuleModule; + 'forbid-foreign-prop-types': import("eslint").Rule.RuleModule; + 'forbid-prop-types': import("eslint").Rule.RuleModule; + 'forward-ref-uses-ref': import("eslint").Rule.RuleModule; + 'function-component-definition': import("eslint").Rule.RuleModule; + 'hook-use-state': import("eslint").Rule.RuleModule; + 'iframe-missing-sandbox': import("eslint").Rule.RuleModule; + 'jsx-boolean-value': import("eslint").Rule.RuleModule; + 'jsx-child-element-spacing': import("eslint").Rule.RuleModule; + 'jsx-closing-bracket-location': import("eslint").Rule.RuleModule; + 'jsx-closing-tag-location': import("eslint").Rule.RuleModule; + 'jsx-curly-spacing': import("eslint").Rule.RuleModule; + 'jsx-curly-newline': import("eslint").Rule.RuleModule; + 'jsx-equals-spacing': import("eslint").Rule.RuleModule; + 'jsx-filename-extension': import("eslint").Rule.RuleModule; + 'jsx-first-prop-new-line': import("eslint").Rule.RuleModule; + 'jsx-handler-names': import("eslint").Rule.RuleModule; + 'jsx-indent': import("eslint").Rule.RuleModule; + 'jsx-indent-props': import("eslint").Rule.RuleModule; + 'jsx-key': import("eslint").Rule.RuleModule; + 'jsx-max-depth': import("eslint").Rule.RuleModule; + 'jsx-max-props-per-line': import("eslint").Rule.RuleModule; + 'jsx-newline': import("eslint").Rule.RuleModule; + 'jsx-no-bind': import("eslint").Rule.RuleModule; + 'jsx-no-comment-textnodes': import("eslint").Rule.RuleModule; + 'jsx-no-constructed-context-values': import("eslint").Rule.RuleModule; + 'jsx-no-duplicate-props': import("eslint").Rule.RuleModule; + 'jsx-no-leaked-render': import("eslint").Rule.RuleModule; + 'jsx-no-literals': import("eslint").Rule.RuleModule; + 'jsx-no-script-url': import("eslint").Rule.RuleModule; + 'jsx-no-target-blank': import("eslint").Rule.RuleModule; + 'jsx-no-useless-fragment': import("eslint").Rule.RuleModule; + 'jsx-one-expression-per-line': import("eslint").Rule.RuleModule; + 'jsx-no-undef': import("eslint").Rule.RuleModule; + 'jsx-curly-brace-presence': import("eslint").Rule.RuleModule; + 'jsx-pascal-case': import("eslint").Rule.RuleModule; + 'jsx-fragments': import("eslint").Rule.RuleModule; + 'jsx-props-no-multi-spaces': import("eslint").Rule.RuleModule; + 'jsx-props-no-spreading': import("eslint").Rule.RuleModule; + 'jsx-props-no-spread-multi': import("eslint").Rule.RuleModule; + 'jsx-sort-default-props': import("eslint").Rule.RuleModule; + 'jsx-sort-props': import("eslint").Rule.RuleModule; + 'jsx-space-before-closing': import("eslint").Rule.RuleModule; + 'jsx-tag-spacing': import("eslint").Rule.RuleModule; + 'jsx-uses-react': import("eslint").Rule.RuleModule; + 'jsx-uses-vars': import("eslint").Rule.RuleModule; + 'jsx-wrap-multilines': import("eslint").Rule.RuleModule; + 'no-invalid-html-attribute': import("eslint").Rule.RuleModule; + 'no-access-state-in-setstate': import("eslint").Rule.RuleModule; + 'no-adjacent-inline-elements': import("eslint").Rule.RuleModule; + 'no-array-index-key': import("eslint").Rule.RuleModule; + 'no-arrow-function-lifecycle': import("eslint").Rule.RuleModule; + 'no-children-prop': import("eslint").Rule.RuleModule; + 'no-danger': import("eslint").Rule.RuleModule; + 'no-danger-with-children': import("eslint").Rule.RuleModule; + 'no-deprecated': import("eslint").Rule.RuleModule; + 'no-did-mount-set-state': import("eslint").Rule.RuleModule; + 'no-did-update-set-state': import("eslint").Rule.RuleModule; + 'no-direct-mutation-state': import("eslint").Rule.RuleModule; + 'no-find-dom-node': import("eslint").Rule.RuleModule; + 'no-is-mounted': import("eslint").Rule.RuleModule; + 'no-multi-comp': import("eslint").Rule.RuleModule; + 'no-namespace': import("eslint").Rule.RuleModule; + 'no-set-state': import("eslint").Rule.RuleModule; + 'no-string-refs': import("eslint").Rule.RuleModule; + 'no-redundant-should-component-update': import("eslint").Rule.RuleModule; + 'no-render-return-value': import("eslint").Rule.RuleModule; + 'no-this-in-sfc': import("eslint").Rule.RuleModule; + 'no-typos': import("eslint").Rule.RuleModule; + 'no-unescaped-entities': import("eslint").Rule.RuleModule; + 'no-unknown-property': import("eslint").Rule.RuleModule; + 'no-unsafe': import("eslint").Rule.RuleModule; + 'no-unstable-nested-components': import("eslint").Rule.RuleModule; + 'no-unused-class-component-methods': import("eslint").Rule.RuleModule; + 'no-unused-prop-types': import("eslint").Rule.RuleModule; + 'no-unused-state': import("eslint").Rule.RuleModule; + 'no-object-type-as-default-prop': import("eslint").Rule.RuleModule; + 'no-will-update-set-state': import("eslint").Rule.RuleModule; + 'prefer-es6-class': import("eslint").Rule.RuleModule; + 'prefer-exact-props': import("eslint").Rule.RuleModule; + 'prefer-read-only-props': import("eslint").Rule.RuleModule; + 'prefer-stateless-function': import("eslint").Rule.RuleModule; + 'prop-types': import("eslint").Rule.RuleModule; + 'react-in-jsx-scope': import("eslint").Rule.RuleModule; + 'require-default-props': import("eslint").Rule.RuleModule; + 'require-optimization': import("eslint").Rule.RuleModule; + 'require-render-return': import("eslint").Rule.RuleModule; + 'self-closing-comp': import("eslint").Rule.RuleModule; + 'sort-comp': import("eslint").Rule.RuleModule; + 'sort-default-props': import("eslint").Rule.RuleModule; + 'sort-prop-types': import("eslint").Rule.RuleModule; + 'state-in-constructor': import("eslint").Rule.RuleModule; + 'static-property-placement': import("eslint").Rule.RuleModule; + 'style-prop-object': import("eslint").Rule.RuleModule; + 'void-dom-elements-no-children': import("eslint").Rule.RuleModule; +}; +declare const configs: { + recommended: { + plugins: ["react"]; + parserOptions: { + ecmaFeatures: { + jsx: boolean; + }; + }; + rules: { + 'react/display-name': 2; + 'react/jsx-key': 2; + 'react/jsx-no-comment-textnodes': 2; + 'react/jsx-no-duplicate-props': 2; + 'react/jsx-no-target-blank': 2; + 'react/jsx-no-undef': 2; + 'react/jsx-uses-react': 2; + 'react/jsx-uses-vars': 2; + 'react/no-children-prop': 2; + 'react/no-danger-with-children': 2; + 'react/no-deprecated': 2; + 'react/no-direct-mutation-state': 2; + 'react/no-find-dom-node': 2; + 'react/no-is-mounted': 2; + 'react/no-render-return-value': 2; + 'react/no-string-refs': 2; + 'react/no-unescaped-entities': 2; + 'react/no-unknown-property': 2; + 'react/no-unsafe': 0; + 'react/prop-types': 2; + 'react/react-in-jsx-scope': 2; + 'react/require-render-return': 2; + }; + }; + all: { + plugins: ["react"]; + parserOptions: { + ecmaFeatures: { + jsx: boolean; + }; + }; + rules: Record<"boolean-prop-naming" | "button-has-type" | "checked-requires-onchange-or-readonly" | "default-props-match-prop-types" | "destructuring-assignment" | "display-name" | "forbid-component-props" | "forbid-dom-props" | "forbid-elements" | "forbid-foreign-prop-types" | "forbid-prop-types" | "prop-types" | "forward-ref-uses-ref" | "function-component-definition" | "hook-use-state" | "iframe-missing-sandbox" | "jsx-boolean-value" | "jsx-child-element-spacing" | "jsx-closing-bracket-location" | "jsx-closing-tag-location" | "jsx-curly-spacing" | "jsx-curly-newline" | "jsx-equals-spacing" | "jsx-filename-extension" | "jsx-first-prop-new-line" | "jsx-handler-names" | "jsx-indent" | "jsx-indent-props" | "jsx-key" | "jsx-max-depth" | "jsx-max-props-per-line" | "jsx-newline" | "jsx-no-bind" | "jsx-no-comment-textnodes" | "jsx-no-constructed-context-values" | "jsx-no-duplicate-props" | "jsx-no-leaked-render" | "jsx-no-literals" | "jsx-no-script-url" | "jsx-no-target-blank" | "jsx-no-useless-fragment" | "jsx-one-expression-per-line" | "jsx-no-undef" | "jsx-curly-brace-presence" | "jsx-pascal-case" | "jsx-fragments" | "jsx-props-no-multi-spaces" | "jsx-props-no-spreading" | "jsx-props-no-spread-multi" | "sort-default-props" | "jsx-sort-default-props" | "jsx-sort-props" | "jsx-tag-spacing" | "jsx-space-before-closing" | "jsx-uses-react" | "jsx-uses-vars" | "jsx-wrap-multilines" | "no-invalid-html-attribute" | "no-access-state-in-setstate" | "no-adjacent-inline-elements" | "no-array-index-key" | "no-arrow-function-lifecycle" | "no-children-prop" | "no-danger" | "no-danger-with-children" | "no-deprecated" | "no-direct-mutation-state" | "no-find-dom-node" | "no-is-mounted" | "no-multi-comp" | "no-namespace" | "no-set-state" | "no-string-refs" | "no-redundant-should-component-update" | "no-render-return-value" | "no-this-in-sfc" | "no-typos" | "no-unescaped-entities" | "no-unknown-property" | "no-unsafe" | "no-unstable-nested-components" | "no-unused-class-component-methods" | "no-unused-prop-types" | "no-unused-state" | "no-object-type-as-default-prop" | "prefer-es6-class" | "prefer-exact-props" | "prefer-read-only-props" | "prefer-stateless-function" | "react-in-jsx-scope" | "require-default-props" | "require-optimization" | "require-render-return" | "self-closing-comp" | "sort-comp" | "sort-prop-types" | "state-in-constructor" | "static-property-placement" | "style-prop-object" | "void-dom-elements-no-children" | "no-did-mount-set-state" | "no-did-update-set-state" | "no-will-update-set-state", 2 | "error">; + }; + 'jsx-runtime': { + plugins: ["react"]; + parserOptions: { + ecmaFeatures: { + jsx: boolean; + }; + jsxPragma: any; + }; + rules: { + 'react/react-in-jsx-scope': 0; + 'react/jsx-uses-react': 0; + }; + }; + flat: Record; +}; +type ReactFlatConfig = { + plugins: { + react: typeof plugin; + }; + rules: import('eslint').Linter.RulesRecord; + languageOptions: { + parserOptions: import('eslint').Linter.ParserOptions; + }; +}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/index.d.ts.map b/node_modules/eslint-plugin-react/index.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..ac9e5feb81771f0ca9b6ea14e1bf818ceac56a85 --- /dev/null +++ b/node_modules/eslint-plugin-react/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AAiGA,8LAA8L;AAE9L,sJAAsJ;AACtJ,sBADW;IAAE,eAAe,EAAE,OAAO,eAAe,CAAC;IAAC,KAAK,EAAE,OAAO,QAAQ,CAAC;IAAC,OAAO,EAAE,OAAO,OAAO,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;KAAE,CAAA;CAAC,CAKhJ;;;;AAhFF,uCAAuC;AACvC,+BADW,OAAO,CAAC,OAAO,QAAQ,CAAC,CAC2C;AApB9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAwC;AAgCxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuDmB,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;EAGhD;;aAEuB;QAAE,KAAK,EAAE,OAAO,MAAM,CAAA;KAAE;WAAS,OAAO,QAAQ,EAAE,MAAM,CAAC,WAAW;qBAAmB;QAAE,aAAa,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAA;KAAE"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/index.js b/node_modules/eslint-plugin-react/index.js new file mode 100644 index 0000000000000000000000000000000000000000..64ff9bbdc46ae8ce952969e9deafc11d8aeda46f --- /dev/null +++ b/node_modules/eslint-plugin-react/index.js @@ -0,0 +1,125 @@ +'use strict'; + +const fromEntries = require('object.fromentries'); +const entries = require('object.entries'); + +const allRules = require('./lib/rules'); + +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])); +} + +/** @type {Partial} */ +const activeRules = filterRules(allRules, (rule) => !rule.meta.deprecated); +/** @type {Record} */ +const activeRulesConfig = configureAsError(activeRules); + +/** @type {Partial} */ +const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated); + +/** @type {['react']} */ +// for legacy config system +const plugins = [ + 'react', +]; + +// TODO: with TS 4.5+, inline this +const SEVERITY_ERROR = /** @type {2} */ (2); +const SEVERITY_OFF = /** @type {0} */ (0); + +const configs = { + recommended: { + plugins, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'react/display-name': SEVERITY_ERROR, + 'react/jsx-key': SEVERITY_ERROR, + 'react/jsx-no-comment-textnodes': SEVERITY_ERROR, + 'react/jsx-no-duplicate-props': SEVERITY_ERROR, + 'react/jsx-no-target-blank': SEVERITY_ERROR, + 'react/jsx-no-undef': SEVERITY_ERROR, + 'react/jsx-uses-react': SEVERITY_ERROR, + 'react/jsx-uses-vars': SEVERITY_ERROR, + 'react/no-children-prop': SEVERITY_ERROR, + 'react/no-danger-with-children': SEVERITY_ERROR, + 'react/no-deprecated': SEVERITY_ERROR, + 'react/no-direct-mutation-state': SEVERITY_ERROR, + 'react/no-find-dom-node': SEVERITY_ERROR, + 'react/no-is-mounted': SEVERITY_ERROR, + 'react/no-render-return-value': SEVERITY_ERROR, + 'react/no-string-refs': SEVERITY_ERROR, + 'react/no-unescaped-entities': SEVERITY_ERROR, + 'react/no-unknown-property': SEVERITY_ERROR, + 'react/no-unsafe': SEVERITY_OFF, + 'react/prop-types': SEVERITY_ERROR, + 'react/react-in-jsx-scope': SEVERITY_ERROR, + 'react/require-render-return': SEVERITY_ERROR, + }, + }, + all: { + plugins, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + rules: activeRulesConfig, + }, + 'jsx-runtime': { + plugins, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxPragma: null, // for @typescript/eslint-parser + }, + rules: { + 'react/react-in-jsx-scope': SEVERITY_OFF, + 'react/jsx-uses-react': SEVERITY_OFF, + }, + }, + flat: /** @type {Record} */ ({ + __proto__: null, + }), +}; + +/** @typedef {{ plugins: { react: typeof plugin }, rules: import('eslint').Linter.RulesRecord, languageOptions: { parserOptions: import('eslint').Linter.ParserOptions } }} ReactFlatConfig */ + +/** @type {{ deprecatedRules: typeof deprecatedRules, rules: typeof allRules, configs: typeof configs & { flat: Record }}} */ +const plugin = { + deprecatedRules, + rules: allRules, + configs, +}; + +Object.assign(configs.flat, { + recommended: { + plugins: { react: plugin }, + rules: configs.recommended.rules, + languageOptions: { parserOptions: configs.recommended.parserOptions }, + }, + all: { + plugins: { react: plugin }, + rules: configs.all.rules, + languageOptions: { parserOptions: configs.all.parserOptions }, + }, + 'jsx-runtime': { + plugins: { react: plugin }, + rules: configs['jsx-runtime'].rules, + languageOptions: { parserOptions: configs['jsx-runtime'].parserOptions }, + }, +}); + +module.exports = plugin; diff --git a/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5434adc3946d84f7cf733d01ecebb6d48fb6c3c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=boolean-prop-naming.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..dfec8ee982cfc2aeed289fadb75bc4aa829a5395 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"boolean-prop-naming.d.ts","sourceRoot":"","sources":["boolean-prop-naming.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js new file mode 100644 index 0000000000000000000000000000000000000000..afa844a903b0916f9ba923dd01a59169f43262f9 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js @@ -0,0 +1,426 @@ +/** + * @fileoverview Enforces consistent naming for boolean props + * @author Ev Haus + */ + +'use strict'; + +const flatMap = require('array.prototype.flatmap'); +const values = require('object.values'); + +const Components = require('../util/Components'); +const propsUtil = require('../util/props'); +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const propWrapperUtil = require('../util/propWrapper'); +const report = require('../util/report'); +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +/** + * Checks if prop is nested + * @param {Object} prop Property object, single prop type declaration + * @returns {boolean} + */ +function nestedPropTypes(prop) { + return ( + prop.type === 'Property' + && astUtil.isCallExpression(prop.value) + ); +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + category: 'Stylistic Issues', + description: 'Enforces consistent naming for boolean props', + recommended: false, + url: docsUrl('boolean-prop-naming'), + }, + + messages, + + schema: [{ + additionalProperties: false, + properties: { + propTypeNames: { + items: { + type: 'string', + }, + minItems: 1, + type: 'array', + uniqueItems: true, + }, + rule: { + default: '^(is|has)[A-Z]([A-Za-z0-9]?)+', + minLength: 1, + type: 'string', + }, + message: { + minLength: 1, + type: 'string', + }, + validateNested: { + default: false, + type: 'boolean', + }, + }, + type: 'object', + }], + }, + + create: Components.detect((context, components, utils) => { + const config = context.options[0] || {}; + const rule = config.rule ? new RegExp(config.rule) : null; + const propTypeNames = config.propTypeNames || ['bool']; + + // Remembers all Flowtype object definitions + const objectTypeAnnotations = new Map(); + + /** + * Returns the prop key to ensure we handle the following cases: + * propTypes: { + * full: React.PropTypes.bool, + * short: PropTypes.bool, + * direct: bool, + * required: PropTypes.bool.isRequired + * } + * @param {Object} node The node we're getting the name of + * @returns {string | null} + */ + function getPropKey(node) { + // Check for `ExperimentalSpreadProperty` (eslint 3/4) and `SpreadElement` (eslint 5) + // so we can skip validation of those fields. + // Otherwise it will look for `node.value.property` which doesn't exist and breaks eslint. + if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') { + return null; + } + if (node.value && node.value.property) { + const name = node.value.property.name; + if (name === 'isRequired') { + if (node.value.object && node.value.object.property) { + return node.value.object.property.name; + } + return null; + } + return name; + } + if (node.value && node.value.type === 'Identifier') { + return node.value.name; + } + return null; + } + + /** + * Returns the name of the given node (prop) + * @param {Object} node The node we're getting the name of + * @returns {string} + */ + function getPropName(node) { + // Due to this bug https://github.com/babel/babel-eslint/issues/307 + // 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 getSourceCode(context).getFirstToken(node).value; + } + + return node.key.name; + } + + /** + * Checks if prop is declared in flow way + * @param {Object} prop Property object, single prop type declaration + * @returns {boolean} + */ + function flowCheck(prop) { + return ( + prop.type === 'ObjectTypeProperty' + && prop.value.type === 'BooleanTypeAnnotation' + && rule.test(getPropName(prop)) === false + ); + } + + /** + * Checks if prop is declared in regular way + * @param {Object} prop Property object, single prop type declaration + * @returns {boolean} + */ + function regularCheck(prop) { + const propKey = getPropKey(prop); + return ( + propKey + && propTypeNames.indexOf(propKey) >= 0 + && rule.test(getPropName(prop)) === false + ); + } + + function tsCheck(prop) { + if (prop.type !== 'TSPropertySignature') return false; + const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation; + return ( + typeAnnotation + && typeAnnotation.type === 'TSBooleanKeyword' + && rule.test(getPropName(prop)) === false + ); + } + + /** + * Runs recursive check on all proptypes + * @param {Array} proptypes A list of Property object (for each proptype defined) + * @param {Function} addInvalidProp callback to run for each error + */ + function runCheck(proptypes, addInvalidProp) { + if (proptypes) { + proptypes.forEach((prop) => { + if (config.validateNested && nestedPropTypes(prop)) { + runCheck(prop.value.arguments[0].properties, addInvalidProp); + return; + } + if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) { + addInvalidProp(prop); + } + }); + } + } + + /** + * Checks and mark props with invalid naming + * @param {Object} node The component node we're testing + * @param {Array} proptypes A list of Property object (for each proptype defined) + */ + function validatePropNaming(node, proptypes) { + const component = components.get(node) || node; + const invalidProps = component.invalidProps || []; + + runCheck(proptypes, (prop) => { + invalidProps.push(prop); + }); + + components.set(node, { + invalidProps, + }); + } + + /** + * Reports invalid prop naming + * @param {Object} component The component to process + */ + function reportInvalidNaming(component) { + component.invalidProps.forEach((propNode) => { + const propName = getPropName(propNode); + report(context, config.message || messages.patternMismatch, !config.message && 'patternMismatch', { + node: propNode, + data: { + component: propName, + propName, + pattern: config.rule, + }, + }); + }); + } + + function checkPropWrapperArguments(node, args) { + if (!node || !Array.isArray(args)) { + return; + } + args.filter((arg) => arg.type === 'ObjectExpression').forEach((object) => validatePropNaming(node, object.properties)); + } + + function getComponentTypeAnnotation(component) { + // If this is a functional component that uses a global type, check it + if ( + (component.node.type === 'FunctionDeclaration' || component.node.type === 'ArrowFunctionExpression') + && component.node.params + && component.node.params.length > 0 + && component.node.params[0].typeAnnotation + ) { + return component.node.params[0].typeAnnotation.typeAnnotation; + } + + if ( + !component.node.parent + || component.node.parent.type !== 'VariableDeclarator' + || !component.node.parent.id + || component.node.parent.id.type !== 'Identifier' + || !component.node.parent.id.typeAnnotation + || !component.node.parent.id.typeAnnotation.typeAnnotation + ) { + return; + } + + const annotationTypeArguments = propsUtil.getTypeArguments( + component.node.parent.id.typeAnnotation.typeAnnotation + ); + if ( + annotationTypeArguments && ( + annotationTypeArguments.type === 'TSTypeParameterInstantiation' + || annotationTypeArguments.type === 'TypeParameterInstantiation' + ) + ) { + return annotationTypeArguments.params.find( + (param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation' + ); + } + } + + function findAllTypeAnnotations(identifier, node) { + if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation' || node.type === 'TSInterfaceBody') { + const currentNode = [].concat( + objectTypeAnnotations.get(identifier.name) || [], + node + ); + objectTypeAnnotations.set(identifier.name, currentNode); + } else if ( + node.type === 'TSParenthesizedType' + && ( + node.typeAnnotation.type === 'TSIntersectionType' + || node.typeAnnotation.type === 'TSUnionType' + ) + ) { + node.typeAnnotation.types.forEach((type) => { + findAllTypeAnnotations(identifier, type); + }); + } else if ( + node.type === 'TSIntersectionType' + || node.type === 'TSUnionType' + || node.type === 'IntersectionTypeAnnotation' + || node.type === 'UnionTypeAnnotation' + ) { + node.types.forEach((type) => { + findAllTypeAnnotations(identifier, type); + }); + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + 'ClassProperty, PropertyDefinition'(node) { + if (!rule || !propsUtil.isPropTypesDeclaration(node)) { + return; + } + if ( + node.value + && astUtil.isCallExpression(node.value) + && propWrapperUtil.isPropWrapperFunction( + context, + getText(context, node.value.callee) + ) + ) { + checkPropWrapperArguments(node, node.value.arguments); + } + if (node.value && node.value.properties) { + validatePropNaming(node, node.value.properties); + } + if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) { + validatePropNaming(node, node.typeAnnotation.typeAnnotation.properties); + } + }, + + MemberExpression(node) { + if (!rule || !propsUtil.isPropTypesDeclaration(node)) { + return; + } + const component = utils.getRelatedComponent(node); + if (!component || !node.parent.right) { + return; + } + const right = node.parent.right; + if ( + astUtil.isCallExpression(right) + && propWrapperUtil.isPropWrapperFunction( + context, + getText(context, right.callee) + ) + ) { + checkPropWrapperArguments(component.node, right.arguments); + return; + } + validatePropNaming(component.node, node.parent.right.properties); + }, + + ObjectExpression(node) { + if (!rule) { + return; + } + + // Search for the proptypes declaration + node.properties.forEach((property) => { + if (!propsUtil.isPropTypesDeclaration(property)) { + return; + } + validatePropNaming(node, property.value.properties); + }); + }, + + TypeAlias(node) { + findAllTypeAnnotations(node.id, node.right); + }, + + TSTypeAliasDeclaration(node) { + findAllTypeAnnotations(node.id, node.typeAnnotation); + }, + + TSInterfaceDeclaration(node) { + findAllTypeAnnotations(node.id, node.body); + }, + + // eslint-disable-next-line object-shorthand + 'Program:exit'() { + if (!rule) { + return; + } + + values(components.list()).forEach((component) => { + const annotation = getComponentTypeAnnotation(component); + + if (annotation) { + let propType; + if (annotation.type === 'GenericTypeAnnotation') { + propType = objectTypeAnnotations.get(annotation.id.name); + } else if (annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'TSTypeLiteral') { + propType = annotation; + } else if (annotation.type === 'TSTypeReference') { + propType = objectTypeAnnotations.get(annotation.typeName.name); + } else if (annotation.type === 'TSIntersectionType') { + propType = flatMap(annotation.types, (type) => ( + type.type === 'TSTypeReference' + ? objectTypeAnnotations.get(type.typeName.name) + : type + )); + } + + if (propType) { + [].concat(propType).filter(Boolean).forEach((prop) => { + validatePropNaming( + component.node, + prop.properties || prop.members || prop.body + ); + }); + } + } + + if (component.invalidProps && component.invalidProps.length > 0) { + reportInvalidNaming(component); + } + }); + + // Reset cache + objectTypeAnnotations.clear(); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts b/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e3136b9a50279e3989993d8df3bf4767f6f9480 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=button-has-type.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..9c54047e0dbc648d6d8130fc0d2d01f5281237e4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"button-has-type.d.ts","sourceRoot":"","sources":["button-has-type.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/button-has-type.js b/node_modules/eslint-plugin-react/lib/rules/button-has-type.js new file mode 100644 index 0000000000000000000000000000000000000000..a6fd41cbfebb169eca66ec9ad22401cfd6c675b0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/button-has-type.js @@ -0,0 +1,169 @@ +/** + * @fileoverview Forbid "button" element without an explicit "type" attribute + * @author Filipp Riabchun + */ + +'use strict'; + +const getProp = require('jsx-ast-utils/getProp'); +const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue'); +const docsUrl = require('../util/docsUrl'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const optionDefaults = { + button: true, + submit: true, + reset: true, +}; + +const messages = { + missingType: 'Missing an explicit type attribute for button', + complexType: 'The button type attribute must be specified by a static string or a trivial ternary expression', + invalidValue: '"{{value}}" is an invalid value for button type attribute', + forbiddenValue: '"{{value}}" is an invalid value for button type attribute', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of `button` elements without an explicit `type` attribute', + category: 'Possible Errors', + recommended: false, + url: docsUrl('button-has-type'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + button: { + default: optionDefaults.button, + type: 'boolean', + }, + submit: { + default: optionDefaults.submit, + type: 'boolean', + }, + reset: { + default: optionDefaults.reset, + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = Object.assign({}, optionDefaults, context.options[0]); + + function reportMissing(node) { + report(context, messages.missingType, 'missingType', { + node, + }); + } + + function reportComplex(node) { + report(context, messages.complexType, 'complexType', { + node, + }); + } + + function checkValue(node, value) { + if (!(value in configuration)) { + report(context, messages.invalidValue, 'invalidValue', { + node, + data: { + value, + }, + }); + } else if (!configuration[value]) { + report(context, messages.forbiddenValue, 'forbiddenValue', { + node, + data: { + value, + }, + }); + } + } + + function checkExpression(node, expression) { + switch (expression.type) { + case 'Literal': + checkValue(node, expression.value); + return; + case 'TemplateLiteral': + if (expression.expressions.length === 0) { + checkValue(node, expression.quasis[0].value.raw); + } else { + reportComplex(expression); + } + return; + case 'ConditionalExpression': + checkExpression(node, expression.consequent); + checkExpression(node, expression.alternate); + return; + default: + reportComplex(expression); + } + } + + return { + JSXElement(node) { + if (node.openingElement.name.name !== 'button') { + return; + } + + const typeProp = getProp(node.openingElement.attributes, 'type'); + + if (!typeProp) { + reportMissing(node); + return; + } + + if (typeProp.value && typeProp.value.type === 'JSXExpressionContainer') { + checkExpression(node, typeProp.value.expression); + return; + } + + const propValue = getLiteralPropValue(typeProp); + checkValue(node, propValue); + }, + CallExpression(node) { + if (!isCreateElement(context, node) || node.arguments.length < 1) { + return; + } + + if (node.arguments[0].type !== 'Literal' || node.arguments[0].value !== 'button') { + return; + } + + if (!node.arguments[1] || node.arguments[1].type !== 'ObjectExpression') { + reportMissing(node); + return; + } + + const props = node.arguments[1].properties; + const typeProp = props.find((prop) => ( + 'key' in prop + && prop.key + && 'name' in prop.key + && prop.key.name === 'type' + )); + + if (!typeProp) { + reportMissing(node); + return; + } + + checkExpression(node, 'value' in typeProp ? typeProp.value : undefined); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed939d3dceb8e4a5164097a01e54dae0006dab3b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=checked-requires-onchange-or-readonly.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..6cd34eea9eda47674a04eedb015e52140074e942 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"checked-requires-onchange-or-readonly.d.ts","sourceRoot":"","sources":["checked-requires-onchange-or-readonly.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js new file mode 100644 index 0000000000000000000000000000000000000000..d67449ea515394457c6aec4d44c6d024b1b60666 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js @@ -0,0 +1,142 @@ +/** + * @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: false, + ignoreExclusiveCheckedAttribute: false, +}; + +/** + * @param {object[]} 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] + : [] + ) + ) + ); +} + +/** @type {import('eslint').Rule.RuleModule} */ +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(context, node)) { + 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/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5f5b51672732ce6f708c7d9732c85e574987bed --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=default-props-match-prop-types.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..7a83ff067a500c861f6fa1dd6b667908e47aabaa --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"default-props-match-prop-types.d.ts","sourceRoot":"","sources":["default-props-match-prop-types.js"],"names":[],"mappings":"wBAuBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js new file mode 100644 index 0000000000000000000000000000000000000000..95b341650a00760a5718c0e2086d435c31dce95e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js @@ -0,0 +1,109 @@ +/** + * @fileOverview Enforce all defaultProps are defined in propTypes + * @author Vitor Balocco + * @author Roy Sutton + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + requiredHasDefault: 'defaultProp "{{name}}" defined for isRequired propType.', + defaultHasNoType: 'defaultProp "{{name}}" has no corresponding propTypes declaration.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce all defaultProps have a corresponding non-required PropType', + category: 'Best Practices', + url: docsUrl('default-props-match-prop-types'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowRequiredDefaults: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create: Components.detect((context, components) => { + const configuration = context.options[0] || {}; + const allowRequiredDefaults = configuration.allowRequiredDefaults || false; + + /** + * Reports all defaultProps passed in that don't have an appropriate propTypes counterpart. + * @param {Object[]} propTypes Array of propTypes to check. + * @param {Object} defaultProps Object of defaultProps to check. Keys are the props names. + * @return {void} + */ + function reportInvalidDefaultProps(propTypes, defaultProps) { + // If this defaultProps is "unresolved" or the propTypes is undefined, then we should ignore + // this component and not report any errors for it, to avoid false-positives with e.g. + // external defaultProps/propTypes declarations or spread operators. + if (defaultProps === 'unresolved' || !propTypes || Object.keys(propTypes).length === 0) { + return; + } + + Object.keys(defaultProps).forEach((defaultPropName) => { + const defaultProp = defaultProps[defaultPropName]; + const prop = propTypes[defaultPropName]; + + if (prop && (allowRequiredDefaults || !prop.isRequired)) { + return; + } + + if (prop) { + report(context, messages.requiredHasDefault, 'requiredHasDefault', { + node: defaultProp.node, + data: { + name: defaultPropName, + }, + }); + } else { + report(context, messages.defaultHasNoType, 'defaultHasNoType', { + node: defaultProp.node, + data: { + name: defaultPropName, + }, + }); + } + }); + } + + // -------------------------------------------------------------------------- + // Public API + // -------------------------------------------------------------------------- + + return { + 'Program:exit'() { + // If no defaultProps could be found, we don't report anything. + values(components.list()) + .filter((component) => component.defaultProps) + .forEach((component) => { + reportInvalidDefaultProps( + component.declaredPropTypes, + component.defaultProps || {} + ); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b7348c6e114d3bcff77b479ee566279cf43d852 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=destructuring-assignment.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..686fa164fa741d5b5bb84bb5ce6d738c4690aaff --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"destructuring-assignment.d.ts","sourceRoot":"","sources":["destructuring-assignment.js"],"names":[],"mappings":"wBA2DW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js new file mode 100644 index 0000000000000000000000000000000000000000..6e254f772fddb2a23e6e9d5d52bdb53b2626480a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js @@ -0,0 +1,319 @@ +/** + * @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context. + */ + +'use strict'; + +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 getScope = eslintUtil.getScope; +const getText = eslintUtil.getText; + +const DEFAULT_OPTION = 'always'; + +function createSFCParams() { + const queue = []; + + return { + push(params) { + queue.unshift(params); + }, + pop() { + queue.shift(); + }, + propsName() { + const found = queue.find((params) => { + const props = params[0]; + return props && !props.destructuring && props.name; + }); + return found && found[0] && found[0].name; + }, + contextName() { + const found = queue.find((params) => { + const context = params[1]; + return context && !context.destructuring && context.name; + }); + return found && found[1] && found[1].name; + }, + }; +} + +function evalParams(params) { + return params.map((param) => ({ + destructuring: param.type === 'ObjectPattern', + name: param.type === 'Identifier' && param.name, + })); +} + +const messages = { + noDestructPropsInSFCArg: 'Must never use destructuring props assignment in SFC argument', + noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument', + noDestructAssignment: 'Must never use destructuring {{type}} assignment', + useDestructAssignment: 'Must use destructuring {{type}} assignment', + destructureInSignature: 'Must destructure props in the function signature.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce consistent usage of destructuring assignment of props, state, and context', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('destructuring-assignment'), + }, + fixable: 'code', + messages, + + schema: [{ + type: 'string', + enum: [ + 'always', + 'never', + ], + }, { + type: 'object', + properties: { + ignoreClassFields: { + type: 'boolean', + }, + destructureInSignature: { + type: 'string', + enum: [ + 'always', + 'ignore', + ], + }, + }, + additionalProperties: false, + }], + }, + + create: Components.detect((context, components, utils) => { + const configuration = context.options[0] || DEFAULT_OPTION; + const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false; + const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore'; + const sfcParams = createSFCParams(); + + /** + * @param {ASTNode} node We expect either an ArrowFunctionExpression, + * FunctionDeclaration, or FunctionExpression + */ + function handleStatelessComponent(node) { + const params = evalParams(node.params); + + const SFCComponent = components.get(getScope(context, node).block); + if (!SFCComponent) { + return; + } + sfcParams.push(params); + + if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') { + report(context, messages.noDestructPropsInSFCArg, 'noDestructPropsInSFCArg', { + node, + }); + } else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') { + report(context, messages.noDestructContextInSFCArg, 'noDestructContextInSFCArg', { + node, + }); + } + } + + function handleStatelessComponentExit(node) { + const SFCComponent = components.get(getScope(context, node).block); + if (SFCComponent) { + sfcParams.pop(); + } + } + + function handleSFCUsage(node) { + const propsName = sfcParams.propsName(); + const contextName = sfcParams.contextName(); + // props.aProp || context.aProp + const isPropUsed = ( + (propsName && node.object.name === propsName) + || (contextName && node.object.name === contextName) + ) + && !isAssignmentLHS(node); + if (isPropUsed && configuration === 'always' && !node.optional) { + report(context, messages.useDestructAssignment, 'useDestructAssignment', { + node, + data: { + type: node.object.name, + }, + }); + } + } + + function isInClassProperty(node) { + let curNode = node.parent; + while (curNode) { + if (curNode.type === 'ClassProperty' || curNode.type === 'PropertyDefinition') { + return true; + } + curNode = curNode.parent; + } + return false; + } + + function handleClassUsage(node) { + // this.props.Aprop || this.context.aProp || this.state.aState + const isPropUsed = ( + node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression' + && (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state') + && !isAssignmentLHS(node) + ); + + if ( + isPropUsed && configuration === 'always' + && !(ignoreClassFields && isInClassProperty(node)) + ) { + report(context, messages.useDestructAssignment, 'useDestructAssignment', { + node, + data: { + type: node.object.property.name, + }, + }); + } + } + + // valid-jsdoc cannot read function types + // eslint-disable-next-line valid-jsdoc + /** + * Find a parent that satisfy the given predicate + * @param {ASTNode} node + * @param {(node: ASTNode) => boolean} predicate + * @returns {ASTNode | undefined} + */ + function findParent(node, predicate) { + let n = node; + while (n) { + if (predicate(n)) { + return n; + } + n = n.parent; + } + return undefined; + } + + return { + + FunctionDeclaration: handleStatelessComponent, + + ArrowFunctionExpression: handleStatelessComponent, + + FunctionExpression: handleStatelessComponent, + + 'FunctionDeclaration:exit': handleStatelessComponentExit, + + 'ArrowFunctionExpression:exit': handleStatelessComponentExit, + + 'FunctionExpression:exit': handleStatelessComponentExit, + + MemberExpression(node) { + const SFCComponent = utils.getParentStatelessComponent(node); + if (SFCComponent) { + handleSFCUsage(node); + } + + const classComponent = utils.getParentComponent(node); + if (classComponent) { + handleClassUsage(node); + } + }, + + TSQualifiedName(node) { + if (configuration !== 'always') { + return; + } + // handle `typeof props.a.b` + if (node.left.type === 'Identifier' + && node.left.name === sfcParams.propsName() + && findParent(node, (n) => n.type === 'TSTypeQuery') + && utils.getParentStatelessComponent(node) + ) { + report(context, messages.useDestructAssignment, 'useDestructAssignment', { + node, + data: { + type: 'props', + }, + }); + } + }, + + VariableDeclarator(node) { + const classComponent = utils.getParentComponent(node); + const SFCComponent = components.get(getScope(context, node).block); + + const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); + // let {foo} = props; + const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context'); + // let {foo} = this.props; + const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && ( + node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state' + ); + + if (SFCComponent && destructuringSFC && configuration === 'never') { + report(context, messages.noDestructAssignment, 'noDestructAssignment', { + node, + data: { + type: node.init.name, + }, + }); + } + + if ( + classComponent && destructuringClass && configuration === 'never' + && !(ignoreClassFields && (node.parent.type === 'ClassProperty' || node.parent.type === 'PropertyDefinition')) + ) { + report(context, messages.noDestructAssignment, 'noDestructAssignment', { + node, + data: { + type: node.init.property.name, + }, + }); + } + + if ( + SFCComponent + && destructuringSFC + && configuration === 'always' + && destructureInSignature === 'always' + && node.init.name === 'props' + ) { + const scopeSetProps = getScope(context, node).set.get('props'); + const propsRefs = scopeSetProps && scopeSetProps.references; + if (!propsRefs) { + return; + } + + // Skip if props is used elsewhere + if (propsRefs.length > 1) { + return; + } + report(context, messages.destructureInSignature, 'destructureInSignature', { + node, + fix(fixer) { + const param = SFCComponent.node.params[0]; + if (!param) { + return; + } + const replaceRange = [ + param.range[0], + param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1], + ]; + return [ + fixer.replaceTextRange(replaceRange, getText(context, node.id)), + fixer.remove(node.parent), + ]; + }, + }); + } + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts b/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9da0311932194c10168099a71ee7e02fbeaa6a1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=display-name.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..6f47ffb2e6880c66c621a92b5b5b53163498c7b0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"display-name.d.ts","sourceRoot":"","sources":["display-name.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/display-name.js b/node_modules/eslint-plugin-react/lib/rules/display-name.js new file mode 100644 index 0000000000000000000000000000000000000000..b85ec34f1ccebf0a36106d5dcd0e1e58a81d33bc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/display-name.js @@ -0,0 +1,285 @@ +/** + * @fileoverview Prevent missing displayName in a React component definition + * @author Yannick Croissant + */ + +'use strict'; + +const values = require('object.values'); +const filter = require('es-iterator-helpers/Iterator.prototype.filter'); +const forEach = require('es-iterator-helpers/Iterator.prototype.forEach'); + +const Components = require('../util/Components'); +const isCreateContext = require('../util/isCreateContext'); +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const testReactVersion = require('../util/version').testReactVersion; +const propsUtil = require('../util/props'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noDisplayName: 'Component definition is missing display name', + noContextDisplayName: 'Context definition is missing display name', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow missing displayName in a React component definition', + category: 'Best Practices', + recommended: true, + url: docsUrl('display-name'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + ignoreTranspilerName: { + type: 'boolean', + }, + checkContextObjects: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create: Components.detect((context, components, utils) => { + const config = context.options[0] || {}; + const ignoreTranspilerName = config.ignoreTranspilerName || false; + const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0'); + + const contextObjects = new Map(); + + /** + * Mark a prop type as declared + * @param {ASTNode} node The AST node being checked. + */ + function markDisplayNameAsDeclared(node) { + components.set(node, { + hasDisplayName: true, + }); + } + + /** + * Checks if React.forwardRef is nested inside React.memo + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if React.forwardRef is nested inside React.memo, false if not. + */ + function isNestedMemo(node) { + return astUtil.isCallExpression(node) + && node.arguments + && astUtil.isCallExpression(node.arguments[0]) + && utils.isPragmaComponentWrapper(node); + } + + /** + * Reports missing display name for a given component + * @param {Object} component The component to process + */ + function reportMissingDisplayName(component) { + if ( + testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0') + && isNestedMemo(component.node) + ) { + return; + } + + report(context, messages.noDisplayName, 'noDisplayName', { + node: component.node, + }); + } + + /** + * Reports missing display name for a given context object + * @param {Object} contextObj The context object to process + */ + function reportMissingContextDisplayName(contextObj) { + report(context, messages.noContextDisplayName, 'noContextDisplayName', { + node: contextObj.node, + }); + } + + /** + * Checks if the component have a name set by the transpiler + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if component has a name, false if not. + */ + function hasTranspilerName(node) { + const namedObjectAssignment = ( + node.type === 'ObjectExpression' + && node.parent + && node.parent.parent + && node.parent.parent.type === 'AssignmentExpression' + && ( + !node.parent.parent.left.object + || node.parent.parent.left.object.name !== 'module' + || node.parent.parent.left.property.name !== 'exports' + ) + ); + const namedObjectDeclaration = ( + node.type === 'ObjectExpression' + && node.parent + && node.parent.parent + && node.parent.parent.type === 'VariableDeclarator' + ); + const namedClass = ( + (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') + && node.id + && !!node.id.name + ); + + const namedFunctionDeclaration = ( + (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') + && node.id + && !!node.id.name + ); + + const namedFunctionExpression = ( + astUtil.isFunctionLikeExpression(node) + && node.parent + && (node.parent.type === 'VariableDeclarator' || node.parent.type === 'Property' || node.parent.method === true) + && (!node.parent.parent || !componentUtil.isES5Component(node.parent.parent, context)) + ); + + if ( + namedObjectAssignment || namedObjectDeclaration + || namedClass + || namedFunctionDeclaration || namedFunctionExpression + ) { + return true; + } + return false; + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + ExpressionStatement(node) { + if (checkContextObjects && isCreateContext(node)) { + contextObjects.set(node.expression.left.name, { node, hasDisplayName: false }); + } + }, + VariableDeclarator(node) { + if (checkContextObjects && isCreateContext(node)) { + contextObjects.set(node.id.name, { node, hasDisplayName: false }); + } + }, + 'ClassProperty, PropertyDefinition'(node) { + if (!propsUtil.isDisplayNameDeclaration(node)) { + return; + } + markDisplayNameAsDeclared(node); + }, + + MemberExpression(node) { + if (!propsUtil.isDisplayNameDeclaration(node.property)) { + return; + } + if ( + checkContextObjects + && node.object + && node.object.name + && contextObjects.has(node.object.name) + ) { + contextObjects.get(node.object.name).hasDisplayName = true; + } + const component = utils.getRelatedComponent(node); + if (!component) { + return; + } + markDisplayNameAsDeclared(astUtil.unwrapTSAsExpression(component.node)); + }, + + 'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) { + if (ignoreTranspilerName || !hasTranspilerName(node)) { + return; + } + if (components.get(node)) { + markDisplayNameAsDeclared(node); + } + }, + + MethodDefinition(node) { + if (!propsUtil.isDisplayNameDeclaration(node.key)) { + return; + } + markDisplayNameAsDeclared(node); + }, + + 'ClassExpression, ClassDeclaration'(node) { + if (ignoreTranspilerName || !hasTranspilerName(node)) { + return; + } + markDisplayNameAsDeclared(node); + }, + + ObjectExpression(node) { + if (!componentUtil.isES5Component(node, context)) { + return; + } + if (ignoreTranspilerName || !hasTranspilerName(node)) { + // Search for the displayName declaration + node.properties.forEach((property) => { + if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) { + return; + } + markDisplayNameAsDeclared(node); + }); + return; + } + markDisplayNameAsDeclared(node); + }, + + CallExpression(node) { + if (!utils.isPragmaComponentWrapper(node)) { + return; + } + + if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) { + // 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 + const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node); + if ( + !isWrappedInAnotherPragma + && (ignoreTranspilerName || !hasTranspilerName(node.arguments[0])) + ) { + return; + } + + if (components.get(node)) { + markDisplayNameAsDeclared(node); + } + } + }, + + 'Program:exit'() { + const list = components.list(); + // Report missing display name for all components + values(list).filter((component) => !component.hasDisplayName).forEach((component) => { + reportMissingDisplayName(component); + }); + if (checkContextObjects) { + // Report missing display name for all context objects + forEach( + filter(contextObjects.values(), (v) => !v.hasDisplayName), + (contextObj) => reportMissingContextDisplayName(contextObj) + ); + } + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..06e412d9042e26f83155dbd551e2d9de26e73d42 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forbid-component-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..c0ff62941789978e23691997ba5b88f187dbc00e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forbid-component-props.d.ts","sourceRoot":"","sources":["forbid-component-props.js"],"names":[],"mappings":"wBAyBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js new file mode 100644 index 0000000000000000000000000000000000000000..2dd4412b87e7d57d0d46415cd2816e73f6b34e7e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js @@ -0,0 +1,239 @@ +/** + * @fileoverview Forbid certain props on components + * @author Joe Lencioni + */ + +'use strict'; + +const minimatch = require('minimatch'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DEFAULTS = ['className', 'style']; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + propIsForbidden: 'Prop "{{prop}}" is forbidden on Components', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow certain props on components', + category: 'Best Practices', + recommended: false, + url: docsUrl('forbid-component-props'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + anyOf: [ + { type: 'string' }, + { + type: 'object', + properties: { + propName: { type: 'string' }, + allowedFor: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + allowedForPatterns: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + message: { type: 'string' }, + }, + additionalProperties: false, + }, + { + type: 'object', + properties: { + propName: { type: 'string' }, + disallowedFor: { + type: 'array', + uniqueItems: true, + minItems: 1, + items: { type: 'string' }, + }, + disallowedForPatterns: { + type: 'array', + uniqueItems: true, + minItems: 1, + items: { type: 'string' }, + }, + message: { type: 'string' }, + }, + anyOf: [ + { required: ['disallowedFor'] }, + { required: ['disallowedForPatterns'] }, + ], + additionalProperties: false, + }, + { + type: 'object', + properties: { + propNamePattern: { type: 'string' }, + allowedFor: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + allowedForPatterns: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + message: { type: 'string' }, + }, + additionalProperties: false, + }, + { + type: 'object', + properties: { + propNamePattern: { type: 'string' }, + disallowedFor: { + type: 'array', + uniqueItems: true, + minItems: 1, + items: { type: 'string' }, + }, + disallowedForPatterns: { + type: 'array', + uniqueItems: true, + minItems: 1, + items: { type: 'string' }, + }, + message: { type: 'string' }, + }, + anyOf: [ + { required: ['disallowedFor'] }, + { required: ['disallowedForPatterns'] }, + ], + additionalProperties: false, + }, + ], + }, + }, + }, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => { + const propName = typeof value === 'string' ? value : value.propName; + const propPattern = value.propNamePattern; + const prop = propName || propPattern; + const options = { + allowList: [].concat(value.allowedFor || []), + allowPatternList: [].concat(value.allowedForPatterns || []), + disallowList: [].concat(value.disallowedFor || []), + disallowPatternList: [].concat(value.disallowedForPatterns || []), + message: typeof value === 'string' ? null : value.message, + isPattern: !!value.propNamePattern, + }; + return [prop, options]; + })); + + function getPropOptions(prop) { + // Get config options having pattern + const propNamePatternArray = Array.from(forbid.entries()).filter((propEntry) => propEntry[1].isPattern); + // Match current prop with pattern options, return if matched + const propNamePattern = propNamePatternArray.find((propPatternVal) => minimatch(prop, propPatternVal[0])); + // Get options for matched propNamePattern + const propNamePatternOptions = propNamePattern && propNamePattern[1]; + + const options = forbid.get(prop) || propNamePatternOptions; + return options; + } + + function isForbidden(prop, tagName) { + const options = getPropOptions(prop); + if (!options) { + return false; + } + + function checkIsTagForbiddenByAllowOptions() { + if (options.allowList.indexOf(tagName) !== -1) { + return false; + } + + if (options.allowPatternList.length === 0) { + return true; + } + + return options.allowPatternList.every( + (pattern) => !minimatch(tagName, pattern) + ); + } + + function checkIsTagForbiddenByDisallowOptions() { + if (options.disallowList.indexOf(tagName) !== -1) { + return true; + } + + if (options.disallowPatternList.length === 0) { + return false; + } + + return options.disallowPatternList.some( + (pattern) => minimatch(tagName, pattern) + ); + } + + const hasDisallowOptions = options.disallowList.length > 0 || options.disallowPatternList.length > 0; + + // disallowList should have a least one item (schema configuration) + const isTagForbidden = hasDisallowOptions + ? checkIsTagForbiddenByDisallowOptions() + : checkIsTagForbiddenByAllowOptions(); + + // if the tagName is undefined (``), we assume it's a forbidden element + return typeof tagName === 'undefined' || isTagForbidden; + } + + return { + JSXAttribute(node) { + const parentName = node.parent.name; + // Extract a component name when using a "namespace", e.g. ``. + const tag = parentName.name || `${parentName.object.name}.${parentName.property.name}`; + const componentName = parentName.name || parentName.property.name; + if (componentName && typeof componentName[0] === 'string' && componentName[0] !== componentName[0].toUpperCase()) { + // This is a DOM node, not a Component, so exit. + return; + } + + const prop = node.name.name; + + if (!isForbidden(prop, tag)) { + return; + } + + const customMessage = getPropOptions(prop).message; + + report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', { + node, + data: { + prop, + }, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a7f2c51fd031fac331cd51d82301685865c8ffe --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forbid-dom-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..93174c15cccce496a451882e94086da0abc6c12b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forbid-dom-props.d.ts","sourceRoot":"","sources":["forbid-dom-props.js"],"names":[],"mappings":"wBAuCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js new file mode 100644 index 0000000000000000000000000000000000000000..4638a8700d0b6f9f3849f4427cd4af1aa9d9d386 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Forbid certain props on DOM Nodes + * @author David Vázquez + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DEFAULTS = []; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** + * @param {Map} forbidMap // { disallowList: null | string[], message: null | string } + * @param {string} prop + * @param {string} tagName + * @returns {boolean} + */ +function isForbidden(forbidMap, prop, tagName) { + const options = forbidMap.get(prop); + return options && ( + typeof tagName === 'undefined' + || !options.disallowList + || options.disallowList.indexOf(tagName) !== -1 + ); +} + +const messages = { + propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow certain props on DOM Nodes', + category: 'Best Practices', + recommended: false, + url: docsUrl('forbid-dom-props'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + anyOf: [{ + type: 'string', + }, { + type: 'object', + properties: { + propName: { + type: 'string', + }, + disallowedFor: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, + message: { + type: 'string', + }, + }, + }], + minLength: 1, + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => { + const propName = typeof value === 'string' ? value : value.propName; + return [propName, { + disallowList: typeof value === 'string' ? null : (value.disallowedFor || null), + message: typeof value === 'string' ? null : value.message, + }]; + })); + + return { + JSXAttribute(node) { + const tag = node.parent.name.name; + if (!(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())) { + // This is a Component, not a DOM node, so exit. + return; + } + + const prop = node.name.name; + + if (!isForbidden(forbid, prop, tag)) { + return; + } + + const customMessage = forbid.get(prop).message; + + report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', { + node, + data: { + prop, + }, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1caad3467396cae0b4feda5a1dc2efaa226eced5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forbid-elements.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..d7a227c2f2549999e13cd7c0e0f6f785c4249640 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forbid-elements.d.ts","sourceRoot":"","sources":["forbid-elements.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..f08ef1d1e7c58f12af973fa0925d569432e17144 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js @@ -0,0 +1,119 @@ +/** + * @fileoverview Forbid certain elements + * @author Kenneth Chung + */ + +'use strict'; + +const has = require('hasown'); +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + forbiddenElement: '<{{element}}> is forbidden', + forbiddenElement_message: '<{{element}}> is forbidden, {{message}}', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow certain elements', + category: 'Best Practices', + recommended: false, + url: docsUrl('forbid-elements'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + anyOf: [ + { type: 'string' }, + { + type: 'object', + properties: { + element: { type: 'string' }, + message: { type: 'string' }, + }, + required: ['element'], + additionalProperties: false, + }, + ], + }, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const forbidConfiguration = configuration.forbid || []; + + /** @type {Record} */ + const indexedForbidConfigs = {}; + + forbidConfiguration.forEach((item) => { + if (typeof item === 'string') { + indexedForbidConfigs[item] = { element: item }; + } else { + indexedForbidConfigs[item.element] = item; + } + }); + + function reportIfForbidden(element, node) { + if (has(indexedForbidConfigs, element)) { + const message = indexedForbidConfigs[element].message; + + report( + context, + message ? messages.forbiddenElement_message : messages.forbiddenElement, + message ? 'forbiddenElement_message' : 'forbiddenElement', + { + node, + data: { + element, + message, + }, + } + ); + } + } + + return { + JSXOpeningElement(node) { + reportIfForbidden(getText(context, node.name), node.name); + }, + + CallExpression(node) { + if (!isCreateElement(context, node)) { + return; + } + + const argument = node.arguments[0]; + if (!argument) { + return; + } + + if (argument.type === 'Identifier' && /^[A-Z_]/.test(argument.name)) { + reportIfForbidden(argument.name, argument); + } else if (argument.type === 'Literal' && /^[a-z][^.]*$/.test(String(argument.value))) { + reportIfForbidden(argument.value, argument); + } else if (argument.type === 'MemberExpression') { + reportIfForbidden(getText(context, argument), argument); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..875c4083d485e118224742c41d9c5d96e82a40ed --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forbid-foreign-prop-types.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..9b4993de4ddc12269354904c1b88d6a116aed8a5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forbid-foreign-prop-types.d.ts","sourceRoot":"","sources":["forbid-foreign-prop-types.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js new file mode 100644 index 0000000000000000000000000000000000000000..20d191d26483c1a4a436474229a697645433cdb4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js @@ -0,0 +1,141 @@ +/** + * @fileoverview Forbid using another component's propTypes + * @author Ian Christian Myers + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const ast = require('../util/ast'); +const report = require('../util/report'); + +const messages = { + forbiddenPropType: 'Using propTypes from another component is not safe because they may be removed in production builds', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow using another component\'s propTypes', + category: 'Best Practices', + recommended: false, + url: docsUrl('forbid-foreign-prop-types'), + }, + + messages, + + schema: [ + { + type: 'object', + properties: { + allowInPropTypes: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const config = context.options[0] || {}; + const allowInPropTypes = config.allowInPropTypes || false; + + // -------------------------------------------------------------------------- + // Helpers + // -------------------------------------------------------------------------- + + function findParentAssignmentExpression(node) { + let parent = node.parent; + + while (parent && parent.type !== 'Program') { + if (parent.type === 'AssignmentExpression') { + return parent; + } + parent = parent.parent; + } + return null; + } + + function findParentClassProperty(node) { + let parent = node.parent; + + while (parent && parent.type !== 'Program') { + if (parent.type === 'ClassProperty' || parent.type === 'PropertyDefinition') { + return parent; + } + parent = parent.parent; + } + return null; + } + + function isAllowedAssignment(node) { + if (!allowInPropTypes) { + return false; + } + + const assignmentExpression = findParentAssignmentExpression(node); + + if ( + assignmentExpression + && assignmentExpression.left + && assignmentExpression.left.property + && assignmentExpression.left.property.name === 'propTypes' + ) { + return true; + } + + const classProperty = findParentClassProperty(node); + + if ( + classProperty + && classProperty.key + && classProperty.key.name === 'propTypes' + ) { + return true; + } + return false; + } + + return { + MemberExpression(node) { + if ( + (node.property + && ( + !node.computed + && node.property.type === 'Identifier' + && node.property.name === 'propTypes' + && !ast.isAssignmentLHS(node) + && !isAllowedAssignment(node) + )) || ( + // @ts-expect-error: The JSXText type is not present in the estree type definitions + (node.property.type === 'Literal' || node.property.type === 'JSXText') + && 'value' in node.property + && node.property.value === 'propTypes' + && !ast.isAssignmentLHS(node) + && !isAllowedAssignment(node) + ) + ) { + report(context, messages.forbiddenPropType, 'forbiddenPropType', { + node: node.property, + }); + } + }, + + ObjectPattern(node) { + const propTypesNode = node.properties.find((property) => ( + property.type === 'Property' + && 'name' in property.key + && property.key.name === 'propTypes' + )); + + if (propTypesNode) { + report(context, messages.forbiddenPropType, 'forbiddenPropType', { + node: propTypesNode, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b851e9093c71a8487e7c1f82532d1d510d67adb8 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forbid-prop-types.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..3cfd8461dff230e20128a3e30d1824b50288d2f2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forbid-prop-types.d.ts","sourceRoot":"","sources":["forbid-prop-types.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js new file mode 100644 index 0000000000000000000000000000000000000000..44de1fa02336b3fc85becc77019db2f49a2c6e2f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js @@ -0,0 +1,298 @@ +/** + * @fileoverview Forbid certain propTypes + */ + +'use strict'; + +const variableUtil = require('../util/variable'); +const propsUtil = require('../util/props'); +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const propWrapperUtil = require('../util/propWrapper'); +const report = require('../util/report'); +const getText = require('../util/eslint').getText; + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DEFAULTS = ['any', 'array', 'object']; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + forbiddenPropType: 'Prop type "{{target}}" is forbidden', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow certain propTypes', + category: 'Best Practices', + recommended: false, + url: docsUrl('forbid-prop-types'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + type: 'string', + }, + }, + checkContextTypes: { + type: 'boolean', + }, + checkChildContextTypes: { + type: 'boolean', + }, + }, + additionalProperties: true, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const checkContextTypes = configuration.checkContextTypes || false; + const checkChildContextTypes = configuration.checkChildContextTypes || false; + let propTypesPackageName = null; + let reactPackageName = null; + let isForeignPropTypesPackage = false; + + function isPropTypesPackage(node) { + return ( + node.type === 'Identifier' + && ( + node.name === null + || node.name === propTypesPackageName + || !isForeignPropTypesPackage + ) + ) || ( + node.type === 'MemberExpression' + && ( + node.object.name === null + || node.object.name === reactPackageName + || !isForeignPropTypesPackage + ) + ); + } + + function isForbidden(type) { + const forbid = configuration.forbid || DEFAULTS; + return forbid.indexOf(type) >= 0; + } + + function reportIfForbidden(type, declaration, target) { + if (isForbidden(type)) { + report(context, messages.forbiddenPropType, 'forbiddenPropType', { + node: declaration, + data: { + target, + }, + }); + } + } + + function shouldCheckContextTypes(node) { + if (checkContextTypes && propsUtil.isContextTypesDeclaration(node)) { + return true; + } + return false; + } + + function shouldCheckChildContextTypes(node) { + if (checkChildContextTypes && propsUtil.isChildContextTypesDeclaration(node)) { + return true; + } + return false; + } + + /** + * Checks if propTypes declarations are forbidden + * @param {Array} declarations The array of AST nodes being checked. + * @returns {void} + */ + function checkProperties(declarations) { + if (declarations) { + declarations.forEach((declaration) => { + if (declaration.type !== 'Property') { + return; + } + let target; + let value = declaration.value; + if ( + value.type === 'MemberExpression' + && value.property + && value.property.name + && value.property.name === 'isRequired' + ) { + value = value.object; + } + if (astUtil.isCallExpression(value)) { + if (!isPropTypesPackage(value.callee)) { + return; + } + value.arguments.forEach((arg) => { + const name = arg.type === 'MemberExpression' ? arg.property.name : arg.name; + reportIfForbidden(name, declaration, name); + }); + value = value.callee; + } + if (!isPropTypesPackage(value)) { + return; + } + if (value.property) { + target = value.property.name; + } else if (value.type === 'Identifier') { + target = value.name; + } + reportIfForbidden(target, declaration, target); + }); + } + } + + function checkNode(node) { + if (!node) { + return; + } + + if (node.type === 'ObjectExpression') { + checkProperties(node.properties); + } else if (node.type === 'Identifier') { + const propTypesObject = variableUtil.findVariableByName(context, node, node.name); + if (propTypesObject && propTypesObject.properties) { + checkProperties(propTypesObject.properties); + } + } else if (astUtil.isCallExpression(node)) { + const innerNode = node.arguments && node.arguments[0]; + if ( + propWrapperUtil.isPropWrapperFunction(context, getText(context, node.callee)) + && innerNode + ) { + checkNode(innerNode); + } + } + } + + return { + ImportDeclaration(node) { + if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types" + if (node.specifiers.length > 0) { + propTypesPackageName = node.specifiers[0].local.name; + } + } else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react" + if (node.specifiers.length > 0) { + reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"` + } + if (node.specifiers.length >= 1) { + const propTypesSpecifier = node.specifiers.find((specifier) => ( + 'imported' in specifier + && specifier.imported + && 'name' in specifier.imported + && specifier.imported.name === 'PropTypes' + )); + if (propTypesSpecifier) { + propTypesPackageName = propTypesSpecifier.local.name; + } + } + } else { // package is not imported from "react" or "prop-types" + // eslint-disable-next-line no-lonely-if + if (node.specifiers.some((x) => x.local.name === 'PropTypes')) { // assert: node.specifiers.length > 1 + isForeignPropTypesPackage = true; + } + } + }, + + 'ClassProperty, PropertyDefinition'(node) { + if ( + !propsUtil.isPropTypesDeclaration(node) + && !isPropTypesPackage(node) + && !shouldCheckContextTypes(node) + && !shouldCheckChildContextTypes(node) + ) { + return; + } + checkNode(node.value); + }, + + MemberExpression(node) { + if ( + !propsUtil.isPropTypesDeclaration(node) + && !isPropTypesPackage(node) + && !shouldCheckContextTypes(node) + && !shouldCheckChildContextTypes(node) + ) { + return; + } + + checkNode('right' in node.parent && node.parent.right); + }, + + CallExpression(node) { + if ( + node.callee.type === 'MemberExpression' + && node.callee.object + && !isPropTypesPackage(node.callee.object) + && !propsUtil.isPropTypesDeclaration(node.callee) + ) { + return; + } + + if ( + node.arguments.length > 0 + && ( + ('name' in node.callee && node.callee.name === 'shape') + || astUtil.getPropertyName(node.callee) === 'shape' + ) + ) { + checkProperties('properties' in node.arguments[0] && node.arguments[0].properties); + } + }, + + MethodDefinition(node) { + if ( + !propsUtil.isPropTypesDeclaration(node) + && !isPropTypesPackage(node) + && !shouldCheckContextTypes(node) + && !shouldCheckChildContextTypes(node) + ) { + return; + } + + const returnStatement = astUtil.findReturnStatement(node); + + if (returnStatement && returnStatement.argument) { + checkNode(returnStatement.argument); + } + }, + + ObjectExpression(node) { + node.properties.forEach((property) => { + if (!('key' in property) || !property.key) { + return; + } + + if ( + !propsUtil.isPropTypesDeclaration(property) + && !isPropTypesPackage(property) + && !shouldCheckContextTypes(property) + && !shouldCheckChildContextTypes(property) + ) { + return; + } + if (property.value.type === 'ObjectExpression') { + checkProperties(property.value.properties); + } + }); + }, + + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7b6b33a97c354111785ee930a82208ac5025473 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=forward-ref-uses-ref.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..efc4fd6443b5d9fa18c73c674162bdf522107fd1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"forward-ref-uses-ref.d.ts","sourceRoot":"","sources":["forward-ref-uses-ref.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js new file mode 100644 index 0000000000000000000000000000000000000000..3a0b7de4c951e7ea92a9de7bac924ba95d643342 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js @@ -0,0 +1,100 @@ +/** + * @fileoverview Require all forwardRef components include a ref parameter + */ + +'use strict'; + +const isParenthesized = require('../util/ast').isParenthesized; +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const getMessageData = require('../util/message'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** + * @param {ASTNode} node + * @returns {boolean} If the node represents the identifier `forwardRef`. + */ +function isForwardRefIdentifier(node) { + return node.type === 'Identifier' && node.name === 'forwardRef'; +} + +/** + * @param {ASTNode} node + * @returns {boolean} If the node represents a function call `forwardRef()` or `React.forwardRef()`. + */ +function isForwardRefCall(node) { + return ( + node.type === 'CallExpression' + && ( + isForwardRefIdentifier(node.callee) + || (node.callee.type === 'MemberExpression' && isForwardRefIdentifier(node.callee.property)) + ) + ); +} + +const messages = { + missingRefParameter: 'forwardRef is used with this component but no ref parameter is set', + addRefParameter: 'Add a ref parameter', + removeForwardRef: 'Remove forwardRef wrapper', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Require all forwardRef components include a ref parameter', + category: 'Possible Errors', + recommended: false, + url: docsUrl('forward-ref-uses-ref'), + }, + messages, + schema: [], + type: 'suggestion', + hasSuggestions: true, + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + 'FunctionExpression, ArrowFunctionExpression'(node) { + if (!isForwardRefCall(node.parent)) { + return; + } + + if (node.params.length === 1) { + report(context, messages.missingRefParameter, 'missingRefParameter', { + node, + suggest: [ + Object.assign( + getMessageData('addRefParameter', messages.addRefParameter), + { + fix(fixer) { + const param = node.params[0]; + // If using shorthand arrow function syntax, add parentheses around the new parameter pair + const shouldAddParentheses = node.type === 'ArrowFunctionExpression' && !isParenthesized(context, param); + return [].concat( + shouldAddParentheses ? fixer.insertTextBefore(param, '(') : [], + fixer.insertTextAfter(param, `, ref${shouldAddParentheses ? ')' : ''}`) + ); + }, + } + ), + Object.assign( + getMessageData('removeForwardRef', messages.removeForwardRef), + { + fix(fixer) { + return fixer.replaceText(node.parent, sourceCode.getText(node)); + }, + } + ), + ], + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..63eed49fc904b975f9e4c64678d50df9c5925994 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=function-component-definition.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..922758309ab9994789de8f15541c59ec4dad6e80 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"function-component-definition.d.ts","sourceRoot":"","sources":["function-component-definition.js"],"names":[],"mappings":"wBAqHW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js new file mode 100644 index 0000000000000000000000000000000000000000..cca665abe2c5658ff8f8e03c1fd0cae08a1769eb --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js @@ -0,0 +1,285 @@ +/** + * @fileoverview Standardize the way function component get defined + * @author Stefan Wullems + */ + +'use strict'; + +const arrayIncludes = require('array-includes'); +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const reportC = require('../util/report'); +const getText = require('../util/eslint').getText; +const propsUtil = require('../util/props'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function buildFunction(template, parts) { + return Object.keys(parts).reduce( + (acc, key) => acc.replace(`{${key}}`, () => parts[key] || ''), + template + ); +} + +const NAMED_FUNCTION_TEMPLATES = { + 'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}', + 'arrow-function': '{varType} {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}', + 'function-expression': '{varType} {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}', +}; + +const UNNAMED_FUNCTION_TEMPLATES = { + 'function-expression': 'function{typeParams}({params}){returnType} {body}', + 'arrow-function': '{typeParams}({params}){returnType} => {body}', +}; + +function hasOneUnconstrainedTypeParam(node) { + const nodeTypeArguments = propsUtil.getTypeArguments(node); + + return nodeTypeArguments + && nodeTypeArguments.params + && nodeTypeArguments.params.length === 1 + && !nodeTypeArguments.params[0].constraint; +} + +function hasName(node) { + return ( + node.type === 'FunctionDeclaration' + || node.parent.type === 'VariableDeclarator' + ); +} + +function getNodeText(prop, source) { + if (!prop) return null; + return source.slice(prop.range[0], prop.range[1]); +} + +function getName(node) { + if (node.type === 'FunctionDeclaration') { + return node.id.name; + } + + if ( + node.type === 'ArrowFunctionExpression' + || node.type === 'FunctionExpression' + ) { + return hasName(node) && node.parent.id.name; + } +} + +function getParams(node, source) { + if (node.params.length === 0) return null; + return source.slice( + node.params[0].range[0], + node.params[node.params.length - 1].range[1] + ); +} + +function getBody(node, source) { + const range = node.body.range; + + if (node.body.type !== 'BlockStatement') { + return ['{', ` return ${source.slice(range[0], range[1])}`, '}'].join('\n'); + } + + return source.slice(range[0], range[1]); +} + +function getTypeAnnotation(node, source) { + if (!hasName(node) || node.type === 'FunctionDeclaration') return; + + if ( + node.type === 'ArrowFunctionExpression' + || node.type === 'FunctionExpression' + ) { + return getNodeText(node.parent.id.typeAnnotation, source); + } +} + +function isUnfixableBecauseOfExport(node) { + return ( + node.type === 'FunctionDeclaration' + && node.parent + && node.parent.type === 'ExportDefaultDeclaration' + ); +} + +function isFunctionExpressionWithName(node) { + return node.type === 'FunctionExpression' && node.id && node.id.name; +} + +const messages = { + 'function-declaration': 'Function component is not a function declaration', + 'function-expression': 'Function component is not a function expression', + 'arrow-function': 'Function component is not an arrow function', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce a specific function type for function components', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('function-component-definition'), + }, + fixable: 'code', + + messages, + + schema: [ + { + type: 'object', + properties: { + namedComponents: { + anyOf: [ + { + enum: [ + 'function-declaration', + 'arrow-function', + 'function-expression', + ], + }, + { + type: 'array', + items: { + type: 'string', + enum: [ + 'function-declaration', + 'arrow-function', + 'function-expression', + ], + }, + }, + ], + }, + unnamedComponents: { + anyOf: [ + { enum: ['arrow-function', 'function-expression'] }, + { + type: 'array', + items: { + type: 'string', + enum: ['arrow-function', 'function-expression'], + }, + }, + ], + }, + }, + }, + ], + }, + + create: Components.detect((context, components) => { + const configuration = context.options[0] || {}; + let fileVarType = 'var'; + + const namedConfig = [].concat( + configuration.namedComponents || 'function-declaration' + ); + const unnamedConfig = [].concat( + configuration.unnamedComponents || 'function-expression' + ); + + function getFixer(node, options) { + const source = getText(context); + + const typeAnnotation = getTypeAnnotation(node, source); + + if (options.type === 'function-declaration' && typeAnnotation) { + return; + } + if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) { + return; + } + if (isUnfixableBecauseOfExport(node)) return; + if (isFunctionExpressionWithName(node)) return; + let varType = fileVarType; + if ( + (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') + && node.parent.type === 'VariableDeclarator' + ) { + varType = node.parent.parent.kind; + } + + const nodeTypeArguments = propsUtil.getTypeArguments(node); + return (fixer) => fixer.replaceTextRange( + options.range, + buildFunction(options.template, { + typeAnnotation, + typeParams: getNodeText(nodeTypeArguments, source), + params: getParams(node, source), + returnType: getNodeText(node.returnType, source), + body: getBody(node, source), + name: getName(node), + varType, + }) + ); + } + + function report(node, options) { + reportC(context, messages[options.messageId], options.messageId, { + node, + fix: getFixer(node, options.fixerOptions), + }); + } + + function validate(node, functionType) { + if (!components.get(node)) return; + + if (node.parent && node.parent.type === 'Property') return; + + if (hasName(node) && !arrayIncludes(namedConfig, functionType)) { + report(node, { + messageId: namedConfig[0], + fixerOptions: { + type: namedConfig[0], + template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]], + range: + node.type === 'FunctionDeclaration' + ? node.range + : node.parent.parent.range, + }, + }); + } + if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) { + report(node, { + messageId: unnamedConfig[0], + fixerOptions: { + type: unnamedConfig[0], + template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]], + range: node.range, + }, + }); + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + const validatePairs = []; + let hasES6OrJsx = false; + return { + FunctionDeclaration(node) { + validatePairs.push([node, 'function-declaration']); + }, + ArrowFunctionExpression(node) { + validatePairs.push([node, 'arrow-function']); + }, + FunctionExpression(node) { + validatePairs.push([node, 'function-expression']); + }, + VariableDeclaration(node) { + hasES6OrJsx = hasES6OrJsx || node.kind === 'const' || node.kind === 'let'; + }, + 'Program:exit'() { + if (hasES6OrJsx) fileVarType = 'const'; + validatePairs.forEach((pair) => validate(pair[0], pair[1])); + }, + 'ImportDeclaration, ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration, ExportSpecifier, ExportDefaultSpecifier, JSXElement, TSExportAssignment, TSImportEqualsDeclaration'() { + hasES6OrJsx = true; + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..81cb4758e4b94d68f150af2f0fb8e41d9ed45011 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=hook-use-state.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..f4503cce0fbb97a4270becc56027bd1815e70716 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"hook-use-state.d.ts","sourceRoot":"","sources":["hook-use-state.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js new file mode 100644 index 0000000000000000000000000000000000000000..2d1cf681cdf881f38735b460a7b34f29c6b9b146 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js @@ -0,0 +1,205 @@ +/** + * @fileoverview Ensure symmetric naming of useState hook value and setter variables + * @author Duncan Beevers + */ + +'use strict'; + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const getMessageData = require('../util/message'); +const getText = require('../util/eslint').getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function isNodeDestructuring(node) { + return node && (node.type === 'ArrayPattern' || node.type === 'ObjectPattern'); +} + +const messages = { + useStateErrorMessage: 'useState call is not destructured into value + setter pair', + useStateErrorMessageOrAddOption: 'useState call is not destructured into value + setter pair (you can allow destructuring by enabling "allowDestructuredState" option)', + suggestPair: 'Destructure useState call into value + setter pair', + suggestMemo: 'Replace useState call with useMemo', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Ensure destructuring and symmetric naming of useState hook value and setter variables', + category: 'Best Practices', + recommended: false, + url: docsUrl('hook-use-state'), + }, + messages, + schema: [{ + type: 'object', + properties: { + allowDestructuredState: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + }], + type: 'suggestion', + hasSuggestions: true, + }, + + create: Components.detect((context, components, util) => { + const configuration = context.options[0] || {}; + const allowDestructuredState = configuration.allowDestructuredState || false; + + return { + CallExpression(node) { + const isImmediateReturn = node.parent + && node.parent.type === 'ReturnStatement'; + + if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) { + return; + } + + const isDestructuringDeclarator = node.parent + && node.parent.type === 'VariableDeclarator' + && node.parent.id.type === 'ArrayPattern'; + + if (!isDestructuringDeclarator) { + report( + context, + messages.useStateErrorMessage, + 'useStateErrorMessage', + { + node, + suggest: false, + } + ); + return; + } + + const variableNodes = node.parent.id.elements; + const valueVariable = variableNodes[0]; + const setterVariable = variableNodes[1]; + const isOnlyValueDestructuring = isNodeDestructuring(valueVariable) && !isNodeDestructuring(setterVariable); + + if (allowDestructuredState && isOnlyValueDestructuring) { + return; + } + + const valueVariableName = valueVariable + ? valueVariable.name + : undefined; + + const setterVariableName = setterVariable + ? setterVariable.name + : undefined; + + const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined; + const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined; + const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined; + const expectedSetterVariableNames = upperCaseCandidatePrefix ? [ + `set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`, + `set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`, + ] : []; + + const isSymmetricGetterSetterPair = valueVariable + && setterVariable + && expectedSetterVariableNames.indexOf(setterVariableName) !== -1 + && variableNodes.length === 2; + + if (!isSymmetricGetterSetterPair) { + const suggestions = [ + Object.assign( + getMessageData('suggestPair', messages.suggestPair), + { + fix(fixer) { + if (expectedSetterVariableNames.length > 0) { + return fixer.replaceTextRange( + node.parent.id.range, + `[${valueVariableName}, ${expectedSetterVariableNames[0]}]` + ); + } + }, + } + ), + ]; + + const defaultReactImports = components.getDefaultReactImports(); + const defaultReactImportSpecifier = defaultReactImports + ? defaultReactImports[0] + : undefined; + + const defaultReactImportName = defaultReactImportSpecifier + ? defaultReactImportSpecifier.local.name + : undefined; + + const namedReactImports = components.getNamedReactImports(); + const useStateReactImportSpecifier = namedReactImports + ? namedReactImports.find((specifier) => specifier.imported.name === 'useState') + : undefined; + + const isSingleGetter = valueVariable && variableNodes.length === 1; + const isUseStateCalledWithSingleArgument = node.arguments.length === 1; + if (isSingleGetter && isUseStateCalledWithSingleArgument) { + const useMemoReactImportSpecifier = namedReactImports + && namedReactImports.find((specifier) => specifier.imported.name === 'useMemo'); + + let useMemoCode; + if (useMemoReactImportSpecifier) { + useMemoCode = useMemoReactImportSpecifier.local.name; + } else if (defaultReactImportName) { + useMemoCode = `${defaultReactImportName}.useMemo`; + } else { + useMemoCode = 'useMemo'; + } + + suggestions.unshift(Object.assign( + getMessageData('suggestMemo', messages.suggestMemo), + { + fix: (fixer) => [ + // Add useMemo import, if necessary + useStateReactImportSpecifier + && (!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}(() => ${getText(context, node.arguments[0])}, [])` + ), + ].filter(Boolean), + } + )); + } + + if (isOnlyValueDestructuring) { + report( + context, + messages.useStateErrorMessageOrAddOption, + 'useStateErrorMessageOrAddOption', + { + node: node.parent.id, + suggest: false, + } + ); + return; + } + + report( + context, + messages.useStateErrorMessage, + 'useStateErrorMessage', + { + node: node.parent.id, + suggest: suggestions, + } + ); + } + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e082114e2dfaefee74a1bd18be3fce4d0fa639fe --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=iframe-missing-sandbox.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..580c1c712fde9e2526514509fda42df276f05405 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"iframe-missing-sandbox.d.ts","sourceRoot":"","sources":["iframe-missing-sandbox.js"],"names":[],"mappings":"wBA+GW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js new file mode 100644 index 0000000000000000000000000000000000000000..7d8f234d97edb810e7fbc3559169424dbb4582b3 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js @@ -0,0 +1,143 @@ +/** + * @fileoverview TBD + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); + +const messages = { + attributeMissing: 'An iframe element is missing a sandbox attribute', + invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"', + invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid', +}; + +const ALLOWED_VALUES = [ + // From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox + '', + 'allow-downloads-without-user-activation', + 'allow-downloads', + 'allow-forms', + 'allow-modals', + 'allow-orientation-lock', + 'allow-pointer-lock', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-presentation', + 'allow-same-origin', + 'allow-scripts', + 'allow-storage-access-by-user-activation', + 'allow-top-navigation', + 'allow-top-navigation-by-user-activation', +]; + +function validateSandboxAttribute(context, node, attribute) { + if (typeof attribute !== 'string') { + // Only string literals are supported for now + return; + } + const values = attribute.split(' '); + let allowScripts = false; + let allowSameOrigin = false; + values.forEach((attributeValue) => { + const trimmedAttributeValue = attributeValue.trim(); + if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) { + report(context, messages.invalidValue, 'invalidValue', { + node, + data: { + value: trimmedAttributeValue, + }, + }); + } + if (trimmedAttributeValue === 'allow-scripts') { + allowScripts = true; + } + if (trimmedAttributeValue === 'allow-same-origin') { + allowSameOrigin = true; + } + }); + if (allowScripts && allowSameOrigin) { + report(context, messages.invalidCombination, 'invalidCombination', { + node, + }); + } +} + +function checkAttributes(context, node) { + let sandboxAttributeFound = false; + node.attributes.forEach((attribute) => { + if (attribute.type === 'JSXAttribute' + && attribute.name + && attribute.name.type === 'JSXIdentifier' + && attribute.name.name === 'sandbox' + ) { + sandboxAttributeFound = true; + if ( + attribute.value + && attribute.value.type === 'Literal' + && attribute.value.value + ) { + validateSandboxAttribute(context, node, attribute.value.value); + } + } + }); + if (!sandboxAttributeFound) { + report(context, messages.attributeMissing, 'attributeMissing', { + node, + }); + } +} + +function checkProps(context, node) { + let sandboxAttributeFound = false; + if (node.arguments.length > 1) { + const props = node.arguments[1]; + const sandboxProp = props.properties && props.properties.find((x) => x.type === 'Property' && x.key.name === 'sandbox'); + if (sandboxProp) { + sandboxAttributeFound = true; + if (sandboxProp.value && sandboxProp.value.type === 'Literal' && sandboxProp.value.value) { + validateSandboxAttribute(context, node, sandboxProp.value.value); + } + } + } + if (!sandboxAttributeFound) { + report(context, messages.attributeMissing, 'attributeMissing', { + node, + }); + } +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce sandbox attribute on iframe elements', + category: 'Best Practices', + recommended: false, + url: docsUrl('iframe-missing-sandbox'), + }, + + schema: [], + + messages, + }, + + create(context) { + return { + 'JSXOpeningElement[name.name="iframe"]'(node) { + checkAttributes(context, node); + }, + + CallExpression(node) { + 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/node_modules/eslint-plugin-react/lib/rules/index.d.ts b/node_modules/eslint-plugin-react/lib/rules/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..efd87987bb0597379f5408d19a4f60b7765ded02 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/index.d.ts @@ -0,0 +1,108 @@ +export = rules; +/** @satisfies {Record} */ +declare const rules: { + 'boolean-prop-naming': import("eslint").Rule.RuleModule; + 'button-has-type': import("eslint").Rule.RuleModule; + 'checked-requires-onchange-or-readonly': import("eslint").Rule.RuleModule; + 'default-props-match-prop-types': import("eslint").Rule.RuleModule; + 'destructuring-assignment': import("eslint").Rule.RuleModule; + 'display-name': import("eslint").Rule.RuleModule; + 'forbid-component-props': import("eslint").Rule.RuleModule; + 'forbid-dom-props': import("eslint").Rule.RuleModule; + 'forbid-elements': import("eslint").Rule.RuleModule; + 'forbid-foreign-prop-types': import("eslint").Rule.RuleModule; + 'forbid-prop-types': import("eslint").Rule.RuleModule; + 'forward-ref-uses-ref': import("eslint").Rule.RuleModule; + 'function-component-definition': import("eslint").Rule.RuleModule; + 'hook-use-state': import("eslint").Rule.RuleModule; + 'iframe-missing-sandbox': import("eslint").Rule.RuleModule; + 'jsx-boolean-value': import("eslint").Rule.RuleModule; + 'jsx-child-element-spacing': import("eslint").Rule.RuleModule; + 'jsx-closing-bracket-location': import("eslint").Rule.RuleModule; + 'jsx-closing-tag-location': import("eslint").Rule.RuleModule; + 'jsx-curly-spacing': import("eslint").Rule.RuleModule; + 'jsx-curly-newline': import("eslint").Rule.RuleModule; + 'jsx-equals-spacing': import("eslint").Rule.RuleModule; + 'jsx-filename-extension': import("eslint").Rule.RuleModule; + 'jsx-first-prop-new-line': import("eslint").Rule.RuleModule; + 'jsx-handler-names': import("eslint").Rule.RuleModule; + 'jsx-indent': import("eslint").Rule.RuleModule; + 'jsx-indent-props': import("eslint").Rule.RuleModule; + 'jsx-key': import("eslint").Rule.RuleModule; + 'jsx-max-depth': import("eslint").Rule.RuleModule; + 'jsx-max-props-per-line': import("eslint").Rule.RuleModule; + 'jsx-newline': import("eslint").Rule.RuleModule; + 'jsx-no-bind': import("eslint").Rule.RuleModule; + 'jsx-no-comment-textnodes': import("eslint").Rule.RuleModule; + 'jsx-no-constructed-context-values': import("eslint").Rule.RuleModule; + 'jsx-no-duplicate-props': import("eslint").Rule.RuleModule; + 'jsx-no-leaked-render': import("eslint").Rule.RuleModule; + 'jsx-no-literals': import("eslint").Rule.RuleModule; + 'jsx-no-script-url': import("eslint").Rule.RuleModule; + 'jsx-no-target-blank': import("eslint").Rule.RuleModule; + 'jsx-no-useless-fragment': import("eslint").Rule.RuleModule; + 'jsx-one-expression-per-line': import("eslint").Rule.RuleModule; + 'jsx-no-undef': import("eslint").Rule.RuleModule; + 'jsx-curly-brace-presence': import("eslint").Rule.RuleModule; + 'jsx-pascal-case': import("eslint").Rule.RuleModule; + 'jsx-fragments': import("eslint").Rule.RuleModule; + 'jsx-props-no-multi-spaces': import("eslint").Rule.RuleModule; + 'jsx-props-no-spreading': import("eslint").Rule.RuleModule; + 'jsx-props-no-spread-multi': import("eslint").Rule.RuleModule; + 'jsx-sort-default-props': import("eslint").Rule.RuleModule; + 'jsx-sort-props': import("eslint").Rule.RuleModule; + 'jsx-space-before-closing': import("eslint").Rule.RuleModule; + 'jsx-tag-spacing': import("eslint").Rule.RuleModule; + 'jsx-uses-react': import("eslint").Rule.RuleModule; + 'jsx-uses-vars': import("eslint").Rule.RuleModule; + 'jsx-wrap-multilines': import("eslint").Rule.RuleModule; + 'no-invalid-html-attribute': import("eslint").Rule.RuleModule; + 'no-access-state-in-setstate': import("eslint").Rule.RuleModule; + 'no-adjacent-inline-elements': import("eslint").Rule.RuleModule; + 'no-array-index-key': import("eslint").Rule.RuleModule; + 'no-arrow-function-lifecycle': import("eslint").Rule.RuleModule; + 'no-children-prop': import("eslint").Rule.RuleModule; + 'no-danger': import("eslint").Rule.RuleModule; + 'no-danger-with-children': import("eslint").Rule.RuleModule; + 'no-deprecated': import("eslint").Rule.RuleModule; + 'no-did-mount-set-state': import("eslint").Rule.RuleModule; + 'no-did-update-set-state': import("eslint").Rule.RuleModule; + 'no-direct-mutation-state': import("eslint").Rule.RuleModule; + 'no-find-dom-node': import("eslint").Rule.RuleModule; + 'no-is-mounted': import("eslint").Rule.RuleModule; + 'no-multi-comp': import("eslint").Rule.RuleModule; + 'no-namespace': import("eslint").Rule.RuleModule; + 'no-set-state': import("eslint").Rule.RuleModule; + 'no-string-refs': import("eslint").Rule.RuleModule; + 'no-redundant-should-component-update': import("eslint").Rule.RuleModule; + 'no-render-return-value': import("eslint").Rule.RuleModule; + 'no-this-in-sfc': import("eslint").Rule.RuleModule; + 'no-typos': import("eslint").Rule.RuleModule; + 'no-unescaped-entities': import("eslint").Rule.RuleModule; + 'no-unknown-property': import("eslint").Rule.RuleModule; + 'no-unsafe': import("eslint").Rule.RuleModule; + 'no-unstable-nested-components': import("eslint").Rule.RuleModule; + 'no-unused-class-component-methods': import("eslint").Rule.RuleModule; + 'no-unused-prop-types': import("eslint").Rule.RuleModule; + 'no-unused-state': import("eslint").Rule.RuleModule; + 'no-object-type-as-default-prop': import("eslint").Rule.RuleModule; + 'no-will-update-set-state': import("eslint").Rule.RuleModule; + 'prefer-es6-class': import("eslint").Rule.RuleModule; + 'prefer-exact-props': import("eslint").Rule.RuleModule; + 'prefer-read-only-props': import("eslint").Rule.RuleModule; + 'prefer-stateless-function': import("eslint").Rule.RuleModule; + 'prop-types': import("eslint").Rule.RuleModule; + 'react-in-jsx-scope': import("eslint").Rule.RuleModule; + 'require-default-props': import("eslint").Rule.RuleModule; + 'require-optimization': import("eslint").Rule.RuleModule; + 'require-render-return': import("eslint").Rule.RuleModule; + 'self-closing-comp': import("eslint").Rule.RuleModule; + 'sort-comp': import("eslint").Rule.RuleModule; + 'sort-default-props': import("eslint").Rule.RuleModule; + 'sort-prop-types': import("eslint").Rule.RuleModule; + 'state-in-constructor': import("eslint").Rule.RuleModule; + 'static-property-placement': import("eslint").Rule.RuleModule; + 'style-prop-object': import("eslint").Rule.RuleModule; + 'void-dom-elements-no-children': import("eslint").Rule.RuleModule; +}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..edb90c97058c4b03153418492150db1438266f6c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AAIA,oEAAoE;AACpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwGE"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/index.js b/node_modules/eslint-plugin-react/lib/rules/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0e73ab1a3f08f146ce5b5e5840ecc003a52d4571 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/index.js @@ -0,0 +1,112 @@ +'use strict'; + +/* eslint global-require: 0 */ + +/** @satisfies {Record} */ +const rules = { + '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'), + 'forbid-component-props': require('./forbid-component-props'), + 'forbid-dom-props': require('./forbid-dom-props'), + 'forbid-elements': require('./forbid-elements'), + 'forbid-foreign-prop-types': require('./forbid-foreign-prop-types'), + 'forbid-prop-types': require('./forbid-prop-types'), + 'forward-ref-uses-ref': require('./forward-ref-uses-ref'), + 'function-component-definition': require('./function-component-definition'), + 'hook-use-state': require('./hook-use-state'), + 'iframe-missing-sandbox': require('./iframe-missing-sandbox'), + 'jsx-boolean-value': require('./jsx-boolean-value'), + 'jsx-child-element-spacing': require('./jsx-child-element-spacing'), + 'jsx-closing-bracket-location': require('./jsx-closing-bracket-location'), + 'jsx-closing-tag-location': require('./jsx-closing-tag-location'), + 'jsx-curly-spacing': require('./jsx-curly-spacing'), + 'jsx-curly-newline': require('./jsx-curly-newline'), + 'jsx-equals-spacing': require('./jsx-equals-spacing'), + 'jsx-filename-extension': require('./jsx-filename-extension'), + 'jsx-first-prop-new-line': require('./jsx-first-prop-new-line'), + 'jsx-handler-names': require('./jsx-handler-names'), + 'jsx-indent': require('./jsx-indent'), + 'jsx-indent-props': require('./jsx-indent-props'), + 'jsx-key': require('./jsx-key'), + 'jsx-max-depth': require('./jsx-max-depth'), + 'jsx-max-props-per-line': require('./jsx-max-props-per-line'), + 'jsx-newline': require('./jsx-newline'), + 'jsx-no-bind': require('./jsx-no-bind'), + 'jsx-no-comment-textnodes': require('./jsx-no-comment-textnodes'), + 'jsx-no-constructed-context-values': require('./jsx-no-constructed-context-values'), + 'jsx-no-duplicate-props': require('./jsx-no-duplicate-props'), + 'jsx-no-leaked-render': require('./jsx-no-leaked-render'), + 'jsx-no-literals': require('./jsx-no-literals'), + 'jsx-no-script-url': require('./jsx-no-script-url'), + 'jsx-no-target-blank': require('./jsx-no-target-blank'), + 'jsx-no-useless-fragment': require('./jsx-no-useless-fragment'), + 'jsx-one-expression-per-line': require('./jsx-one-expression-per-line'), + 'jsx-no-undef': require('./jsx-no-undef'), + 'jsx-curly-brace-presence': require('./jsx-curly-brace-presence'), + 'jsx-pascal-case': require('./jsx-pascal-case'), + 'jsx-fragments': require('./jsx-fragments'), + 'jsx-props-no-multi-spaces': require('./jsx-props-no-multi-spaces'), + 'jsx-props-no-spreading': require('./jsx-props-no-spreading'), + 'jsx-props-no-spread-multi': require('./jsx-props-no-spread-multi'), + 'jsx-sort-default-props': require('./jsx-sort-default-props'), + 'jsx-sort-props': require('./jsx-sort-props'), + 'jsx-space-before-closing': require('./jsx-space-before-closing'), + 'jsx-tag-spacing': require('./jsx-tag-spacing'), + 'jsx-uses-react': require('./jsx-uses-react'), + 'jsx-uses-vars': require('./jsx-uses-vars'), + 'jsx-wrap-multilines': require('./jsx-wrap-multilines'), + 'no-invalid-html-attribute': require('./no-invalid-html-attribute'), + 'no-access-state-in-setstate': require('./no-access-state-in-setstate'), + 'no-adjacent-inline-elements': require('./no-adjacent-inline-elements'), + 'no-array-index-key': require('./no-array-index-key'), + 'no-arrow-function-lifecycle': require('./no-arrow-function-lifecycle'), + 'no-children-prop': require('./no-children-prop'), + 'no-danger': require('./no-danger'), + 'no-danger-with-children': require('./no-danger-with-children'), + 'no-deprecated': require('./no-deprecated'), + 'no-did-mount-set-state': require('./no-did-mount-set-state'), + 'no-did-update-set-state': require('./no-did-update-set-state'), + 'no-direct-mutation-state': require('./no-direct-mutation-state'), + 'no-find-dom-node': require('./no-find-dom-node'), + 'no-is-mounted': require('./no-is-mounted'), + 'no-multi-comp': require('./no-multi-comp'), + 'no-namespace': require('./no-namespace'), + 'no-set-state': require('./no-set-state'), + 'no-string-refs': require('./no-string-refs'), + 'no-redundant-should-component-update': require('./no-redundant-should-component-update'), + 'no-render-return-value': require('./no-render-return-value'), + 'no-this-in-sfc': require('./no-this-in-sfc'), + 'no-typos': require('./no-typos'), + 'no-unescaped-entities': require('./no-unescaped-entities'), + 'no-unknown-property': require('./no-unknown-property'), + 'no-unsafe': require('./no-unsafe'), + 'no-unstable-nested-components': require('./no-unstable-nested-components'), + 'no-unused-class-component-methods': require('./no-unused-class-component-methods'), + 'no-unused-prop-types': require('./no-unused-prop-types'), + 'no-unused-state': require('./no-unused-state'), + 'no-object-type-as-default-prop': require('./no-object-type-as-default-prop'), + 'no-will-update-set-state': require('./no-will-update-set-state'), + 'prefer-es6-class': require('./prefer-es6-class'), + 'prefer-exact-props': require('./prefer-exact-props'), + 'prefer-read-only-props': require('./prefer-read-only-props'), + 'prefer-stateless-function': require('./prefer-stateless-function'), + 'prop-types': require('./prop-types'), + 'react-in-jsx-scope': require('./react-in-jsx-scope'), + 'require-default-props': require('./require-default-props'), + 'require-optimization': require('./require-optimization'), + 'require-render-return': require('./require-render-return'), + 'self-closing-comp': require('./self-closing-comp'), + 'sort-comp': require('./sort-comp'), + 'sort-default-props': require('./sort-default-props'), + 'sort-prop-types': require('./sort-prop-types'), + 'state-in-constructor': require('./state-in-constructor'), + 'static-property-placement': require('./static-property-placement'), + 'style-prop-object': require('./style-prop-object'), + 'void-dom-elements-no-children': require('./void-dom-elements-no-children'), +}; + +module.exports = rules; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1041d7eb6a14a15e24fcf9c002dade9d28fd7304 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-boolean-value.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..5b7e600df8b2cc8b1a1450bd662dd5f287d368ff --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-boolean-value.d.ts","sourceRoot":"","sources":["jsx-boolean-value.js"],"names":[],"mappings":"wBAwDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js new file mode 100644 index 0000000000000000000000000000000000000000..4572e7ff8d688b930be33829dc53f5dcb98ed367 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js @@ -0,0 +1,169 @@ +/** + * @fileoverview Enforce boolean attributes notation in JSX + * @author Yannick Croissant + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const exceptionsSchema = { + type: 'array', + items: { type: 'string', minLength: 1 }, + uniqueItems: true, +}; + +const ALWAYS = 'always'; +const NEVER = 'never'; + +/** + * @param {string} configuration + * @param {Set} exceptions + * @param {string} propName + * @returns {boolean} propName + */ +function isAlways(configuration, exceptions, propName) { + const isException = exceptions.has(propName); + if (configuration === ALWAYS) { + return !isException; + } + return isException; +} +/** + * @param {string} configuration + * @param {Set} exceptions + * @param {string} propName + * @returns {boolean} propName + */ +function isNever(configuration, exceptions, propName) { + const isException = exceptions.has(propName); + if (configuration === NEVER) { + return !isException; + } + return isException; +} + +const messages = { + 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}}`', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce boolean attributes notation in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-boolean-value'), + }, + fixable: 'code', + + messages, + + schema: { + anyOf: [{ + type: 'array', + items: [{ enum: [ALWAYS, NEVER] }], + additionalItems: false, + }, { + type: 'array', + items: [{ + enum: [ALWAYS], + }, { + type: 'object', + additionalProperties: false, + properties: { + [NEVER]: exceptionsSchema, + assumeUndefinedIsFalse: { + type: 'boolean', + }, + }, + }], + additionalItems: false, + }, { + type: 'array', + items: [{ + enum: [NEVER], + }, { + type: 'object', + additionalProperties: false, + properties: { + [ALWAYS]: exceptionsSchema, + assumeUndefinedIsFalse: { + type: 'boolean', + }, + }, + }], + additionalItems: false, + }], + }, + }, + + create(context) { + const configuration = context.options[0] || NEVER; + const configObject = context.options[1] || {}; + const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []); + + return { + JSXAttribute(node) { + const propName = node.name && node.name.name; + const value = node.value; + + if ( + isAlways(configuration, exceptions, propName) + && value === null + ) { + const messageId = 'setBoolean'; + const data = { propName }; + report(context, messages[messageId], messageId, { + node, + data, + fix(fixer) { + return fixer.insertTextAfter(node, '={true}'); + }, + }); + } + if ( + isNever(configuration, exceptions, propName) + && value + && value.type === 'JSXExpressionContainer' + && value.expression.value === true + ) { + const messageId = 'omitBoolean'; + const data = { propName }; + report(context, messages[messageId], messageId, { + node, + data, + fix(fixer) { + return fixer.removeRange([node.name.range[1], value.range[1]]); + }, + }); + } + if ( + isNever(configuration, exceptions, propName) + && configObject.assumeUndefinedIsFalse + && value + && value.type === 'JSXExpressionContainer' + && value.expression.value === false + ) { + const messageId = 'omitPropAndBoolean'; + const data = { propName }; + report(context, messages[messageId], messageId, { + node, + data, + fix(fixer) { + return fixer.removeRange([node.name.range[0], value.range[1]]); + }, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..26963d0243553a81f9384c76f15c16895494f559 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-child-element-spacing.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..531c3f2e8125f0afe84468cd7d07a0e89aed4358 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-child-element-spacing.d.ts","sourceRoot":"","sources":["jsx-child-element-spacing.js"],"names":[],"mappings":"wBA8CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js new file mode 100644 index 0000000000000000000000000000000000000000..06807efcc6eb71d71c0f5a83bb98c93a91875013 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js @@ -0,0 +1,117 @@ +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements + +// Note: 'br' is not included because whitespace around br tags is inconsequential to the rendered output +const INLINE_ELEMENTS = new Set([ + 'a', + 'abbr', + 'acronym', + 'b', + 'bdo', + 'big', + 'button', + 'cite', + 'code', + 'dfn', + 'em', + 'i', + 'img', + 'input', + 'kbd', + 'label', + 'map', + 'object', + 'q', + 'samp', + 'script', + 'select', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'textarea', + 'tt', + 'var', +]); + +const messages = { + spacingAfterPrev: 'Ambiguous spacing after previous element {{element}}', + spacingBeforeNext: 'Ambiguous spacing before next element {{element}}', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-child-element-spacing'), + }, + fixable: null, + + messages, + + schema: [], + }, + create(context) { + const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/; + const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/; + + const elementName = (node) => ( + node.openingElement + && node.openingElement.name + && node.openingElement.name.type === 'JSXIdentifier' + && node.openingElement.name.name + ); + + const isInlineElement = (node) => ( + node.type === 'JSXElement' + && INLINE_ELEMENTS.has(elementName(node)) + ); + + const handleJSX = (node) => { + let lastChild = null; + let child = null; + (node.children.concat([null])).forEach((nextChild) => { + if ( + (lastChild || nextChild) + && (!lastChild || isInlineElement(lastChild)) + && (child && (child.type === 'Literal' || child.type === 'JSXText')) + && (!nextChild || isInlineElement(nextChild)) + && true + ) { + if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) { + report(context, messages.spacingAfterPrev, 'spacingAfterPrev', { + node: lastChild, + loc: lastChild.loc.end, + data: { + element: elementName(lastChild), + }, + }); + } else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) { + report(context, messages.spacingBeforeNext, 'spacingBeforeNext', { + node: nextChild, + loc: nextChild.loc.start, + data: { + element: elementName(nextChild), + }, + }); + } + } + lastChild = child; + child = nextChild; + }); + }; + + return { + JSXElement: handleJSX, + JSXFragment: handleJSX, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..288f2746432410a6d2c2b31348dd53ced87a9d87 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-closing-bracket-location.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..626fa033061e58c858cc0233faffab95e8aadf56 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-closing-bracket-location.d.ts","sourceRoot":"","sources":["jsx-closing-bracket-location.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js new file mode 100644 index 0000000000000000000000000000000000000000..eed661e944f104d277131b07f42f658120f933d5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js @@ -0,0 +1,315 @@ +/** + * @fileoverview Validate closing bracket location in JSX + * @author Yannick Croissant + */ + +'use strict'; + +const has = require('hasown'); +const repeat = require('string.prototype.repeat'); + +const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + bracketLocation: 'The closing bracket must be {{location}}{{details}}', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce closing bracket location in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-closing-bracket-location'), + }, + fixable: 'code', + + messages, + + schema: [{ + anyOf: [ + { + enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'], + }, + { + type: 'object', + properties: { + location: { + enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'], + }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + nonEmpty: { + enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false], + }, + selfClosing: { + enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false], + }, + }, + additionalProperties: false, + }, + ], + }], + }, + + create(context) { + const MESSAGE_LOCATION = { + 'after-props': 'placed after the last prop', + 'after-tag': 'placed after the opening tag', + 'props-aligned': 'aligned with the last prop', + 'tag-aligned': 'aligned with the opening tag', + 'line-aligned': 'aligned with the line containing the opening tag', + }; + const DEFAULT_LOCATION = 'tag-aligned'; + + const config = context.options[0]; + const options = { + nonEmpty: DEFAULT_LOCATION, + selfClosing: DEFAULT_LOCATION, + }; + + if (typeof config === 'string') { + // simple shorthand [1, 'something'] + options.nonEmpty = config; + options.selfClosing = config; + } else if (typeof config === 'object') { + // [1, {location: 'something'}] (back-compat) + if (has(config, 'location')) { + options.nonEmpty = config.location; + options.selfClosing = config.location; + } + // [1, {nonEmpty: 'something'}] + if (has(config, 'nonEmpty')) { + options.nonEmpty = config.nonEmpty; + } + // [1, {selfClosing: 'something'}] + if (has(config, 'selfClosing')) { + options.selfClosing = config.selfClosing; + } + } + + /** + * Get expected location for the closing bracket + * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop + * @return {string} Expected location for the closing bracket + */ + function getExpectedLocation(tokens) { + let location; + // Is always after the opening tag if there is no props + if (typeof tokens.lastProp === 'undefined') { + location = 'after-tag'; + // Is always after the last prop if this one is on the same line as the opening bracket + } else if (tokens.opening.line === tokens.lastProp.lastLine) { + location = 'after-props'; + // Else use configuration dependent on selfClosing property + } else { + location = tokens.selfClosing ? options.selfClosing : options.nonEmpty; + } + return location; + } + + /** + * Get the correct 0-indexed column for the closing bracket, given the + * expected location. + * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop + * @param {string} expectedLocation Expected location for the closing bracket + * @return {?Number} The correct column for the closing bracket, or null + */ + function getCorrectColumn(tokens, expectedLocation) { + switch (expectedLocation) { + case 'props-aligned': + return tokens.lastProp.column; + case 'tag-aligned': + return tokens.opening.column; + case 'line-aligned': + return tokens.openingStartOfLine.column; + default: + return null; + } + } + + /** + * Check if the closing bracket is correctly located + * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop + * @param {string} expectedLocation Expected location for the closing bracket + * @return {boolean} True if the closing bracket is correctly located, false if not + */ + function hasCorrectLocation(tokens, expectedLocation) { + switch (expectedLocation) { + case 'after-tag': + return tokens.tag.line === tokens.closing.line; + case 'after-props': + return tokens.lastProp.lastLine === tokens.closing.line; + case 'props-aligned': + case 'tag-aligned': + case 'line-aligned': { + const correctColumn = getCorrectColumn(tokens, expectedLocation); + return correctColumn === tokens.closing.column; + } + default: + return true; + } + } + + /** + * Get the characters used for indentation on the line to be matched + * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop + * @param {string} expectedLocation Expected location for the closing bracket + * @param {number} [correctColumn] Expected column for the closing bracket. Default to 0 + * @return {string} The characters used for indentation + */ + function getIndentation(tokens, expectedLocation, correctColumn) { + const newColumn = correctColumn || 0; + let indentation; + let spaces = ''; + switch (expectedLocation) { + case 'props-aligned': + indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.lastProp.firstLine - 1])[0]; + break; + case 'tag-aligned': + case 'line-aligned': + indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.opening.line - 1])[0]; + break; + default: + indentation = ''; + } + if (indentation.length + 1 < newColumn) { + // Non-whitespace characters were included in the column offset + spaces = repeat(' ', +correctColumn - indentation.length); + } + return indentation + spaces; + } + + /** + * Get the locations of the opening bracket, closing bracket, last prop, and + * start of opening line. + * @param {ASTNode} node The node to check + * @return {Object} Locations of the opening bracket, closing bracket, last + * prop and start of opening line. + */ + function getTokensLocations(node) { + 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; + let lastProp; + if (node.attributes.length) { + lastProp = node.attributes[node.attributes.length - 1]; + lastProp = { + column: sourceCode.getFirstToken(lastProp).loc.start.column, + firstLine: sourceCode.getFirstToken(lastProp).loc.start.line, + lastLine: sourceCode.getLastToken(lastProp).loc.end.line, + }; + } + const openingLine = sourceCode.lines[opening.line - 1]; + const closingLine = sourceCode.lines[closing.line - 1]; + const isTab = { + openTab: /^\t/.test(openingLine), + closeTab: /^\t/.test(closingLine), + }; + const openingStartOfLine = { + column: /^\s*/.exec(openingLine)[0].length, + line: opening.line, + }; + return { + isTab, + tag, + opening, + closing, + lastProp, + selfClosing: node.selfClosing, + openingStartOfLine, + }; + } + + /** + * Get an unique ID for a given JSXOpeningElement + * + * @param {ASTNode} node The AST node being checked. + * @returns {string} Unique ID (based on its range) + */ + function getOpeningElementId(node) { + return node.range.join(':'); + } + + const lastAttributeNode = {}; + + return { + JSXAttribute(node) { + lastAttributeNode[getOpeningElementId(node.parent)] = node; + }, + + JSXSpreadAttribute(node) { + lastAttributeNode[getOpeningElementId(node.parent)] = node; + }, + + 'JSXOpeningElement:exit'(node) { + const attributeNode = lastAttributeNode[getOpeningElementId(node)]; + const cachedLastAttributeEndPos = attributeNode ? attributeNode.range[1] : null; + + let expectedNextLine; + const tokens = getTokensLocations(node); + const expectedLocation = getExpectedLocation(tokens); + let usingSameIndentation = true; + + if (expectedLocation === 'tag-aligned') { + usingSameIndentation = tokens.isTab.openTab === tokens.isTab.closeTab; + } + + if (hasCorrectLocation(tokens, expectedLocation) && usingSameIndentation) { + return; + } + + const data = { + location: MESSAGE_LOCATION[expectedLocation], + details: '', + }; + const correctColumn = getCorrectColumn(tokens, expectedLocation); + + if (correctColumn !== null) { + expectedNextLine = tokens.lastProp + && (tokens.lastProp.lastLine === tokens.closing.line); + data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? ' on the next line)' : ')'}`; + } + + report(context, messages.bracketLocation, 'bracketLocation', { + node, + loc: tokens.closing, + data, + fix(fixer) { + const closingTag = tokens.selfClosing ? '/>' : '>'; + switch (expectedLocation) { + case 'after-tag': + if (cachedLastAttributeEndPos) { + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], + (expectedNextLine ? '\n' : '') + closingTag); + } + return fixer.replaceTextRange([node.name.range[1], node.range[1]], + (expectedNextLine ? '\n' : ' ') + closingTag); + case 'after-props': + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], + (expectedNextLine ? '\n' : '') + closingTag); + case 'props-aligned': + case 'tag-aligned': + case 'line-aligned': + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], + `\n${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`); + default: + return true; + } + }, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..258a41346b0e64e598aae3f5c9aa59d25ace5785 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-closing-tag-location.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..a376ecb2146befb41009965bbcc801238d1dbc6d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-closing-tag-location.d.ts","sourceRoot":"","sources":["jsx-closing-tag-location.js"],"names":[],"mappings":"wBAgCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js new file mode 100644 index 0000000000000000000000000000000000000000..5f6f6efe38df70c90529f2bf59cd171c34e484b3 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js @@ -0,0 +1,142 @@ +/** + * @fileoverview Validate closing tag location in JSX + * @author Ross Solomon + */ + +'use strict'; + +const repeat = require('string.prototype.repeat'); +const has = require('hasown'); + +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + onOwnLine: 'Closing tag of a multiline JSX expression must be on its own line.', + matchIndent: 'Expected closing tag to match indentation of opening.', + alignWithOpening: 'Expected closing tag to be aligned with the line containing the opening tag', +}; + +const defaultOption = 'tag-aligned'; + +const optionMessageMap = { + 'tag-aligned': 'matchIndent', + 'line-aligned': 'alignWithOpening', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce closing tag location for multiline JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-closing-tag-location'), + }, + fixable: 'whitespace', + messages, + schema: [{ + anyOf: [ + { + enum: ['tag-aligned', 'line-aligned'], + }, + { + type: 'object', + properties: { + location: { + enum: ['tag-aligned', 'line-aligned'], + }, + }, + additionalProperties: false, + }, + ], + }], + }, + + create(context) { + const config = context.options[0]; + let option = defaultOption; + + if (typeof config === 'string') { + option = config; + } else if (typeof config === 'object') { + if (has(config, 'location')) { + option = config.location; + } + } + + function getIndentation(openingStartOfLine, opening) { + if (option === 'line-aligned') return openingStartOfLine.column; + if (option === 'tag-aligned') return opening.loc.start.column; + } + + function handleClosingElement(node) { + if (!node.parent) { + return; + } + const sourceCode = getSourceCode(context); + + const opening = node.parent.openingElement || node.parent.openingFragment; + const openingLoc = sourceCode.getFirstToken(opening).loc.start; + const openingLine = sourceCode.lines[openingLoc.line - 1]; + + const openingStartOfLine = { + column: /^\s*/.exec(openingLine)[0].length, + line: openingLoc.line, + }; + + if (opening.loc.start.line === node.loc.start.line) { + return; + } + + if ( + opening.loc.start.column === node.loc.start.column + && option === 'tag-aligned' + ) { + return; + } + + if ( + openingStartOfLine.column === node.loc.start.column + && option === 'line-aligned' + ) { + return; + } + + const messageId = astUtil.isNodeFirstInLine(context, node) + ? optionMessageMap[option] + : 'onOwnLine'; + + report(context, messages[messageId], messageId, { + node, + loc: node.loc, + fix(fixer) { + const indent = repeat( + ' ', + getIndentation(openingStartOfLine, opening) + ); + + if (astUtil.isNodeFirstInLine(context, node)) { + return fixer.replaceTextRange( + [node.range[0] - node.loc.start.column, node.range[0]], + indent + ); + } + + return fixer.insertTextBefore(node, `\n${indent}`); + }, + }); + } + + return { + JSXClosingElement: handleClosingElement, + JSXClosingFragment: handleClosingElement, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6cbfc15333996ca4e2f8ed3ecb433ba0dd065894 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-curly-brace-presence.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..399dcdd74f34021f5f86b5353e42bb349e3759cc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-curly-brace-presence.d.ts","sourceRoot":"","sources":["jsx-curly-brace-presence.js"],"names":[],"mappings":"wBAgJW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js new file mode 100644 index 0000000000000000000000000000000000000000..0f17143ffa0feff3a673d6e6fcbb47f3bfdecd5c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js @@ -0,0 +1,421 @@ +/** + * @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX + * @author Jacky Ho + * @author Simon Lydell + */ + +'use strict'; + +const arrayIncludes = require('array-includes'); + +const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const OPTION_ALWAYS = 'always'; +const OPTION_NEVER = 'never'; +const OPTION_IGNORE = 'ignore'; + +const OPTION_VALUES = [ + OPTION_ALWAYS, + OPTION_NEVER, + OPTION_IGNORE, +]; +const DEFAULT_CONFIG = { props: OPTION_NEVER, children: OPTION_NEVER, propElementValues: OPTION_IGNORE }; + +const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g; + +function containsLineTerminators(rawStringValue) { + return /[\n\r\u2028\u2029]/.test(rawStringValue); +} + +function containsBackslash(rawStringValue) { + return arrayIncludes(rawStringValue, '\\'); +} + +function containsHTMLEntity(rawStringValue) { + return HTML_ENTITY_REGEX().test(rawStringValue); +} + +function containsOnlyHtmlEntities(rawStringValue) { + return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''; +} + +function containsDisallowedJSXTextChars(rawStringValue) { + return /[{<>}]/.test(rawStringValue); +} + +function containsQuoteCharacters(value) { + return /['"]/.test(value); +} + +function containsMultilineComment(value) { + return /\/\*/.test(value); +} + +function escapeDoubleQuotes(rawStringValue) { + return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"'); +} + +function escapeBackslashes(rawStringValue) { + return rawStringValue.replace(/\\/g, '\\\\'); +} + +function needToEscapeCharacterForJSX(raw, node) { + return ( + containsBackslash(raw) + || containsHTMLEntity(raw) + || (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw)) + ); +} + +function containsWhitespaceExpression(child) { + if (child.type === 'JSXExpressionContainer') { + const value = child.expression.value; + return value ? jsxUtil.isWhiteSpaces(value) : false; + } + return false; +} + +function isLineBreak(text) { + return containsLineTerminators(text) && text.trim() === ''; +} + +function wrapNonHTMLEntities(text) { + const HTML_ENTITY = ''; + const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => ( + word === '' ? '' : `{${JSON.stringify(word)}}` + )).join(HTML_ENTITY); + + const htmlEntities = text.match(HTML_ENTITY_REGEX()); + return htmlEntities.reduce((acc, htmlEntity) => ( + acc.replace(HTML_ENTITY, htmlEntity) + ), withCurlyBraces); +} + +function wrapWithCurlyBraces(rawText) { + if (!containsLineTerminators(rawText)) { + return `{${JSON.stringify(rawText)}}`; + } + + return rawText.split('\n').map((line) => { + if (line.trim() === '') { + return line; + } + const firstCharIndex = line.search(/[^\s]/); + const leftWhitespace = line.slice(0, firstCharIndex); + const text = line.slice(firstCharIndex); + + if (containsHTMLEntity(line)) { + return `${leftWhitespace}${wrapNonHTMLEntities(text)}`; + } + return `${leftWhitespace}{${JSON.stringify(text)}}`; + }).join('\n'); +} + +function isWhiteSpaceLiteral(node) { + return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value); +} + +function isStringWithTrailingWhiteSpaces(value) { + return /^\s|\s$/.test(value); +} + +function isLiteralWithTrailingWhiteSpaces(node) { + return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value); +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + unnecessaryCurly: 'Curly braces are unnecessary here.', + missingCurly: 'Need to wrap this literal in a JSX expression.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-curly-brace-presence'), + }, + fixable: 'code', + + messages, + + schema: [ + { + anyOf: [ + { + type: 'object', + properties: { + props: { enum: OPTION_VALUES }, + children: { enum: OPTION_VALUES }, + propElementValues: { enum: OPTION_VALUES }, + }, + additionalProperties: false, + }, + { + enum: OPTION_VALUES, + }, + ], + }, + ], + }, + + create(context) { + const ruleOptions = context.options[0]; + const userConfig = typeof ruleOptions === 'string' + ? { props: ruleOptions, children: ruleOptions, propElementValues: OPTION_IGNORE } + : Object.assign({}, DEFAULT_CONFIG, ruleOptions); + + /** + * Report and fix an unnecessary curly brace violation on a node + * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression + */ + function reportUnnecessaryCurly(JSXExpressionNode) { + report(context, messages.unnecessaryCurly, 'unnecessaryCurly', { + node: JSXExpressionNode, + fix(fixer) { + const expression = JSXExpressionNode.expression; + + let textToReplace; + if (jsxUtil.isJSX(expression)) { + textToReplace = getText(context, expression); + } else { + const expressionType = expression && expression.type; + const parentType = JSXExpressionNode.parent.type; + + if (parentType === 'JSXAttribute') { + if (expressionType !== 'TemplateLiteral' && /["]/.test(expression.raw.slice(1, -1))) { + textToReplace = expression.raw; + } else { + textToReplace = `"${expressionType === 'TemplateLiteral' + ? expression.quasis[0].value.raw + : expression.raw.slice(1, -1) + }"`; + } + } else if (jsxUtil.isJSX(expression)) { + textToReplace = getText(context, expression); + } else { + textToReplace = expressionType === 'TemplateLiteral' + ? expression.quasis[0].value.cooked : expression.value; + } + } + + return fixer.replaceText(JSXExpressionNode, textToReplace); + }, + }); + } + + function reportMissingCurly(literalNode) { + report(context, messages.missingCurly, 'missingCurly', { + node: literalNode, + fix(fixer) { + if (jsxUtil.isJSX(literalNode)) { + return fixer.replaceText(literalNode, `{${getText(context, literalNode)}}`); + } + + // If a HTML entity name is found, bail out because it can be fixed + // by either using the real character or the unicode equivalent. + // If it contains any line terminator character, bail out as well. + if ( + containsOnlyHtmlEntities(literalNode.raw) + || (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw)) + || isLineBreak(literalNode.raw) + ) { + return null; + } + + const expression = literalNode.parent.type === 'JSXAttribute' + ? `{"${escapeDoubleQuotes(escapeBackslashes( + literalNode.raw.slice(1, -1) + ))}"}` + : wrapWithCurlyBraces(literalNode.raw); + + return fixer.replaceText(literalNode, expression); + }, + }); + } + + // Bail out if there is any character that needs to be escaped in JSX + // because escaping decreases readability and the original code may be more + // readable anyway or intentional for other specific reasons + function lintUnnecessaryCurly(JSXExpressionNode) { + const expression = JSXExpressionNode.expression; + const expressionType = expression.type; + + const sourceCode = getSourceCode(context); + // Curly braces containing comments are necessary + if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0) { + return; + } + + if ( + (expressionType === 'Literal' || expressionType === 'JSXText') + && typeof expression.value === 'string' + && ( + (JSXExpressionNode.parent.type === 'JSXAttribute' && !isWhiteSpaceLiteral(expression)) + || !isLiteralWithTrailingWhiteSpaces(expression) + ) + && !containsMultilineComment(expression.value) + && !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && ( + jsxUtil.isJSX(JSXExpressionNode.parent) + || (!containsQuoteCharacters(expression.value) || typeof expression.value === 'string') + ) + ) { + reportUnnecessaryCurly(JSXExpressionNode); + } else if ( + expressionType === 'TemplateLiteral' + && expression.expressions.length === 0 + && expression.quasis[0].value.raw.indexOf('\n') === -1 + && !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw) + && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode) + && !containsQuoteCharacters(expression.quasis[0].value.cooked) + ) { + reportUnnecessaryCurly(JSXExpressionNode); + } else if (jsxUtil.isJSX(expression)) { + reportUnnecessaryCurly(JSXExpressionNode); + } + } + + function areRuleConditionsSatisfied(parent, config, ruleCondition) { + return ( + parent.type === 'JSXAttribute' + && typeof config.props === 'string' + && config.props === ruleCondition + ) || ( + jsxUtil.isJSX(parent) + && typeof config.children === 'string' + && config.children === ruleCondition + ); + } + + function getAdjacentSiblings(node, children) { + for (let i = 1; i < children.length - 1; i++) { + const child = children[i]; + if (node === child) { + return [children[i - 1], children[i + 1]]; + } + } + if (node === children[0] && children[1]) { + return [children[1]]; + } + if (node === children[children.length - 1] && children[children.length - 2]) { + return [children[children.length - 2]]; + } + return []; + } + + function hasAdjacentJsxExpressionContainers(node, children) { + if (!children) { + return false; + } + const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child)); + const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral); + + return adjSiblings.some((x) => x.type && x.type === 'JSXExpressionContainer'); + } + function hasAdjacentJsx(node, children) { + if (!children) { + return false; + } + const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child)); + const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral); + + return adjSiblings.some((x) => x.type && arrayIncludes(['JSXExpressionContainer', 'JSXElement'], x.type)); + } + function shouldCheckForUnnecessaryCurly(node, config) { + const parent = node.parent; + // Bail out if the parent is a JSXAttribute & its contents aren't + // StringLiteral or TemplateLiteral since e.g + // } prop2={...} /> + + if ( + parent.type && parent.type === 'JSXAttribute' + && (node.expression && node.expression.type + && node.expression.type !== 'Literal' + && node.expression.type !== 'StringLiteral' + && node.expression.type !== 'TemplateLiteral') + ) { + return false; + } + + // If there are adjacent `JsxExpressionContainer` then there is no need, + // to check for unnecessary curly braces. + if (jsxUtil.isJSX(parent) && hasAdjacentJsxExpressionContainers(node, parent.children)) { + return false; + } + if (containsWhitespaceExpression(node) && hasAdjacentJsx(node, parent.children)) { + return false; + } + if ( + parent.children + && parent.children.length === 1 + && containsWhitespaceExpression(node) + ) { + return false; + } + + return areRuleConditionsSatisfied(parent, config, OPTION_NEVER); + } + + function shouldCheckForMissingCurly(node, config) { + if (jsxUtil.isJSX(node)) { + return config.propElementValues !== OPTION_IGNORE; + } + if ( + isLineBreak(node.raw) + || containsOnlyHtmlEntities(node.raw) + ) { + return false; + } + const parent = node.parent; + if ( + parent.children + && parent.children.length === 1 + && containsWhitespaceExpression(parent.children[0]) + ) { + return false; + } + + return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS); + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + 'JSXAttribute > JSXExpressionContainer > JSXElement'(node) { + if (userConfig.propElementValues === OPTION_NEVER) { + reportUnnecessaryCurly(node.parent); + } + }, + + JSXExpressionContainer(node) { + if (shouldCheckForUnnecessaryCurly(node, userConfig)) { + lintUnnecessaryCurly(node); + } + }, + + 'JSXAttribute > JSXElement, Literal, JSXText'(node) { + if (shouldCheckForMissingCurly(node, userConfig)) { + reportMissingCurly(node); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3456a0cb387ff354d8b696eb4b1ddc4de4137a3b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-curly-newline.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..5a715baf9de8c6873ce3e3c59c8a380bb9138256 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-curly-newline.d.ts","sourceRoot":"","sources":["jsx-curly-newline.js"],"names":[],"mappings":"wBA+CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js new file mode 100644 index 0000000000000000000000000000000000000000..046ff795829e13400f72011cc751a5227d1757cf --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js @@ -0,0 +1,187 @@ +/** + * @fileoverview enforce consistent line breaks inside jsx curly + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const eslintUtil = require('../util/eslint'); +const report = require('../util/report'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function getNormalizedOption(context) { + const rawOption = context.options[0] || 'consistent'; + + if (rawOption === 'consistent') { + return { + multiline: 'consistent', + singleline: 'consistent', + }; + } + + if (rawOption === 'never') { + return { + multiline: 'forbid', + singleline: 'forbid', + }; + } + + return { + multiline: rawOption.multiline || 'consistent', + singleline: rawOption.singleline || 'consistent', + }; +} + +const messages = { + expectedBefore: 'Expected newline before \'}\'.', + expectedAfter: 'Expected newline after \'{\'.', + unexpectedBefore: 'Unexpected newline before \'}\'.', + unexpectedAfter: 'Unexpected newline after \'{\'.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'layout', + + docs: { + description: 'Enforce consistent linebreaks in curly braces in JSX attributes and expressions', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-curly-newline'), + }, + + fixable: 'whitespace', + + schema: [ + { + anyOf: [ + { + enum: ['consistent', 'never'], + }, + { + type: 'object', + properties: { + singleline: { enum: ['consistent', 'require', 'forbid'] }, + multiline: { enum: ['consistent', 'require', 'forbid'] }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages, + }, + + create(context) { + const sourceCode = getSourceCode(context); + const option = getNormalizedOption(context); + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + /** + * Determines whether two adjacent tokens are on the same line. + * @param {Object} left - The left token object. + * @param {Object} right - The right token object. + * @returns {boolean} Whether or not the tokens are on the same line. + */ + function isTokenOnSameLine(left, right) { + return left.loc.end.line === right.loc.start.line; + } + + /** + * Determines whether there should be newlines inside curlys + * @param {ASTNode} expression The expression contained in the curlys + * @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code. + * @returns {boolean} `true` if there should be newlines inside the function curlys + */ + function shouldHaveNewlines(expression, hasLeftNewline) { + const isMultiline = expression.loc.start.line !== expression.loc.end.line; + + switch (isMultiline ? option.multiline : option.singleline) { + case 'forbid': return false; + case 'require': return true; + case 'consistent': + default: return hasLeftNewline; + } + } + + /** + * Validates curlys + * @param {Object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode} expression The expression inside the curly + * @returns {void} + */ + function validateCurlys(curlys, expression) { + const leftCurly = curlys.leftCurly; + const rightCurly = curlys.rightCurly; + const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly); + const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly); + const hasLeftNewline = !isTokenOnSameLine(leftCurly, tokenAfterLeftCurly); + const hasRightNewline = !isTokenOnSameLine(tokenBeforeRightCurly, rightCurly); + const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline); + + if (hasLeftNewline && !needsNewlines) { + report(context, messages.unexpectedAfter, 'unexpectedAfter', { + node: leftCurly, + fix(fixer) { + 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. + : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]); + }, + }); + } else if (!hasLeftNewline && needsNewlines) { + report(context, messages.expectedAfter, 'expectedAfter', { + node: leftCurly, + fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n'), + }); + } + + if (hasRightNewline && !needsNewlines) { + report(context, messages.unexpectedBefore, 'unexpectedBefore', { + node: rightCurly, + fix(fixer) { + 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. + : fixer.removeRange([ + tokenBeforeRightCurly.range[1], + rightCurly.range[0], + ]); + }, + }); + } else if (!hasRightNewline && needsNewlines) { + report(context, messages.expectedBefore, 'expectedBefore', { + node: rightCurly, + fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n'), + }); + } + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + JSXExpressionContainer(node) { + const curlyTokens = { + leftCurly: sourceCode.getFirstToken(node), + rightCurly: sourceCode.getLastToken(node), + }; + validateCurlys(curlyTokens, node.expression); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..d148673b2f25789f6201e2976475b1fb8aa2990f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-curly-spacing.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..0fba86b65eb761593d97e61cad4c3cf4203eea43 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-curly-spacing.d.ts","sourceRoot":"","sources":["jsx-curly-spacing.js"],"names":[],"mappings":"wBAqCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js new file mode 100644 index 0000000000000000000000000000000000000000..fda49381ce67d11d3d9b14c2d83e7d46147315f6 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js @@ -0,0 +1,432 @@ +/** + * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes. + * @author Jamund Ferguson + * @author Brandyn Bennett + * @author Michael Ficarra + * @author Vignesh Anand + * @author Jamund Ferguson + * @author Yannick Croissant + * @author Erik Wendel + */ + +'use strict'; + +const has = require('hasown'); +const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const SPACING = { + always: 'always', + never: 'never', +}; +const SPACING_VALUES = [SPACING.always, SPACING.never]; + +const messages = { + noNewlineAfter: 'There should be no newline after \'{{token}}\'', + noNewlineBefore: 'There should be no newline before \'{{token}}\'', + noSpaceAfter: 'There should be no space after \'{{token}}\'', + noSpaceBefore: 'There should be no space before \'{{token}}\'', + spaceNeededAfter: 'A space is required after \'{{token}}\'', + spaceNeededBefore: 'A space is required before \'{{token}}\'', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-curly-spacing'), + }, + fixable: 'code', + + messages, + + schema: { + definitions: { + basicConfig: { + type: 'object', + properties: { + when: { + enum: SPACING_VALUES, + }, + allowMultiline: { + type: 'boolean', + }, + spacing: { + type: 'object', + properties: { + objectLiterals: { + enum: SPACING_VALUES, + }, + }, + }, + }, + }, + basicConfigOrBoolean: { + anyOf: [{ + $ref: '#/definitions/basicConfig', + }, { + type: 'boolean', + }], + }, + }, + type: 'array', + items: [{ + anyOf: [{ + allOf: [{ + $ref: '#/definitions/basicConfig', + }, { + type: 'object', + properties: { + attributes: { + $ref: '#/definitions/basicConfigOrBoolean', + }, + children: { + $ref: '#/definitions/basicConfigOrBoolean', + }, + }, + }], + }, { + enum: SPACING_VALUES, + }], + }, { + type: 'object', + properties: { + allowMultiline: { + type: 'boolean', + }, + spacing: { + type: 'object', + properties: { + objectLiterals: { + enum: SPACING_VALUES, + }, + }, + }, + }, + additionalProperties: false, + }], + }, + }, + + create(context) { + function normalizeConfig(configOrTrue, defaults, lastPass) { + const config = configOrTrue === true ? {} : configOrTrue; + const when = config.when || defaults.when; + const allowMultiline = has(config, 'allowMultiline') ? config.allowMultiline : defaults.allowMultiline; + const spacing = config.spacing || {}; + let objectLiteralSpaces = spacing.objectLiterals || defaults.objectLiteralSpaces; + if (lastPass) { + // On the final pass assign the values that should be derived from others if they are still undefined + objectLiteralSpaces = objectLiteralSpaces || when; + } + + return { + when, + allowMultiline, + objectLiteralSpaces, + }; + } + + const DEFAULT_WHEN = SPACING.never; + const DEFAULT_ALLOW_MULTILINE = true; + const DEFAULT_ATTRIBUTES = true; + const DEFAULT_CHILDREN = false; + + let originalConfig = context.options[0] || {}; + if (SPACING_VALUES.indexOf(originalConfig) !== -1) { + originalConfig = Object.assign({ when: context.options[0] }, context.options[1]); + } + const defaultConfig = normalizeConfig(originalConfig, { + when: DEFAULT_WHEN, + allowMultiline: DEFAULT_ALLOW_MULTILINE, + }); + const attributes = has(originalConfig, 'attributes') ? originalConfig.attributes : DEFAULT_ATTRIBUTES; + const attributesConfig = attributes ? normalizeConfig(attributes, defaultConfig, true) : null; + const children = has(originalConfig, 'children') ? originalConfig.children : DEFAULT_CHILDREN; + const childrenConfig = children ? normalizeConfig(children, defaultConfig, true) : null; + + // -------------------------------------------------------------------------- + // Helpers + // -------------------------------------------------------------------------- + + /** + * Determines whether two adjacent tokens have a newline between them. + * @param {Object} left - The left token object. + * @param {Object} right - The right token object. + * @returns {boolean} Whether or not there is a newline between the tokens. + */ + function isMultiline(left, right) { + return left.loc.end.line !== right.loc.start.line; + } + + /** + * Trims text of whitespace between two ranges + * @param {Fixer} fixer - the eslint fixer object + * @param {number} fromLoc - the start location + * @param {number} toLoc - the end location + * @param {string} mode - either 'start' or 'end' + * @param {string=} spacing - a spacing value that will optionally add a space to the removed text + * @returns {Object|*|{range, text}} + */ + function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing) { + let replacementText = getSourceCode(context).text.slice(fromLoc, toLoc); + if (mode === 'start') { + replacementText = replacementText.replace(/^\s+/gm, ''); + } else { + replacementText = replacementText.replace(/\s+$/gm, ''); + } + if (spacing === SPACING.always) { + if (mode === 'start') { + replacementText += ' '; + } else { + replacementText = ` ${replacementText}`; + } + } + return fixer.replaceTextRange([fromLoc, toLoc], replacementText); + } + + /** + * Reports that there shouldn't be a newline after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @param {string} spacing + * @returns {void} + */ + function reportNoBeginningNewline(node, token, spacing) { + report(context, messages.noNewlineAfter, 'noNewlineAfter', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + const nextToken = getSourceCode(context).getTokenAfter(token); + return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start', spacing); + }, + }); + } + + /** + * Reports that there shouldn't be a newline before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @param {string} spacing + * @returns {void} + */ + function reportNoEndingNewline(node, token, spacing) { + report(context, messages.noNewlineBefore, 'noNewlineBefore', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + const previousToken = getSourceCode(context).getTokenBefore(token); + return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end', spacing); + }, + }); + } + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + report(context, messages.noSpaceAfter, 'noSpaceAfter', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + const sourceCode = getSourceCode(context); + const nextToken = sourceCode.getTokenAfter(token); + let nextComment; + + // eslint >=4.x + if (sourceCode.getCommentsAfter) { + nextComment = sourceCode.getCommentsAfter(token); + // eslint 3.x + } else { + const potentialComment = sourceCode.getTokenAfter(token, { includeComments: true }); + nextComment = nextToken === potentialComment ? [] : [potentialComment]; + } + + // Take comments into consideration to narrow the fix range to what is actually affected. (See #1414) + if (nextComment.length > 0) { + return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), 'start'); + } + + return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start'); + }, + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + report(context, messages.noSpaceBefore, 'noSpaceBefore', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + const sourceCode = getSourceCode(context); + const previousToken = sourceCode.getTokenBefore(token); + let previousComment; + + // eslint >=4.x + if (sourceCode.getCommentsBefore) { + previousComment = sourceCode.getCommentsBefore(token); + // eslint 3.x + } else { + const potentialComment = sourceCode.getTokenBefore(token, { includeComments: true }); + previousComment = previousToken === potentialComment ? [] : [potentialComment]; + } + + // Take comments into consideration to narrow the fix range to what is actually affected. (See #1414) + if (previousComment.length > 0) { + return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], 'end'); + } + + return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end'); + }, + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + report(context, messages.spaceNeededAfter, 'spaceNeededAfter', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, ' '); + }, + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + report(context, messages.spaceNeededBefore, 'spaceNeededBefore', { + node, + loc: token.loc.start, + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, ' '); + }, + }); + } + + /** + * Determines if spacing in curly braces is valid. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function validateBraceSpacing(node) { + let config; + switch (node.parent.type) { + case 'JSXAttribute': + case 'JSXOpeningElement': + config = attributesConfig; + break; + + case 'JSXElement': + case 'JSXFragment': + config = childrenConfig; + break; + + default: + return; + } + if (config === null) { + return; + } + + const sourceCode = getSourceCode(context); + const first = sourceCode.getFirstToken(node); + const last = sourceCode.getLastToken(node); + let second = sourceCode.getTokenAfter(first, { includeComments: true }); + let penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); + + if (!second) { + second = sourceCode.getTokenAfter(first); + const leadingComments = sourceCode.getNodeByRangeIndex(second.range[0]).leadingComments; + second = leadingComments ? leadingComments[0] : second; + } + if (!penultimate) { + penultimate = sourceCode.getTokenBefore(last); + const trailingComments = sourceCode.getNodeByRangeIndex(penultimate.range[0]).trailingComments; + penultimate = trailingComments ? trailingComments[trailingComments.length - 1] : penultimate; + } + + const isObjectLiteral = first.value === second.value; + const spacing = isObjectLiteral ? config.objectLiteralSpaces : config.when; + if (spacing === SPACING.always) { + if (!sourceCode.isSpaceBetweenTokens(first, second)) { + reportRequiredBeginningSpace(node, first); + } else if (!config.allowMultiline && isMultiline(first, second)) { + reportNoBeginningNewline(node, first, spacing); + } + if (!sourceCode.isSpaceBetweenTokens(penultimate, last)) { + reportRequiredEndingSpace(node, last); + } else if (!config.allowMultiline && isMultiline(penultimate, last)) { + reportNoEndingNewline(node, last, spacing); + } + } else if (spacing === SPACING.never) { + if (isMultiline(first, second)) { + if (!config.allowMultiline) { + reportNoBeginningNewline(node, first, spacing); + } + } else if (sourceCode.isSpaceBetweenTokens(first, second)) { + reportNoBeginningSpace(node, first); + } + if (isMultiline(penultimate, last)) { + if (!config.allowMultiline) { + reportNoEndingNewline(node, last, spacing); + } + } else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) { + reportNoEndingSpace(node, last); + } + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXExpressionContainer: validateBraceSpacing, + JSXSpreadAttribute: validateBraceSpacing, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..805d6a98df8841535238cdbf7196bc30f34cf1cc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-equals-spacing.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..00ab38e2706f57c01d0230408126c71e7e410255 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-equals-spacing.d.ts","sourceRoot":"","sources":["jsx-equals-spacing.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js new file mode 100644 index 0000000000000000000000000000000000000000..6920fb24bf6aef9ff714ac5e0821053002a44061 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js @@ -0,0 +1,112 @@ +/** + * @fileoverview Disallow or enforce spaces around equal signs in JSX attributes. + * @author ryym + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noSpaceBefore: 'There should be no space before \'=\'', + noSpaceAfter: 'There should be no space after \'=\'', + needSpaceBefore: 'A space is required before \'=\'', + needSpaceAfter: 'A space is required after \'=\'', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce or disallow spaces around equal signs in JSX attributes', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-equals-spacing'), + }, + fixable: 'code', + + messages, + + schema: [{ + enum: ['always', 'never'], + }], + }, + + create(context) { + const config = context.options[0] || 'never'; + + /** + * Determines a given attribute node has an equal sign. + * @param {ASTNode} attrNode - The attribute node. + * @returns {boolean} Whether or not the attriute node has an equal sign. + */ + function hasEqual(attrNode) { + return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null; + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXOpeningElement(node) { + node.attributes.forEach((attrNode) => { + if (!hasEqual(attrNode)) { + return; + } + + const sourceCode = getSourceCode(context); + const equalToken = sourceCode.getTokenAfter(attrNode.name); + const spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken); + const spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value); + + if (config === 'never') { + if (spacedBefore) { + report(context, messages.noSpaceBefore, 'noSpaceBefore', { + node: attrNode, + loc: equalToken.loc.start, + fix(fixer) { + return fixer.removeRange([attrNode.name.range[1], equalToken.range[0]]); + }, + }); + } + if (spacedAfter) { + report(context, messages.noSpaceAfter, 'noSpaceAfter', { + node: attrNode, + loc: equalToken.loc.start, + fix(fixer) { + return fixer.removeRange([equalToken.range[1], attrNode.value.range[0]]); + }, + }); + } + } else if (config === 'always') { + if (!spacedBefore) { + report(context, messages.needSpaceBefore, 'needSpaceBefore', { + node: attrNode, + loc: equalToken.loc.start, + fix(fixer) { + return fixer.insertTextBefore(equalToken, ' '); + }, + }); + } + if (!spacedAfter) { + report(context, messages.needSpaceAfter, 'needSpaceAfter', { + node: attrNode, + loc: equalToken.loc.start, + fix(fixer) { + return fixer.insertTextAfter(equalToken, ' '); + }, + }); + } + } + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..114db99197d4b658850d7dc788253d277211f908 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-filename-extension.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..8f7165b15a4c9b414001bbe13148926e4061deae --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-filename-extension.d.ts","sourceRoot":"","sources":["jsx-filename-extension.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js new file mode 100644 index 0000000000000000000000000000000000000000..4a2cfeede66b68255ca48bc38c0eb2f1537a785a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js @@ -0,0 +1,120 @@ +/** + * @fileoverview Restrict file extensions that may contain JSX + * @author Joe Lencioni + */ + +'use strict'; + +const path = require('path'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DEFAULTS = { + allow: 'always', + extensions: ['.jsx'], + ignoreFilesWithoutCode: false, +}; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noJSXWithExtension: 'JSX not allowed in files with extension \'{{ext}}\'', + extensionOnlyForJSX: 'Only files containing JSX may use the extension \'{{ext}}\'', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow file extensions that may contain JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-filename-extension'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allow: { + enum: ['always', 'as-needed'], + }, + extensions: { + type: 'array', + items: { + type: 'string', + }, + }, + ignoreFilesWithoutCode: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const filename = context.getFilename(); + + let jsxNode; + + if (filename === '') { + // No need to traverse any nodes. + return {}; + } + + const allow = (context.options[0] && context.options[0].allow) || DEFAULTS.allow; + const allowedExtensions = (context.options[0] && context.options[0].extensions) || DEFAULTS.extensions; + const ignoreFilesWithoutCode = (context.options[0] && context.options[0].ignoreFilesWithoutCode) + || DEFAULTS.ignoreFilesWithoutCode; + const isAllowedExtension = allowedExtensions.some((extension) => filename.slice(-extension.length) === extension); + + function handleJSX(node) { + if (!jsxNode) { + jsxNode = node; + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXElement: handleJSX, + JSXFragment: handleJSX, + + 'Program:exit'(node) { + if (jsxNode) { + if (!isAllowedExtension) { + report(context, messages.noJSXWithExtension, 'noJSXWithExtension', { + node: jsxNode, + data: { + ext: path.extname(filename), + }, + }); + } + return; + } + + if (isAllowedExtension && allow === 'as-needed') { + if (ignoreFilesWithoutCode && node.body.length === 0) { + return; + } + report(context, messages.extensionOnlyForJSX, 'extensionOnlyForJSX', { + node, + data: { + ext: path.extname(filename), + }, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..29b05dcbf94e3226891de0886e71e53a51e41f65 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-first-prop-new-line.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..4a6ce8f287819eeeb5c560738706d9c8f44d6520 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-first-prop-new-line.d.ts","sourceRoot":"","sources":["jsx-first-prop-new-line.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js new file mode 100644 index 0000000000000000000000000000000000000000..3014d24f1ecca079995c3ef24482994efb798526 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js @@ -0,0 +1,83 @@ +/** + * @fileoverview Ensure proper position of the first property in JSX + * @author Joachim Seminck + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const propsUtil = require('../util/props'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + propOnNewLine: 'Property should be placed on a new line', + propOnSameLine: 'Property should be placed on the same line as the component declaration', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce proper position of the first property in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-first-prop-new-line'), + }, + fixable: 'code', + + messages, + + schema: [{ + enum: ['always', 'never', 'multiline', 'multiline-multiprop', 'multiprop'], + }], + }, + + create(context) { + const configuration = context.options[0] || 'multiline-multiprop'; + + function isMultilineJSX(jsxNode) { + return jsxNode.loc.start.line < jsxNode.loc.end.line; + } + + return { + JSXOpeningElement(node) { + if ( + (configuration === 'multiline' && isMultilineJSX(node)) + || (configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1) + || (configuration === 'multiprop' && node.attributes.length > 1) + || (configuration === 'always') + ) { + node.attributes.some((decl) => { + if (decl.loc.start.line === node.loc.start.line) { + report(context, messages.propOnNewLine, 'propOnNewLine', { + node: decl, + fix(fixer) { + const nodeTypeArguments = propsUtil.getTypeArguments(node); + return fixer.replaceTextRange([(nodeTypeArguments || node.name).range[1], decl.range[0]], '\n'); + }, + }); + } + return true; + }); + } else if ( + (configuration === 'never' && node.attributes.length > 0) + || (configuration === 'multiprop' && isMultilineJSX(node) && node.attributes.length <= 1) + ) { + const firstNode = node.attributes[0]; + if (node.loc.start.line < firstNode.loc.start.line) { + report(context, messages.propOnSameLine, 'propOnSameLine', { + node: firstNode, + fix(fixer) { + return fixer.replaceTextRange([node.name.range[1], firstNode.range[0]], ' '); + }, + }); + } + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..81ccb27cfd4c94757d9e981e054e83a46b58fc95 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-fragments.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..e36be319e34d66b85124fec04016060201450311 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-fragments.d.ts","sourceRoot":"","sources":["jsx-fragments.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js new file mode 100644 index 0000000000000000000000000000000000000000..b4a545846e456bdf8960d0d189cc29a2ca194d84 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js @@ -0,0 +1,213 @@ +/** + * @fileoverview Enforce shorthand or standard form for React fragments. + * @author Alex Zherdev + */ + +'use strict'; + +const elementType = require('jsx-ast-utils/elementType'); +const pragmaUtil = require('../util/pragma'); +const variableUtil = require('../util/variable'); +const testReactVersion = require('../util/version').testReactVersion; +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const getText = require('../util/eslint').getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function replaceNode(source, node, text) { + return `${source.slice(0, node.range[0])}${text}${source.slice(node.range[1])}`; +} + +const messages = { + fragmentsNotSupported: 'Fragments are only supported starting from React v16.2. Please disable the `react/jsx-fragments` rule in `eslint` settings or upgrade your version of React.', + preferPragma: 'Prefer {{react}}.{{fragment}} over fragment shorthand', + preferFragment: 'Prefer fragment shorthand over {{react}}.{{fragment}}', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce shorthand or standard form for React fragments', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-fragments'), + }, + fixable: 'code', + + messages, + + schema: [{ + enum: ['syntax', 'element'], + }], + }, + + create(context) { + const configuration = context.options[0] || 'syntax'; + const reactPragma = pragmaUtil.getFromContext(context); + const fragmentPragma = pragmaUtil.getFragmentFromContext(context); + const openFragShort = '<>'; + const closeFragShort = ''; + const openFragLong = `<${reactPragma}.${fragmentPragma}>`; + const closeFragLong = ``; + + function reportOnReactVersion(node) { + if (!testReactVersion(context, '>= 16.2.0')) { + report(context, messages.fragmentsNotSupported, 'fragmentsNotSupported', { + node, + }); + return true; + } + + return false; + } + + function getFixerToLong(jsxFragment) { + 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 = getText(context); + source = replaceNode(source, jsxFragment.closingFragment, closeFragLong); + source = replaceNode(source, jsxFragment.openingFragment, openFragLong); + 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) { + return function fix(fixer) { + let source = getText(context); + let lengthDiff; + if (jsxElement.closingElement) { + source = replaceNode(source, jsxElement.closingElement, closeFragShort); + source = replaceNode(source, jsxElement.openingElement, openFragShort); + lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length + + getText(context, jsxElement.closingElement).length - closeFragShort.length; + } else { + source = replaceNode(source, jsxElement.openingElement, `${openFragShort}${closeFragShort}`); + lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length + - closeFragShort.length; + } + + const range = jsxElement.range; + return fixer.replaceTextRange(range, source.slice(range[0], range[1] - lengthDiff)); + }; + } + + function refersToReactFragment(node, name) { + const variableInit = variableUtil.findVariableByName(context, node, name); + if (!variableInit) { + return false; + } + + // const { Fragment } = React; + if (variableInit.type === 'Identifier' && variableInit.name === reactPragma) { + return true; + } + + // const Fragment = React.Fragment; + if ( + variableInit.type === 'MemberExpression' + && variableInit.object.type === 'Identifier' + && variableInit.object.name === reactPragma + && variableInit.property.type === 'Identifier' + && variableInit.property.name === fragmentPragma + ) { + return true; + } + + // const { Fragment } = require('react'); + if ( + variableInit.callee + && variableInit.callee.name === 'require' + && variableInit.arguments + && variableInit.arguments[0] + && variableInit.arguments[0].value === 'react' + ) { + return true; + } + + return false; + } + + const jsxElements = []; + const fragmentNames = new Set([`${reactPragma}.${fragmentPragma}`]); + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXElement(node) { + jsxElements.push(node); + }, + + JSXFragment(node) { + if (reportOnReactVersion(node)) { + return; + } + + if (configuration === 'element') { + report(context, messages.preferPragma, 'preferPragma', { + node, + data: { + react: reactPragma, + fragment: fragmentPragma, + }, + fix: getFixerToLong(node), + }); + } + }, + + ImportDeclaration(node) { + if (node.source && node.source.value === 'react') { + node.specifiers.forEach((spec) => { + if ( + 'imported' in spec + && spec.imported + && 'name' in spec.imported + && spec.imported.name === fragmentPragma + ) { + if (spec.local) { + fragmentNames.add(spec.local.name); + } + } + }); + } + }, + + 'Program:exit'() { + jsxElements.forEach((node) => { + const openingEl = node.openingElement; + const elName = elementType(openingEl); + + if (fragmentNames.has(elName) || refersToReactFragment(node, elName)) { + if (reportOnReactVersion(node)) { + return; + } + + const attrs = openingEl.attributes; + if (configuration === 'syntax' && !(attrs && attrs.length > 0)) { + report(context, messages.preferFragment, 'preferFragment', { + node, + data: { + react: reactPragma, + fragment: fragmentPragma, + }, + fix: getFixerToShort(node), + }); + } + } + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..db7f5fd8db579be0449c44f2a5f9a7ee09ae7893 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-handler-names.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..5d81078c230b076c14bc0c588e3eb8972c28aa26 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-handler-names.d.ts","sourceRoot":"","sources":["jsx-handler-names.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js new file mode 100644 index 0000000000000000000000000000000000000000..f89f24413e8bd0ae35167c52381b907ef687a2bc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js @@ -0,0 +1,209 @@ +/** + * @fileoverview Enforce event handler naming conventions in JSX + * @author Jake Marsh + */ + +'use strict'; + +const minimatch = require('minimatch'); +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + badHandlerName: 'Handler function for {{propKey}} prop key must be a camelCase name beginning with \'{{handlerPrefix}}\' only', + badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}}\'', +}; + +function isPrefixDisabled(prefix) { + return prefix === false; +} + +function isInlineHandler(node) { + return node.value.expression.type === 'ArrowFunctionExpression'; +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce event handler naming conventions in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-handler-names'), + }, + + messages, + + schema: [{ + anyOf: [ + { + type: 'object', + properties: { + eventHandlerPrefix: { type: 'string' }, + eventHandlerPropPrefix: { type: 'string' }, + checkLocalVariables: { type: 'boolean' }, + checkInlineFunction: { type: 'boolean' }, + ignoreComponentNames: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + eventHandlerPrefix: { type: 'string' }, + eventHandlerPropPrefix: { + type: 'boolean', + enum: [false], + }, + checkLocalVariables: { type: 'boolean' }, + checkInlineFunction: { type: 'boolean' }, + ignoreComponentNames: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + eventHandlerPrefix: { + type: 'boolean', + enum: [false], + }, + eventHandlerPropPrefix: { type: 'string' }, + checkLocalVariables: { type: 'boolean' }, + checkInlineFunction: { type: 'boolean' }, + ignoreComponentNames: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + checkLocalVariables: { type: 'boolean' }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + checkInlineFunction: { type: 'boolean' }, + }, + additionalProperties: false, + }, + { + type: 'object', + properties: { + ignoreComponentNames: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + }, + }, + ], + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + + const eventHandlerPrefix = isPrefixDisabled(configuration.eventHandlerPrefix) + ? null + : configuration.eventHandlerPrefix || 'handle'; + const eventHandlerPropPrefix = isPrefixDisabled(configuration.eventHandlerPropPrefix) + ? null + : configuration.eventHandlerPropPrefix || 'on'; + + const EVENT_HANDLER_REGEX = !eventHandlerPrefix + ? null + : new RegExp(`^((props\\.${eventHandlerPropPrefix || ''})|((.*\\.)?${eventHandlerPrefix}))[0-9]*[A-Z].*$`); + const PROP_EVENT_HANDLER_REGEX = !eventHandlerPropPrefix + ? null + : new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`); + + const checkLocal = !!configuration.checkLocalVariables; + + const checkInlineFunction = !!configuration.checkInlineFunction; + + const ignoreComponentNames = configuration.ignoreComponentNames || []; + + return { + JSXAttribute(node) { + const componentName = node.parent.name.name; + + const isComponentNameIgnored = ignoreComponentNames.some((ignoredComponentNamePattern) => minimatch( + componentName, + ignoredComponentNamePattern + )); + + if ( + !node.value + || !node.value.expression + || (!checkInlineFunction && isInlineHandler(node)) + || ( + !checkLocal + && (isInlineHandler(node) + ? !node.value.expression.body.callee || !node.value.expression.body.callee.object + : !node.value.expression.object + ) + ) + || isComponentNameIgnored + ) { + return; + } + + const propKey = typeof node.name === 'object' ? node.name.name : node.name; + const expression = node.value.expression; + const propValue = getText( + context, + checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression + ).replace(/\s*/g, '').replace(/^this\.|.*::/, ''); + + if (propKey === 'ref') { + return; + } + + const propIsEventHandler = PROP_EVENT_HANDLER_REGEX && PROP_EVENT_HANDLER_REGEX.test(propKey); + const propFnIsNamedCorrectly = EVENT_HANDLER_REGEX && EVENT_HANDLER_REGEX.test(propValue); + + if ( + propIsEventHandler + && propFnIsNamedCorrectly !== null + && !propFnIsNamedCorrectly + ) { + report(context, messages.badHandlerName, 'badHandlerName', { + node, + data: { + propKey, + handlerPrefix: eventHandlerPrefix, + }, + }); + } else if ( + propFnIsNamedCorrectly + && propIsEventHandler !== null + && !propIsEventHandler + ) { + report(context, messages.badPropKey, 'badPropKey', { + node, + data: { + propValue, + handlerPropPrefix: eventHandlerPropPrefix, + }, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ab31319008cee211fd38c96b80da581807762d6 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-indent-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..f5be91924366080698ea191740e132d2efdcd771 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-indent-props.d.ts","sourceRoot":"","sources":["jsx-indent-props.js"],"names":[],"mappings":"wBA+CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js new file mode 100644 index 0000000000000000000000000000000000000000..8504878731401879794bc89320f3c9421dc2d0a0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js @@ -0,0 +1,218 @@ +/** + * @fileoverview Validate props indentation in JSX + * @author Yannick Croissant + + * This rule has been ported and modified from eslint and nodeca. + * @author Vitaly Puzrin + * @author Gyandeep Singh + * @copyright 2015 Vitaly Puzrin. All rights reserved. + * @copyright 2015 Gyandeep Singh. All rights reserved. + Copyright (C) 2014 by Vitaly Puzrin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the 'Software'), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +'use strict'; + +const repeat = require('string.prototype.repeat'); + +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const reportC = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce props indentation in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-indent-props'), + }, + fixable: 'code', + + messages, + + schema: [{ + anyOf: [{ + enum: ['tab', 'first'], + }, { + type: 'integer', + }, { + type: 'object', + properties: { + indentMode: { + anyOf: [{ + enum: ['tab', 'first'], + }, { + type: 'integer', + }], + }, + ignoreTernaryOperator: { + type: 'boolean', + }, + }, + }], + }], + }, + + create(context) { + const extraColumnStart = 0; + let indentType = 'space'; + /** @type {number|'first'} */ + let indentSize = 4; + const line = { + isUsingOperator: false, + currentOperator: false, + }; + let ignoreTernaryOperator = false; + + if (context.options.length) { + const isConfigObject = typeof context.options[0] === 'object'; + const indentMode = isConfigObject + ? context.options[0].indentMode + : context.options[0]; + + if (indentMode === 'first') { + indentSize = 'first'; + indentType = 'space'; + } else if (indentMode === 'tab') { + indentSize = 1; + indentType = 'tab'; + } else if (typeof indentMode === 'number') { + indentSize = indentMode; + indentType = 'space'; + } + + if (isConfigObject && context.options[0].ignoreTernaryOperator) { + ignoreTernaryOperator = true; + } + } + + /** + * Reports a given indent violation and properly pluralizes the message + * @param {ASTNode} node Node violating the indent rule + * @param {number} needed Expected indentation character count + * @param {number} gotten Indentation character count in the actual node/code + */ + function report(node, needed, gotten) { + const msgContext = { + needed, + type: indentType, + characters: needed === 1 ? 'character' : 'characters', + gotten, + }; + + reportC(context, messages.wrongIndent, 'wrongIndent', { + node, + data: msgContext, + fix(fixer) { + return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], + repeat(indentType === 'space' ? ' ' : '\t', needed) + ); + }, + }); + } + + /** + * Get node indent + * @param {ASTNode} node Node to examine + * @return {number} Indent + */ + function getNodeIndent(node) { + let src = getText(context, node, node.loc.start.column + extraColumnStart); + const lines = src.split('\n'); + src = lines[0]; + + let regExp; + if (indentType === 'space') { + regExp = /^[ ]+/; + } else { + regExp = /^[\t]+/; + } + + const indent = regExp.exec(src); + const useOperator = /^([ ]|[\t])*[:]/.test(src) || /^([ ]|[\t])*[?]/.test(src); + const useBracket = /[<]/.test(src); + + line.currentOperator = false; + if (useOperator) { + line.isUsingOperator = true; + line.currentOperator = true; + } else if (useBracket) { + line.isUsingOperator = false; + } + + return indent ? indent[0].length : 0; + } + + /** + * Check indent for nodes list + * @param {ASTNode[]} nodes list of node objects + * @param {number} indent needed indent + */ + function checkNodesIndent(nodes, indent) { + let nestedIndent = indent; + nodes.forEach((node) => { + const nodeIndent = getNodeIndent(node); + if ( + line.isUsingOperator + && !line.currentOperator + && indentSize !== 'first' + && !ignoreTernaryOperator + ) { + nestedIndent += indentSize; + line.isUsingOperator = false; + } + if ( + node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression' + && nodeIndent !== nestedIndent && astUtil.isNodeFirstInLine(context, node) + ) { + report(node, nestedIndent, nodeIndent); + } + }); + } + + return { + JSXOpeningElement(node) { + if (!node.attributes.length) { + return; + } + let propIndent; + if (indentSize === 'first') { + const firstPropNode = node.attributes[0]; + propIndent = firstPropNode.loc.start.column; + } else { + const elementIndent = getNodeIndent(node); + propIndent = elementIndent + indentSize; + } + checkNodesIndent(node.attributes, propIndent); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..519d8c88be44ac74fe2bca1045ff6274f67e048c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-indent.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..604fd03567125fbb49858f01005e460b9f0d9428 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-indent.d.ts","sourceRoot":"","sources":["jsx-indent.js"],"names":[],"mappings":"wBAoDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js new file mode 100644 index 0000000000000000000000000000000000000000..f27eca029d031f810a0fbd38fc8c254d2c09cb5d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js @@ -0,0 +1,447 @@ +/** + * @fileoverview Validate JSX indentation + * @author Yannick Croissant + + * This rule has been ported and modified from eslint and nodeca. + * @author Vitaly Puzrin + * @author Gyandeep Singh + * @copyright 2015 Vitaly Puzrin. All rights reserved. + * @copyright 2015 Gyandeep Singh. All rights reserved. + Copyright (C) 2014 by Vitaly Puzrin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the 'Software'), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +'use strict'; + +const matchAll = require('string.prototype.matchall'); +const repeat = require('string.prototype.repeat'); + +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const reportC = require('../util/report'); +const jsxUtil = require('../util/jsx'); +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce JSX indentation', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-indent'), + }, + fixable: 'whitespace', + + messages, + + schema: [{ + anyOf: [{ + enum: ['tab'], + }, { + type: 'integer', + }], + }, { + type: 'object', + properties: { + checkAttributes: { + type: 'boolean', + }, + indentLogicalExpressions: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const extraColumnStart = 0; + let indentType = 'space'; + let indentSize = 4; + + if (context.options.length) { + if (context.options[0] === 'tab') { + indentSize = 1; + indentType = 'tab'; + } else if (typeof context.options[0] === 'number') { + indentSize = context.options[0]; + indentType = 'space'; + } + } + + const indentChar = indentType === 'space' ? ' ' : '\t'; + const options = context.options[1] || {}; + const checkAttributes = options.checkAttributes || false; + const indentLogicalExpressions = options.indentLogicalExpressions || false; + + /** + * Responsible for fixing the indentation issue fix + * @param {ASTNode} node Node violating the indent rule + * @param {number} needed Expected indentation character count + * @returns {Function} function to be executed by the fixer + * @private + */ + function getFixerFunction(node, needed) { + const indent = repeat(indentChar, needed); + + if (node.type === 'JSXText' || node.type === 'Literal') { + return function fix(fixer) { + const regExp = /\n[\t ]*(\S)/g; + const fixedText = node.raw.replace(regExp, (match, p1) => `\n${indent}${p1}`); + return fixer.replaceText(node, fixedText); + }; + } + + if (node.type === 'ReturnStatement') { + const raw = getText(context, node); + const lines = raw.split('\n'); + if (lines.length > 1) { + return function fix(fixer) { + const lastLineStart = raw.lastIndexOf('\n'); + const lastLine = raw.slice(lastLineStart).replace(/^\n[\t ]*(\S)/, (match, p1) => `\n${indent}${p1}`); + return fixer.replaceTextRange( + [node.range[0] + lastLineStart, node.range[1]], + lastLine + ); + }; + } + } + + return function fix(fixer) { + return fixer.replaceTextRange( + [node.range[0] - node.loc.start.column, node.range[0]], + indent + ); + }; + } + + /** + * Reports a given indent violation and properly pluralizes the message + * @param {ASTNode} node Node violating the indent rule + * @param {number} needed Expected indentation character count + * @param {number} gotten Indentation character count in the actual node/code + * @param {Object} [loc] Error line and column location + */ + function report(node, needed, gotten, loc) { + const msgContext = { + needed, + type: indentType, + characters: needed === 1 ? 'character' : 'characters', + gotten, + }; + + reportC(context, messages.wrongIndent, 'wrongIndent', Object.assign({ + node, + data: msgContext, + fix: getFixerFunction(node, needed), + }, loc && { loc })); + } + + /** + * Get node indent + * @param {ASTNode} node Node to examine + * @param {boolean} [byLastLine] get indent of node's last line + * @param {boolean} [excludeCommas] skip comma on start of line + * @return {number} Indent + */ + function getNodeIndent(node, byLastLine, excludeCommas) { + let src = getText(context, node, node.loc.start.column + extraColumnStart); + const lines = src.split('\n'); + if (byLastLine) { + src = lines[lines.length - 1]; + } else { + src = lines[0]; + } + + const skip = excludeCommas ? ',' : ''; + + let regExp; + if (indentType === 'space') { + regExp = new RegExp(`^[ ${skip}]+`); + } else { + regExp = new RegExp(`^[\t${skip}]+`); + } + + const indent = regExp.exec(src); + return indent ? indent[0].length : 0; + } + + /** + * Check if the node is the right member of a logical expression + * @param {ASTNode} node The node to check + * @return {boolean} true if its the case, false if not + */ + function isRightInLogicalExp(node) { + return ( + node.parent + && node.parent.parent + && node.parent.parent.type === 'LogicalExpression' + && node.parent.parent.right === node.parent + && !indentLogicalExpressions + ); + } + + /** + * Check if the node is the alternate member of a conditional expression + * @param {ASTNode} node The node to check + * @return {boolean} true if its the case, false if not + */ + function isAlternateInConditionalExp(node) { + return ( + node.parent + && node.parent.parent + && node.parent.parent.type === 'ConditionalExpression' + && node.parent.parent.alternate === node.parent + && getSourceCode(context).getTokenBefore(node).value !== '(' + ); + } + + /** + * Check if the node is within a DoExpression block but not the first expression (which need to be indented) + * @param {ASTNode} node The node to check + * @return {boolean} true if its the case, false if not + */ + function isSecondOrSubsequentExpWithinDoExp(node) { + /* + It returns true when node.parent.parent.parent.parent matches: + + DoExpression({ + ..., + body: BlockStatement({ + ..., + body: [ + ..., // 1-n times + ExpressionStatement({ + ..., + expression: JSXElement({ + ..., + openingElement: JSXOpeningElement() // the node + }) + }), + ... // 0-n times + ] + }) + }) + + except: + + DoExpression({ + ..., + body: BlockStatement({ + ..., + body: [ + ExpressionStatement({ + ..., + expression: JSXElement({ + ..., + openingElement: JSXOpeningElement() // the node + }) + }), + ... // 0-n times + ] + }) + }) + */ + const isInExpStmt = ( + node.parent + && node.parent.parent + && node.parent.parent.type === 'ExpressionStatement' + ); + if (!isInExpStmt) { + return false; + } + + const expStmt = node.parent.parent; + const isInBlockStmtWithinDoExp = ( + expStmt.parent + && expStmt.parent.type === 'BlockStatement' + && expStmt.parent.parent + && expStmt.parent.parent.type === 'DoExpression' + ); + if (!isInBlockStmtWithinDoExp) { + return false; + } + + const blockStmt = expStmt.parent; + const blockStmtFirstExp = blockStmt.body[0]; + return !(blockStmtFirstExp === expStmt); + } + + /** + * Check indent for nodes list + * @param {ASTNode} node The node to check + * @param {number} indent needed indent + * @param {boolean} [excludeCommas] skip comma on start of line + */ + function checkNodesIndent(node, indent, excludeCommas) { + const nodeIndent = getNodeIndent(node, false, excludeCommas); + const isCorrectRightInLogicalExp = isRightInLogicalExp(node) && (nodeIndent - indent) === indentSize; + const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0; + if ( + nodeIndent !== indent + && astUtil.isNodeFirstInLine(context, node) + && !isCorrectRightInLogicalExp + && !isCorrectAlternateInCondExp + ) { + report(node, indent, nodeIndent); + } + } + + /** + * Check indent for Literal Node or JSXText Node + * @param {ASTNode} node The node to check + * @param {number} indent needed indent + */ + function checkLiteralNodeIndent(node, indent) { + const value = node.value; + const regExp = indentType === 'space' ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g; + const nodeIndentsPerLine = Array.from( + matchAll(String(value), regExp), + (match) => (match[1] ? match[1].length : 0) + ); + const hasFirstInLineNode = nodeIndentsPerLine.length > 0; + if ( + hasFirstInLineNode + && !nodeIndentsPerLine.every((actualIndent) => actualIndent === indent) + ) { + nodeIndentsPerLine.forEach((nodeIndent) => { + report(node, indent, nodeIndent); + }); + } + } + + function handleOpeningElement(node) { + const sourceCode = getSourceCode(context); + let prevToken = sourceCode.getTokenBefore(node); + if (!prevToken) { + return; + } + // Use the parent in a list or an array + if (prevToken.type === 'JSXText' || ((prevToken.type === 'Punctuator') && prevToken.value === ',')) { + prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]); + prevToken = prevToken.type === 'Literal' || prevToken.type === 'JSXText' ? prevToken.parent : prevToken; + // Use the first non-punctuator token in a conditional expression + } else if (prevToken.type === 'Punctuator' && prevToken.value === ':') { + do { + prevToken = sourceCode.getTokenBefore(prevToken); + } while (prevToken.type === 'Punctuator' && prevToken.value !== '/'); + prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]); + while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') { + prevToken = prevToken.parent; + } + } + prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken; + const parentElementIndent = getNodeIndent(prevToken); + const indent = ( + prevToken.loc.start.line === node.loc.start.line + || isRightInLogicalExp(node) + || isAlternateInConditionalExp(node) + || isSecondOrSubsequentExpWithinDoExp(node) + ) ? 0 : indentSize; + checkNodesIndent(node, parentElementIndent + indent); + } + + function handleClosingElement(node) { + if (!node.parent) { + return; + } + const peerElementIndent = getNodeIndent(node.parent.openingElement || node.parent.openingFragment); + checkNodesIndent(node, peerElementIndent); + } + + function handleAttribute(node) { + if (!checkAttributes || (!node.value || node.value.type !== 'JSXExpressionContainer')) { + return; + } + const nameIndent = getNodeIndent(node.name); + 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); + } + + function handleLiteral(node) { + if (!node.parent) { + return; + } + if (node.parent.type !== 'JSXElement' && node.parent.type !== 'JSXFragment') { + return; + } + const parentNodeIndent = getNodeIndent(node.parent); + checkLiteralNodeIndent(node, parentNodeIndent + indentSize); + } + + return { + JSXOpeningElement: handleOpeningElement, + JSXOpeningFragment: handleOpeningElement, + JSXClosingElement: handleClosingElement, + JSXClosingFragment: handleClosingElement, + JSXAttribute: handleAttribute, + JSXExpressionContainer(node) { + if (!node.parent) { + return; + } + const parentNodeIndent = getNodeIndent(node.parent); + checkNodesIndent(node, parentNodeIndent + indentSize); + }, + Literal: handleLiteral, + JSXText: handleLiteral, + + ReturnStatement(node) { + if ( + !node.parent + || !jsxUtil.isJSX(node.argument) + ) { + return; + } + + let fn = node.parent; + while (fn && fn.type !== 'FunctionDeclaration' && fn.type !== 'FunctionExpression') { + fn = fn.parent; + } + if ( + !fn + || !jsxUtil.isReturningJSX(context, node, true) + ) { + return; + } + + const openingIndent = getNodeIndent(node); + const closingIndent = getNodeIndent(node, true); + + if (openingIndent !== closingIndent) { + report(node, openingIndent, closingIndent); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f3f9ef5d3d7db01b97d841c1cd26e8bfe32836b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-key.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..dc7cab4e6ebc730a950863a96feed588031b7f8a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-key.d.ts","sourceRoot":"","sources":["jsx-key.js"],"names":[],"mappings":"wBAmCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-key.js b/node_modules/eslint-plugin-react/lib/rules/jsx-key.js new file mode 100644 index 0000000000000000000000000000000000000000..825d21f4bb22a70d87b32bfcdc531fd6cb88975c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-key.js @@ -0,0 +1,303 @@ +/** + * @fileoverview Report missing `key` props in iterators/collection literals. + * @author Ben Mosher + */ + +'use strict'; + +const hasProp = require('jsx-ast-utils/hasProp'); +const propName = require('jsx-ast-utils/propName'); +const values = require('object.values'); +const docsUrl = require('../util/docsUrl'); +const pragmaUtil = require('../util/pragma'); +const report = require('../util/report'); +const astUtil = require('../util/ast'); +const getText = require('../util/eslint').getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const defaultOptions = { + checkFragmentShorthand: false, + checkKeyMustBeforeSpread: false, + warnOnDuplicates: false, +}; + +const messages = { + missingIterKey: 'Missing "key" prop for element in iterator', + missingIterKeyUsePrag: 'Missing "key" prop for element in iterator. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead', + missingArrayKey: 'Missing "key" prop for element in array', + missingArrayKeyUsePrag: 'Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead', + keyBeforeSpread: '`key` prop must be placed before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`', + nonUniqueKeys: '`key` prop must be unique', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow missing `key` props in iterators/collection literals', + category: 'Possible Errors', + recommended: true, + url: docsUrl('jsx-key'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + checkFragmentShorthand: { + type: 'boolean', + default: defaultOptions.checkFragmentShorthand, + }, + checkKeyMustBeforeSpread: { + type: 'boolean', + default: defaultOptions.checkKeyMustBeforeSpread, + }, + warnOnDuplicates: { + type: 'boolean', + default: defaultOptions.warnOnDuplicates, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const options = Object.assign({}, defaultOptions, context.options[0]); + const checkFragmentShorthand = options.checkFragmentShorthand; + const checkKeyMustBeforeSpread = options.checkKeyMustBeforeSpread; + const warnOnDuplicates = options.warnOnDuplicates; + 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') { + 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, + data: { + reactPrag: reactPragma, + fragPrag: fragmentPragma, + }, + }); + } + } + + function getReturnStatements(node) { + const returnStatements = arguments[1] || []; + if (node.type === 'IfStatement') { + if (node.consequent) { + getReturnStatements(node.consequent, returnStatements); + } + if (node.alternate) { + getReturnStatements(node.alternate, returnStatements); + } + } else if (node.type === 'ReturnStatement') { + returnStatements.push(node); + } else if (Array.isArray(node.body)) { + node.body.forEach((item) => { + if (item.type === 'IfStatement') { + getReturnStatements(item, returnStatements); + } + + if (item.type === 'ReturnStatement') { + returnStatements.push(item); + } + }); + } + + return returnStatements; + } + + /** + * 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 + * @param {ASTNode} node + */ + function checkFunctionsBlockStatement(node) { + if (astUtil.isFunctionLikeExpression(node)) { + if (node.body.type === 'BlockStatement') { + getReturnStatements(node.body) + .filter((returnStatement) => returnStatement && returnStatement.argument) + .forEach((returnStatement) => { + checkIteratorElement(returnStatement.argument); + }); + } + } + } + + /** + * Checks if the given node is an arrow function that has an JSX Element or JSX Fragment in its body, + * and the JSX is missing a key prop + * @param {ASTNode} node + */ + function checkArrowFunctionWithJSX(node) { + const isArrFn = node && node.type === 'ArrowFunctionExpression'; + const shouldCheckNode = (n) => n && (n.type === 'JSXElement' || n.type === 'JSXFragment'); + if (isArrFn && shouldCheckNode(node.body)) { + checkIteratorElement(node.body); + } + if (node.body.type === 'ConditionalExpression') { + if (shouldCheckNode(node.body.consequent)) { + checkIteratorElement(node.body.consequent); + } + if (shouldCheckNode(node.body.alternate)) { + checkIteratorElement(node.body.alternate); + } + } else if (node.body.type === 'LogicalExpression' && shouldCheckNode(node.body.right)) { + checkIteratorElement(node.body.right); + } + } + + const childrenToArraySelector = `:matches( + CallExpression + [callee.object.object.name=${reactPragma}] + [callee.object.property.name=Children] + [callee.property.name=toArray], + CallExpression + [callee.object.name=Children] + [callee.property.name=toArray] + )`.replace(/\s/g, ''); + let isWithinChildrenToArray = false; + + const seen = new WeakSet(); + + return { + [childrenToArraySelector]() { + isWithinChildrenToArray = true; + }, + + [`${childrenToArraySelector}:exit`]() { + isWithinChildrenToArray = false; + }, + + 'ArrayExpression, JSXElement > JSXElement'(node) { + if (isWithinChildrenToArray) { + return; + } + + const jsx = (node.type === 'ArrayExpression' ? node.elements : node.parent.children).filter((x) => x && x.type === 'JSXElement'); + if (jsx.length === 0) { + return; + } + + const map = {}; + jsx.forEach((element) => { + const attrs = element.openingElement.attributes; + const keys = attrs.filter((x) => x.name && x.name.name === 'key'); + + if (keys.length === 0) { + if (node.type === 'ArrayExpression') { + report(context, messages.missingArrayKey, 'missingArrayKey', { + node: element, + }); + } + } else { + keys.forEach((attr) => { + const value = getText(context, attr.value); + if (!map[value]) { map[value] = []; } + map[value].push(attr); + + if (checkKeyMustBeforeSpread && isKeyAfterSpread(attrs)) { + report(context, messages.keyBeforeSpread, 'keyBeforeSpread', { + node: node.type === 'ArrayExpression' ? node : node.parent, + }); + } + }); + } + }); + + if (warnOnDuplicates) { + values(map).filter((v) => v.length > 1).forEach((v) => { + v.forEach((n) => { + if (!seen.has(n)) { + seen.add(n); + report(context, messages.nonUniqueKeys, 'nonUniqueKeys', { + node: n, + }); + } + }); + }); + } + }, + + JSXFragment(node) { + if (!checkFragmentShorthand || isWithinChildrenToArray) { + return; + } + + if (node.parent.type === 'ArrayExpression') { + report(context, messages.missingArrayKeyUsePrag, 'missingArrayKeyUsePrag', { + node, + data: { + reactPrag: reactPragma, + fragPrag: fragmentPragma, + }, + }); + } + }, + + // Array.prototype.map + // eslint-disable-next-line no-multi-str + 'CallExpression[callee.type="MemberExpression"][callee.property.name="map"],\ + CallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"],\ + OptionalCallExpression[callee.type="MemberExpression"][callee.property.name="map"],\ + OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"]'(node) { + if (isWithinChildrenToArray) { + return; + } + + const fn = node.arguments.length > 0 && node.arguments[0]; + if (!fn || !astUtil.isFunctionLikeExpression(fn)) { + return; + } + + checkArrowFunctionWithJSX(fn); + + checkFunctionsBlockStatement(fn); + }, + + // Array.from + 'CallExpression[callee.type="MemberExpression"][callee.property.name="from"]'(node) { + if (isWithinChildrenToArray) { + return; + } + + const fn = node.arguments.length > 1 && node.arguments[1]; + if (!astUtil.isFunctionLikeExpression(fn)) { + return; + } + + checkArrowFunctionWithJSX(fn); + + checkFunctionsBlockStatement(fn); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0823c275b6780614f67be4783a3dbb0bbf0cc06a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-max-depth.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..bfec794cdf21fdf8ce1f399d11a46aa674a3bd3c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-max-depth.d.ts","sourceRoot":"","sources":["jsx-max-depth.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js new file mode 100644 index 0000000000000000000000000000000000000000..12625fa704626dacfc590b9ce51fc037d1798688 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js @@ -0,0 +1,162 @@ +/** + * @fileoverview Validate JSX maximum depth + * @author Chris + */ + +'use strict'; + +const has = require('hasown'); +const includes = require('array-includes'); +const variableUtil = require('../util/variable'); +const jsxUtil = require('../util/jsx'); +const docsUrl = require('../util/docsUrl'); +const reportC = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +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: { + description: 'Enforce JSX maximum depth', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-max-depth'), + }, + + messages, + + schema: [ + { + type: 'object', + properties: { + max: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + create(context) { + const DEFAULT_DEPTH = 2; + + const option = context.options[0] || {}; + const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH; + + function isExpression(node) { + return node.type === 'JSXExpressionContainer'; + } + + function hasJSX(node) { + return jsxUtil.isJSX(node) || (isExpression(node) && jsxUtil.isJSX(node.expression)); + } + + function isLeaf(node) { + const children = node.children; + + return !children || children.length === 0 || !children.some(hasJSX); + } + + function getDepth(node) { + let count = 0; + + while (jsxUtil.isJSX(node.parent) || isExpression(node.parent)) { + node = node.parent; + if (jsxUtil.isJSX(node)) { + count += 1; + } + } + + return count; + } + + function report(node, depth) { + reportC(context, messages.wrongDepth, 'wrongDepth', { + node, + data: { + found: depth, + needed: maxDepth, + }, + }); + } + + function findJSXElementOrFragment(startNode, name, previousReferences) { + function find(refs, prevRefs) { + for (let i = refs.length - 1; i >= 0; i--) { + if (typeof refs[i].writeExpr !== 'undefined') { + const writeExpr = refs[i].writeExpr; + + return (jsxUtil.isJSX(writeExpr) + && writeExpr) + || ((writeExpr && writeExpr.type === 'Identifier') + && findJSXElementOrFragment(startNode, writeExpr.name, prevRefs)); + } + } + + return null; + } + + const variable = variableUtil.getVariableFromContext(context, startNode, name); + if (variable && variable.references) { + const containDuplicates = previousReferences.some((ref) => includes(variable.references, ref)); + + // Prevent getting stuck in circular references + if (containDuplicates) { + return false; + } + + return find(variable.references, previousReferences.concat(variable.references)); + } + + return false; + } + + function checkDescendant(baseDepth, children) { + baseDepth += 1; + (children || []).filter((node) => hasJSX(node)).forEach((node) => { + if (baseDepth > maxDepth) { + report(node, baseDepth); + } else if (!isLeaf(node)) { + checkDescendant(baseDepth, node.children); + } + }); + } + + function handleJSX(node) { + if (!isLeaf(node)) { + return; + } + + const depth = getDepth(node); + if (depth > maxDepth) { + report(node, depth); + } + } + + return { + JSXElement: handleJSX, + JSXFragment: handleJSX, + + JSXExpressionContainer(node) { + if (node.expression.type !== 'Identifier') { + return; + } + + const element = findJSXElementOrFragment(node, node.expression.name, []); + + if (element) { + const baseDepth = getDepth(node); + checkDescendant(baseDepth, element.children); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..95f6832208493114286b888a9c81f98ff0c2a5bc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-max-props-per-line.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..33e539a6842aefd7608790144cc4cf053c515abd --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-max-props-per-line.d.ts","sourceRoot":"","sources":["jsx-max-props-per-line.js"],"names":[],"mappings":"wBA0BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js new file mode 100644 index 0000000000000000000000000000000000000000..eebf7f0f33df7449183eaf1d73a1cb14174cc717 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js @@ -0,0 +1,155 @@ +/** + * @fileoverview Limit maximum of props on a single line in JSX + * @author Yannick Croissant + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const report = require('../util/report'); + +function getPropName(context, propNode) { + if (propNode.type === 'JSXSpreadAttribute') { + return getText(context, propNode.argument); + } + return propNode.name.name; +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + newLine: 'Prop `{{prop}}` must be placed on a new line', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce maximum of props on a single line in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-max-props-per-line'), + }, + fixable: 'code', + + messages, + + schema: [{ + anyOf: [{ + type: 'object', + properties: { + maximum: { + type: 'object', + properties: { + single: { + type: 'integer', + minimum: 1, + }, + multi: { + type: 'integer', + minimum: 1, + }, + }, + }, + }, + additionalProperties: false, + }, { + type: 'object', + properties: { + maximum: { + type: 'number', + minimum: 1, + }, + when: { + type: 'string', + enum: ['always', 'multiline'], + }, + }, + additionalProperties: false, + }], + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const maximum = configuration.maximum || 1; + + const maxConfig = typeof maximum === 'number' + ? { + single: configuration.when === 'multiline' ? Infinity : maximum, + multi: maximum, + } + : { + single: maximum.single || Infinity, + multi: maximum.multi || Infinity, + }; + + function generateFixFunction(line, max) { + const output = []; + const front = line[0].range[0]; + const back = line[line.length - 1].range[1]; + + for (let i = 0; i < line.length; i += max) { + const nodes = line.slice(i, i + max); + output.push(nodes.reduce((prev, curr) => { + if (prev === '') { + return getText(context, curr); + } + return `${prev} ${getText(context, curr)}`; + }, '')); + } + + const code = output.join('\n'); + + return function fix(fixer) { + return fixer.replaceTextRange([front, back], code); + }; + } + + return { + JSXOpeningElement(node) { + if (!node.attributes.length) { + return; + } + + const isSingleLineTag = node.loc.start.line === node.loc.end.line; + + if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) { + return; + } + + const firstProp = node.attributes[0]; + const linePartitionedProps = [[firstProp]]; + + node.attributes.reduce((last, decl) => { + if (last.loc.end.line === decl.loc.start.line) { + linePartitionedProps[linePartitionedProps.length - 1].push(decl); + } else { + linePartitionedProps.push([decl]); + } + return decl; + }); + + linePartitionedProps.forEach((propsInLine) => { + const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line + ? maxConfig.single + : maxConfig.multi; + + if (propsInLine.length > maxPropsCountPerLine) { + const name = getPropName(context, propsInLine[maxPropsCountPerLine]); + report(context, messages.newLine, 'newLine', { + node: propsInLine[maxPropsCountPerLine], + data: { + prop: name, + }, + fix: generateFixFunction(propsInLine, maxPropsCountPerLine), + }); + } + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a22a097ad2ae3e2ab5d4ff7fe1c5b928e1f698c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-newline.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..cc0251dd9bb0ca76aa9172de1653ad1b7a4456ae --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-newline.d.ts","sourceRoot":"","sources":["jsx-newline.js"],"names":[],"mappings":"wBA0BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js new file mode 100644 index 0000000000000000000000000000000000000000..7fd395c72fe81f9566731b1f4465972f961ca5e4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js @@ -0,0 +1,166 @@ +/** + * @fileoverview Require or prevent a new line after jsx elements and expressions. + * @author Johnny Zabala + * @author Joseph Stiles + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + require: 'JSX element should start in a new line', + prevent: 'JSX element should not start in a new line', + allowMultilines: 'Multiline JSX elements should start in a new line', +}; + +function isMultilined(node) { + return node && node.loc.start.line !== node.loc.end.line; +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Require or prevent a new line after jsx elements and expressions.', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-newline'), + }, + fixable: 'code', + + messages, + schema: [ + { + type: 'object', + properties: { + prevent: { + default: false, + type: 'boolean', + }, + allowMultilines: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + if: { + properties: { + allowMultilines: { + const: true, + }, + }, + }, + then: { + properties: { + prevent: { + const: true, + }, + }, + required: [ + 'prevent', + ], + }, + }, + ], + }, + create(context) { + const jsxElementParents = new Set(); + + function isBlockCommentInCurlyBraces(element) { + const elementRawValue = getText(context, element); + return /^\s*{\/\*/.test(elementRawValue); + } + + function isNonBlockComment(element) { + return !isBlockCommentInCurlyBraces(element) && (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer'); + } + + return { + 'Program:exit'() { + jsxElementParents.forEach((parent) => { + parent.children.forEach((element, index, elements) => { + if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') { + const configuration = context.options[0] || {}; + const prevent = configuration.prevent || false; + const allowMultilines = configuration.allowMultilines || false; + + const firstAdjacentSibling = elements[index + 1]; + const secondAdjacentSibling = elements[index + 2]; + + const hasSibling = firstAdjacentSibling + && secondAdjacentSibling + && (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText'); + + if (!hasSibling) return; + + // Check adjacent sibling has the proper amount of newlines + const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value); + + if (isBlockCommentInCurlyBraces(element)) return; + if ( + allowMultilines + && ( + isMultilined(element) + || isMultilined(elements.slice(index + 2).find(isNonBlockComment)) + ) + ) { + if (!isWithoutNewLine) return; + + const regex = /(\n)(?!.*\1)/g; + const replacement = '\n\n'; + const messageId = 'allowMultilines'; + + report(context, messages[messageId], messageId, { + node: secondAdjacentSibling, + fix(fixer) { + return fixer.replaceText( + firstAdjacentSibling, + getText(context, firstAdjacentSibling).replace(regex, replacement) + ); + }, + }); + + return; + } + + if (isWithoutNewLine === prevent) return; + + const messageId = prevent + ? 'prevent' + : 'require'; + + const regex = prevent + ? /(\n\n)(?!.*\1)/g + : /(\n)(?!.*\1)/g; + + const replacement = prevent + ? '\n' + : '\n\n'; + + report(context, messages[messageId], messageId, { + node: secondAdjacentSibling, + fix(fixer) { + return fixer.replaceText( + firstAdjacentSibling, + // double or remove the last newline + getText(context, firstAdjacentSibling).replace(regex, replacement) + ); + }, + }); + } + }); + }); + }, + ':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => { + jsxElementParents.add(node.parent); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..29f9fa349c78ade1aa710c6b49fab943079b85ab --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-bind.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..d0ebfb11a43ff6b0c891ad2654b68befa547c44c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-bind.d.ts","sourceRoot":"","sources":["jsx-no-bind.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js new file mode 100644 index 0000000000000000000000000000000000000000..2e8aa687dc1def4061e973ae751e2d6ec49d44ac --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js @@ -0,0 +1,209 @@ +/** + * @fileoverview Prevents usage of Function.prototype.bind and arrow functions + * in React component props. + * @author Daniel Lo Nigro + * @author Jacky Ho + */ + +'use strict'; + +const propName = require('jsx-ast-utils/propName'); +const docsUrl = require('../util/docsUrl'); +const astUtil = require('../util/ast'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); +const getAncestors = require('../util/eslint').getAncestors; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +const messages = { + bindCall: 'JSX props should not use .bind()', + arrowFunc: 'JSX props should not use arrow functions', + bindExpression: 'JSX props should not use ::', + func: 'JSX props should not use functions', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow `.bind()` or arrow functions in JSX props', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-no-bind'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowArrowFunctions: { + default: false, + type: 'boolean', + }, + allowBind: { + default: false, + type: 'boolean', + }, + allowFunctions: { + default: false, + type: 'boolean', + }, + ignoreRefs: { + default: false, + type: 'boolean', + }, + ignoreDOMComponents: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + + // Keep track of all the variable names pointing to a bind call, + // bind expression or an arrow function in different block statements + const blockVariableNameSets = {}; + + /** + * @param {string | number} blockStart + */ + function setBlockVariableNameSet(blockStart) { + blockVariableNameSets[blockStart] = { + arrowFunc: new Set(), + bindCall: new Set(), + bindExpression: new Set(), + func: new Set(), + }; + } + + function getNodeViolationType(node) { + if ( + !configuration.allowBind + && astUtil.isCallExpression(node) + && node.callee.type === 'MemberExpression' + && node.callee.property.type === 'Identifier' + && node.callee.property.name === 'bind' + ) { + return 'bindCall'; + } + if (node.type === 'ConditionalExpression') { + return getNodeViolationType(node.test) + || getNodeViolationType(node.consequent) + || getNodeViolationType(node.alternate); + } + if (!configuration.allowArrowFunctions && node.type === 'ArrowFunctionExpression') { + return 'arrowFunc'; + } + if ( + !configuration.allowFunctions + && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') + ) { + return 'func'; + } + if (!configuration.allowBind && node.type === 'BindExpression') { + return 'bindExpression'; + } + + return null; + } + + /** + * @param {string | number} violationType + * @param {unknown} variableName + * @param {string | number} blockStart + */ + function addVariableNameToSet(violationType, variableName, blockStart) { + blockVariableNameSets[blockStart][violationType].add(variableName); + } + + function getBlockStatementAncestors(node) { + return getAncestors(context, node).filter( + (ancestor) => ancestor.type === 'BlockStatement' + ).reverse(); + } + + function reportVariableViolation(node, name, blockStart) { + const blockSets = blockVariableNameSets[blockStart]; + const violationTypes = Object.keys(blockSets); + + return violationTypes.find((type) => { + if (blockSets[type].has(name)) { + report(context, messages[type], type, { + node, + }); + return true; + } + + return false; + }); + } + + function findVariableViolation(node, name) { + getBlockStatementAncestors(node).find( + (block) => reportVariableViolation(node, name, block.range[0]) + ); + } + + return { + BlockStatement(node) { + setBlockVariableNameSet(node.range[0]); + }, + + FunctionDeclaration(node) { + const blockAncestors = getBlockStatementAncestors(node); + const variableViolationType = getNodeViolationType(node); + + if (blockAncestors.length > 0 && variableViolationType) { + addVariableNameToSet(variableViolationType, node.id.name, blockAncestors[0].range[0]); + } + }, + + VariableDeclarator(node) { + if (!node.init) { + return; + } + const blockAncestors = getBlockStatementAncestors(node); + const variableViolationType = getNodeViolationType(node.init); + + if ( + blockAncestors.length > 0 + && variableViolationType + && 'kind' in node.parent + && node.parent.kind === 'const' // only support const right now + ) { + addVariableNameToSet(variableViolationType, 'name' in node.id ? node.id.name : undefined, blockAncestors[0].range[0]); + } + }, + + JSXAttribute(node) { + const isRef = configuration.ignoreRefs && propName(node) === 'ref'; + if (isRef || !node.value || !node.value.expression) { + return; + } + const isDOMComponent = jsxUtil.isDOMComponent(node.parent); + if (configuration.ignoreDOMComponents && isDOMComponent) { + return; + } + const valueNode = node.value.expression; + const valueNodeType = valueNode.type; + const nodeViolationType = getNodeViolationType(valueNode); + + if (valueNodeType === 'Identifier') { + findVariableViolation(node, valueNode.name); + } else if (nodeViolationType) { + report(context, messages[nodeViolationType], nodeViolationType, { + node, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f98c89fd03a168cd04c858d941d478f772dbc540 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-comment-textnodes.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..decc84e458b06b52951d7ccb8571644f9ee3ec76 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-comment-textnodes.d.ts","sourceRoot":"","sources":["jsx-no-comment-textnodes.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js new file mode 100644 index 0000000000000000000000000000000000000000..de4d288c37ced64c3399bcd4b95e1178734b4fa2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js @@ -0,0 +1,71 @@ +/** + * @fileoverview Comments inside children section of tag should be placed inside braces. + * @author Ben Vinegar + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + putCommentInBraces: 'Comments inside children section of tag should be placed inside braces', +}; + +/** + * @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 = getText(context, node); + if (/^\s*\/(\/|\*)/m.test(rawValue)) { + // inside component, e.g.
literal
+ if ( + node.parent.type !== 'JSXAttribute' + && node.parent.type !== 'JSXExpressionContainer' + && node.parent.type.indexOf('JSX') !== -1 + ) { + report(context, messages.putCommentInBraces, 'putCommentInBraces', { + node, + }); + } + } +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow comments from being inserted as text nodes', + category: 'Possible Errors', + recommended: true, + url: docsUrl('jsx-no-comment-textnodes'), + }, + + messages, + + schema: [], + }, + + create(context) { + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + Literal(node) { + checkText(context, node); + }, + JSXText(node) { + checkText(context, node); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..02b0aca6347765aae94e1761c0edc69cbf67157a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-constructed-context-values.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..aecb457de37df5822fdb4ff3bbe171973787d54d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-constructed-context-values.d.ts","sourceRoot":"","sources":["jsx-no-constructed-context-values.js"],"names":[],"mappings":"wBA2KW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js new file mode 100644 index 0000000000000000000000000000000000000000..19af33125321849d25702827e718ead4924037d9 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js @@ -0,0 +1,272 @@ +/** + * @fileoverview Prevents jsx context provider values from taking values that + * will cause needless rerenders. + * @author Dylan Oshima + */ + +'use strict'; + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const getScope = require('../util/eslint').getScope; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +// Recursively checks if an element is a construction. +// A construction is a variable that changes identity every render. +function isConstruction(node, callScope) { + switch (node.type) { + case 'Literal': + if (node.regex != null) { + return { type: 'regular expression', node }; + } + return null; + case 'Identifier': { + const variableScoping = callScope.set.get(node.name); + + if (variableScoping == null || variableScoping.defs == null) { + // If it's not in scope, we don't care. + return null; // Handled + } + + // Gets the last variable identity + const variableDefs = variableScoping.defs; + const def = variableDefs[variableDefs.length - 1]; + if (def != null + && def.type !== 'Variable' + && def.type !== 'FunctionName' + ) { + // Parameter or an unusual pattern. Bail out. + return null; // Unhandled + } + + if (def.node.type === 'FunctionDeclaration') { + return { type: 'function declaration', node: def.node, usage: node }; + } + + const init = def.node.init; + if (init == null) { + return null; + } + + const initConstruction = isConstruction(init, callScope); + if (initConstruction == null) { + return null; + } + + return { + type: initConstruction.type, + node: initConstruction.node, + usage: node, + }; + } + case 'ObjectExpression': + // Any object initialized inline will create a new identity + return { type: 'object', node }; + case 'ArrayExpression': + return { type: 'array', node }; + case 'ArrowFunctionExpression': + case 'FunctionExpression': + // Functions that are initialized inline will have a new identity + return { type: 'function expression', node }; + case 'ClassExpression': + return { type: 'class expression', node }; + case 'NewExpression': + // `const a = new SomeClass();` is a construction + return { type: 'new expression', node }; + case 'ConditionalExpression': + return (isConstruction(node.consequent, callScope) + || isConstruction(node.alternate, callScope) + ); + case 'LogicalExpression': + return (isConstruction(node.left, callScope) + || isConstruction(node.right, callScope) + ); + case 'MemberExpression': { + const objConstruction = isConstruction(node.object, callScope); + if (objConstruction == null) { + return null; + } + return { + type: objConstruction.type, + node: objConstruction.node, + usage: node.object, + }; + } + case 'JSXFragment': + return { type: 'JSX fragment', node }; + case 'JSXElement': + return { type: 'JSX element', node }; + case 'AssignmentExpression': { + const construct = isConstruction(node.right, callScope); + if (construct != null) { + return { + type: 'assignment expression', + node: construct.node, + usage: node, + }; + } + return null; + } + case 'TypeCastExpression': + case 'TSAsExpression': + return isConstruction(node.expression, callScope); + default: + return null; + } +} + +function isReactContext(context, node) { + let scope = getScope(context, node); + let variableScoping = null; + const contextName = node.name; + + while (scope && !variableScoping) { // Walk up the scope chain to find the variable + variableScoping = scope.set.get(contextName); + scope = scope.upper; + } + + if (!variableScoping) { // Context was not found in scope + return false; + } + + // Get the variable's definition + const def = variableScoping.defs[0]; + + if (!def || def.node.type !== 'VariableDeclarator') { + return false; + } + + const init = def.node.init; // Variable initializer + + const isCreateContext = init + && init.type === 'CallExpression' + && ( + ( + init.callee.type === 'Identifier' + && init.callee.name === 'createContext' + ) || ( + init.callee.type === 'MemberExpression' + && init.callee.object.name === 'React' + && init.callee.property.name === 'createContext' + ) + ); + + return isCreateContext; +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + withIdentifierMsg: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useMemo hook.", + withIdentifierMsgFunc: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.", + defaultMsg: '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 useMemo hook.', + 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: { + description: 'Disallows JSX context provider values from taking values that will cause needless rerenders', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-no-constructed-context-values'), + }, + messages, + schema: false, + }, + + // eslint-disable-next-line arrow-body-style + create: Components.detect((context, components, utils) => { + return { + JSXOpeningElement(node) { + const openingElementName = node.name; + + if (openingElementName.type === 'JSXMemberExpression') { + const isJSXContext = openingElementName.property.name === 'Provider'; + if (!isJSXContext) { + // Member is not Provider + return; + } + } else if (openingElementName.type === 'JSXIdentifier') { + const isJSXContext = isReactContext(context, openingElementName); + if (!isJSXContext) { + // Member is not context + return; + } + } else { + return; + } + + // Contexts can take in more than just a value prop + // so we need to iterate through all of them + const jsxValueAttribute = node.attributes.find( + (attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === 'value' + ); + + if (jsxValueAttribute == null) { + // No value prop was passed + return; + } + + const valueNode = jsxValueAttribute.value; + if (!valueNode) { + // attribute is a boolean shorthand + return; + } + if (valueNode.type !== 'JSXExpressionContainer') { + // value could be a literal + return; + } + + const valueExpression = valueNode.expression; + const invocationScope = getScope(context, node); + + // Check if the value prop is a construction + const constructInfo = isConstruction(valueExpression, invocationScope); + if (constructInfo == null) { + return; + } + + if (!utils.getParentComponent(node)) { + return; + } + + // Report found error + const constructType = constructInfo.type; + const constructNode = constructInfo.node; + const constructUsage = constructInfo.usage; + const data = { + type: constructType, nodeLine: constructNode.loc.start.line, + }; + let messageId = 'defaultMsg'; + + // Variable passed to value prop + if (constructUsage != null) { + messageId = 'withIdentifierMsg'; + data.usageLine = constructUsage.loc.start.line; + data.variableName = constructUsage.name; + } + + // Type of expression + if ( + constructType === 'function expression' + || constructType === 'function declaration' + ) { + messageId += 'Func'; + } + + report(context, messages[messageId], messageId, { + node: constructNode, + data, + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3aed37a19e888bb8fe7e55726d15e92d212a6912 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-duplicate-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..c5f4015f94be48f07095d0b914cd4c586bf91b0c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-duplicate-props.d.ts","sourceRoot":"","sources":["jsx-no-duplicate-props.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js new file mode 100644 index 0000000000000000000000000000000000000000..77de5bde51f68fe953e50a1b9809f51e50bf4fa0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Enforce no duplicate props + * @author Markus Ånöstam + */ + +'use strict'; + +const has = require('hasown'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noDuplicateProps: 'No duplicate props allowed', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow duplicate properties in JSX', + category: 'Possible Errors', + recommended: true, + url: docsUrl('jsx-no-duplicate-props'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + ignoreCase: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || false; + + return { + JSXOpeningElement(node) { + const props = {}; + + node.attributes.forEach((decl) => { + if (decl.type === 'JSXSpreadAttribute') { + return; + } + + let name = decl.name.name; + + if (typeof name !== 'string') { + return; + } + + if (ignoreCase) { + name = name.toLowerCase(); + } + + if (has(props, name)) { + report(context, messages.noDuplicateProps, 'noDuplicateProps', { + node: decl, + }); + } else { + props[name] = 1; + } + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4a81887b3a8c7cf6a48cc0720c287006bf9f2b3 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-leaked-render.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..8f1ca780adb5d94f084edb5289c57d10d3d17925 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-leaked-render.d.ts","sourceRoot":"","sources":["jsx-no-leaked-render.js"],"names":[],"mappings":"wBA+GW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js new file mode 100644 index 0000000000000000000000000000000000000000..1e271b2a686a6e74917980cdb5e6da8eb5070250 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js @@ -0,0 +1,204 @@ +/** + * @fileoverview Prevent problematic leaked values from being rendered + * @author Mario Beltrán + */ + +'use strict'; + +const find = require('es-iterator-helpers/Iterator.prototype.find'); +const from = require('es-iterator-helpers/Iterator.from'); + +const getText = require('../util/eslint').getText; +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; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const messages = { + noPotentialLeakedRender: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes', +}; + +const COERCE_STRATEGY = 'coerce'; +const TERNARY_STRATEGY = 'ternary'; +const DEFAULT_VALID_STRATEGIES = [TERNARY_STRATEGY, COERCE_STRATEGY]; +const COERCE_VALID_LEFT_SIDE_EXPRESSIONS = ['UnaryExpression', 'BinaryExpression', 'CallExpression']; +const TERNARY_INVALID_ALTERNATE_VALUES = [undefined, null, false]; + +function trimLeftNode(node) { + // Remove double unary expression (boolean coercion), so we avoid trimming valid negations + if (node.type === 'UnaryExpression' && node.argument.type === 'UnaryExpression') { + return trimLeftNode(node.argument.argument); + } + + return node; +} + +function getIsCoerceValidNestedLogicalExpression(node) { + if (node.type === 'LogicalExpression') { + return getIsCoerceValidNestedLogicalExpression(node.left) && getIsCoerceValidNestedLogicalExpression(node.right); + } + + return COERCE_VALID_LEFT_SIDE_EXPRESSIONS.some((validExpression) => validExpression === node.type); +} + +function extractExpressionBetweenLogicalAnds(node) { + if (node.type !== 'LogicalExpression') return [node]; + if (node.operator !== '&&') return [node]; + return [].concat( + extractExpressionBetweenLogicalAnds(node.left), + extractExpressionBetweenLogicalAnds(node.right) + ); +} + +function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) { + const rightSideText = getText(context, rightNode); + + if (fixStrategy === COERCE_STRATEGY) { + const expressions = extractExpressionBetweenLogicalAnds(leftNode); + const newText = expressions.map((node) => { + let nodeText = getText(context, node); + if (isParenthesized(context, node)) { + nodeText = `(${nodeText})`; + } + if (node.parent && node.parent.type === 'ConditionalExpression' && node.parent.consequent.value === false) { + return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!'}${nodeText}`; + } + return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`; + }).join(' && '); + + if (rightNode.parent && rightNode.parent.type === 'ConditionalExpression' && rightNode.parent.consequent.value === false) { + const consequentVal = rightNode.parent.consequent.raw || rightNode.parent.consequent.name; + const alternateVal = rightNode.parent.alternate.raw || rightNode.parent.alternate.name; + if (rightNode.parent.test && rightNode.parent.test.type === 'LogicalExpression') { + return fixer.replaceText(reportedNode, `${newText} ? ${consequentVal} : ${alternateVal}`); + } + return fixer.replaceText(reportedNode, `${newText} && ${alternateVal}`); + } + + if (rightNode.type === 'ConditionalExpression' || rightNode.type === 'LogicalExpression') { + return fixer.replaceText(reportedNode, `${newText} && (${rightSideText})`); + } + if (rightNode.type === 'JSXElement') { + const rightSideTextLines = rightSideText.split('\n'); + if (rightSideTextLines.length > 1) { + const rightSideTextLastLine = rightSideTextLines[rightSideTextLines.length - 1]; + const indentSpacesStart = ' '.repeat(rightSideTextLastLine.search(/\S/)); + const indentSpacesClose = ' '.repeat(rightSideTextLastLine.search(/\S/) - 2); + return fixer.replaceText(reportedNode, `${newText} && (\n${indentSpacesStart}${rightSideText}\n${indentSpacesClose})`); + } + } + if (rightNode.type === 'Literal') { + return null; + } + return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`); + } + + if (fixStrategy === TERNARY_STRATEGY) { + let leftSideText = getText(context, trimLeftNode(leftNode)); + if (isParenthesized(context, leftNode)) { + leftSideText = `(${leftSideText})`; + } + return fixer.replaceText(reportedNode, `${leftSideText} ? ${rightSideText} : null`); + } + + throw new TypeError('Invalid value for "validStrategies" option'); +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow problematic leaked values from being rendered', + category: 'Possible Errors', + recommended: false, + url: docsUrl('jsx-no-leaked-render'), + }, + + messages, + + fixable: 'code', + schema: [ + { + type: 'object', + properties: { + validStrategies: { + type: 'array', + items: { + enum: [ + TERNARY_STRATEGY, + COERCE_STRATEGY, + ], + }, + uniqueItems: true, + default: DEFAULT_VALID_STRATEGIES, + }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const config = context.options[0] || {}; + const validStrategies = new Set(config.validStrategies || DEFAULT_VALID_STRATEGIES); + const fixStrategy = find(from(validStrategies), () => true); + + return { + 'JSXExpressionContainer > LogicalExpression[operator="&&"]'(node) { + const leftSide = node.left; + + const isCoerceValidLeftSide = COERCE_VALID_LEFT_SIDE_EXPRESSIONS + .some((validExpression) => validExpression === leftSide.type); + if (validStrategies.has(COERCE_STRATEGY)) { + if (isCoerceValidLeftSide || getIsCoerceValidNestedLogicalExpression(leftSide)) { + return; + } + const leftSideVar = variableUtil.getVariableFromContext(context, node, 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 === '') { + return; + } + report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', { + node, + fix(fixer) { + return ruleFixer(context, fixStrategy, fixer, node, leftSide, node.right); + }, + }); + }, + + 'JSXExpressionContainer > ConditionalExpression'(node) { + if (validStrategies.has(TERNARY_STRATEGY)) { + return; + } + + const isValidTernaryAlternate = TERNARY_INVALID_ALTERNATE_VALUES.indexOf(node.alternate.value) === -1; + const isJSXElementAlternate = node.alternate.type === 'JSXElement'; + if (isValidTernaryAlternate || isJSXElementAlternate) { + return; + } + + report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', { + node, + fix(fixer) { + return ruleFixer(context, fixStrategy, fixer, node, node.test, node.consequent); + }, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..218459a8c468af6988bbce0023242552ddaaaf6e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts @@ -0,0 +1,13 @@ +declare const _exports: RuleModule; +export = _exports; +export type RuleModule = import("eslint").Rule.RuleModule; +export type Config = { + type: "element"; +} & import("../../types/rules/jsx-no-literals").ElementConfigProperties & import("../../types/rules/jsx-no-literals").ElementOverrides; +export type RawConfig = import("../../types/rules/jsx-no-literals").RawElementConfig & import("../../types/rules/jsx-no-literals").RawElementOverrides; +export type ResolvedConfig = import("../../types/rules/jsx-no-literals").OverrideConfig | import("../../types/rules/jsx-no-literals").Config; +export type OverrideConfig = import("../../types/rules/jsx-no-literals").OverrideConfigProperties & import("../../types/rules/jsx-no-literals").ElementConfigProperties; +export type ElementConfig = { + type: "element"; +} & import("../../types/rules/jsx-no-literals").ElementConfigProperties; +//# sourceMappingURL=jsx-no-literals.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..50339a02ca66adfa2731a61f1e5b9882b3d7eb2c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-literals.d.ts","sourceRoot":"","sources":["jsx-no-literals.js"],"names":[],"mappings":"wBAoJW,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js new file mode 100644 index 0000000000000000000000000000000000000000..45c9a54c48214c1fc25b0155b5215f3d611abe65 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js @@ -0,0 +1,524 @@ +/** + * @fileoverview Prevent using string literals in React component definition + * @author Caleb Morris + * @author David Buchan-Swanson + */ + +'use strict'; + +const iterFrom = require('es-iterator-helpers/Iterator.from'); +const map = require('es-iterator-helpers/Iterator.prototype.map'); +const some = require('es-iterator-helpers/Iterator.prototype.some'); +const flatMap = require('es-iterator-helpers/Iterator.prototype.flatMap'); +const fromEntries = require('object.fromentries'); +const entries = require('object.entries'); + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const getText = require('../util/eslint').getText; + +/** @typedef {import('eslint').Rule.RuleModule} RuleModule */ + +/** @typedef {import('../../types/rules/jsx-no-literals').Config} Config */ +/** @typedef {import('../../types/rules/jsx-no-literals').RawConfig} RawConfig */ +/** @typedef {import('../../types/rules/jsx-no-literals').ResolvedConfig} ResolvedConfig */ +/** @typedef {import('../../types/rules/jsx-no-literals').OverrideConfig} OverrideConfig */ +/** @typedef {import('../../types/rules/jsx-no-literals').ElementConfig} ElementConfig */ + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** + * @param {unknown} value + * @returns {string | unknown} + */ +function trimIfString(value) { + return typeof value === 'string' ? value.trim() : value; +} + +const reOverridableElement = /^[A-Z][\w.]*$/; +const reIsWhiteSpace = /^[\s]+$/; +const jsxElementTypes = new Set(['JSXElement', 'JSXFragment']); +const standardJSXNodeParentTypes = new Set(['JSXAttribute', 'JSXElement', 'JSXExpressionContainer', 'JSXFragment']); + +const messages = { + invalidPropValue: 'Invalid prop value: "{{text}}"', + invalidPropValueInElement: 'Invalid prop value: "{{text}}" in {{element}}', + noStringsInAttributes: 'Strings not allowed in attributes: "{{text}}"', + noStringsInAttributesInElement: 'Strings not allowed in attributes: "{{text}}" in {{element}}', + noStringsInJSX: 'Strings not allowed in JSX files: "{{text}}"', + noStringsInJSXInElement: 'Strings not allowed in JSX files: "{{text}}" in {{element}}', + literalNotInJSXExpression: 'Missing JSX expression container around literal string: "{{text}}"', + literalNotInJSXExpressionInElement: 'Missing JSX expression container around literal string: "{{text}}" in {{element}}', +}; + +/** @type {Exclude['properties']} */ +const commonPropertiesSchema = { + noStrings: { + type: 'boolean', + }, + allowedStrings: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, + ignoreProps: { + type: 'boolean', + }, + noAttributeStrings: { + type: 'boolean', + }, +}; + +// eslint-disable-next-line valid-jsdoc +/** + * Normalizes the element portion of the config + * @param {RawConfig} config + * @returns {ElementConfig} + */ +function normalizeElementConfig(config) { + return { + type: 'element', + noStrings: !!config.noStrings, + allowedStrings: config.allowedStrings + ? new Set(map(iterFrom(config.allowedStrings), trimIfString)) + : new Set(), + ignoreProps: !!config.ignoreProps, + noAttributeStrings: !!config.noAttributeStrings, + }; +} + +// eslint-disable-next-line valid-jsdoc +/** + * Normalizes the config and applies default values to all config options + * @param {RawConfig} config + * @returns {Config} + */ +function normalizeConfig(config) { + /** @type {Config} */ + const normalizedConfig = Object.assign(normalizeElementConfig(config), { + elementOverrides: {}, + }); + + if (config.elementOverrides) { + normalizedConfig.elementOverrides = fromEntries( + flatMap( + iterFrom(entries(config.elementOverrides)), + (entry) => { + const elementName = entry[0]; + const rawElementConfig = entry[1]; + + if (!reOverridableElement.test(elementName)) { + return []; + } + + return [[ + elementName, + Object.assign(normalizeElementConfig(rawElementConfig), { + type: 'override', + name: elementName, + allowElement: !!rawElementConfig.allowElement, + applyToNestedElements: typeof rawElementConfig.applyToNestedElements === 'undefined' || !!rawElementConfig.applyToNestedElements, + }), + ]]; + } + ) + ); + } + + return normalizedConfig; +} + +const elementOverrides = { + type: 'object', + patternProperties: { + [reOverridableElement.source]: { + type: 'object', + properties: Object.assign( + { applyToNestedElements: { type: 'boolean' } }, + commonPropertiesSchema + ), + + }, + }, +}; + +/** @type {RuleModule} */ +module.exports = { + meta: /** @type {RuleModule['meta']} */ ({ + docs: { + description: 'Disallow usage of string literals in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-no-literals'), + }, + + messages, + + schema: [{ + type: 'object', + properties: Object.assign( + { elementOverrides }, + commonPropertiesSchema + ), + additionalProperties: false, + }], + }), + + create(context) { + /** @type {RawConfig} */ + const rawConfig = (context.options.length && context.options[0]) || {}; + const config = normalizeConfig(rawConfig); + + const hasElementOverrides = Object.keys(config.elementOverrides).length > 0; + + /** @type {Map} */ + const renamedImportMap = new Map(); + + /** + * Determines if the given expression is a require statement. Supports + * nested MemberExpresions. ie `require('foo').nested.property` + * @param {ASTNode} node + * @returns {boolean} + */ + function isRequireStatement(node) { + if (node.type === 'CallExpression') { + if (node.callee.type === 'Identifier') { + return node.callee.name === 'require'; + } + } + if (node.type === 'MemberExpression') { + return isRequireStatement(node.object); + } + + return false; + } + + /** @typedef {{ name: string, compoundName?: string }} ElementNameFragment */ + + /** + * Gets the name of the given JSX element. Supports nested + * JSXMemeberExpressions. ie `` + * @param {ASTNode} node + * @returns {ElementNameFragment | undefined} + */ + function getJSXElementName(node) { + if (node.openingElement.name.type === 'JSXIdentifier') { + const name = node.openingElement.name.name; + return { + name: renamedImportMap.get(name) || name, + compoundName: undefined, + }; + } + + /** @type {string[]} */ + const nameFragments = []; + + if (node.openingElement.name.type === 'JSXMemberExpression') { + /** @type {ASTNode} */ + let current = node.openingElement.name; + while (current.type === 'JSXMemberExpression') { + if (current.property.type === 'JSXIdentifier') { + nameFragments.unshift(current.property.name); + } + + current = current.object; + } + + if (current.type === 'JSXIdentifier') { + nameFragments.unshift(current.name); + + const rootFragment = nameFragments[0]; + if (rootFragment) { + const rootFragmentRenamed = renamedImportMap.get(rootFragment); + if (rootFragmentRenamed) { + nameFragments[0] = rootFragmentRenamed; + } + } + + const nameFragment = nameFragments[nameFragments.length - 1]; + if (nameFragment) { + return { + name: nameFragment, + compoundName: nameFragments.join('.'), + }; + } + } + } + } + + /** + * Gets all JSXElement ancestor nodes for the given node + * @param {ASTNode} node + * @returns {ASTNode[]} + */ + function getJSXElementAncestors(node) { + /** @type {ASTNode[]} */ + const ancestors = []; + + let current = node; + while (current) { + if (current.type === 'JSXElement') { + ancestors.push(current); + } + + current = current.parent; + } + + return ancestors; + } + + /** + * @param {ASTNode} node + * @returns {ASTNode} + */ + function getParentIgnoringBinaryExpressions(node) { + let current = node; + while (current.parent.type === 'BinaryExpression') { + current = current.parent; + } + return current.parent; + } + + /** + * @param {ASTNode} node + * @returns {{ parent: ASTNode, grandParent: ASTNode }} + */ + function getParentAndGrandParent(node) { + const parent = getParentIgnoringBinaryExpressions(node); + return { + parent, + grandParent: parent.parent, + }; + } + + /** + * @param {ASTNode} node + * @returns {boolean} + */ + function hasJSXElementParentOrGrandParent(node) { + const ancestors = getParentAndGrandParent(node); + return some(iterFrom([ancestors.parent, ancestors.grandParent]), (parent) => jsxElementTypes.has(parent.type)); + } + + // eslint-disable-next-line valid-jsdoc + /** + * Determines whether a given node's value and its immediate parent are + * viable text nodes that can/should be reported on + * @param {ASTNode} node + * @param {ResolvedConfig} resolvedConfig + * @returns {boolean} + */ + function isViableTextNode(node, resolvedConfig) { + const textValues = iterFrom([trimIfString(node.raw), trimIfString(node.value)]); + if (some(textValues, (value) => resolvedConfig.allowedStrings.has(value))) { + return false; + } + + const parent = getParentIgnoringBinaryExpressions(node); + + let isStandardJSXNode = false; + if (typeof node.value === 'string' && !reIsWhiteSpace.test(node.value) && standardJSXNodeParentTypes.has(parent.type)) { + if (resolvedConfig.noAttributeStrings) { + isStandardJSXNode = parent.type === 'JSXAttribute' || parent.type === 'JSXElement'; + } else { + isStandardJSXNode = parent.type !== 'JSXAttribute'; + } + } + + if (resolvedConfig.noStrings) { + return isStandardJSXNode; + } + + return isStandardJSXNode && parent.type !== 'JSXExpressionContainer'; + } + + // eslint-disable-next-line valid-jsdoc + /** + * Gets an override config for a given node. For any given node, we also + * need to traverse the ancestor tree to determine if an ancestor's config + * will also apply to the current node. + * @param {ASTNode} node + * @returns {OverrideConfig | undefined} + */ + function getOverrideConfig(node) { + if (!hasElementOverrides) { + return; + } + + const allAncestorElements = getJSXElementAncestors(node); + if (!allAncestorElements.length) { + return; + } + + for (const ancestorElement of allAncestorElements) { + const isClosestJSXAncestor = ancestorElement === allAncestorElements[0]; + + const ancestor = getJSXElementName(ancestorElement); + if (ancestor) { + if (ancestor.name) { + const ancestorElements = config.elementOverrides[ancestor.name]; + const ancestorConfig = ancestor.compoundName + ? config.elementOverrides[ancestor.compoundName] || ancestorElements + : ancestorElements; + + if (ancestorConfig) { + if (isClosestJSXAncestor || ancestorConfig.applyToNestedElements) { + return ancestorConfig; + } + } + } + } + } + } + + // eslint-disable-next-line valid-jsdoc + /** + * @param {ResolvedConfig} resolvedConfig + * @returns {boolean} + */ + function shouldAllowElement(resolvedConfig) { + return resolvedConfig.type === 'override' && 'allowElement' in resolvedConfig && !!resolvedConfig.allowElement; + } + + // eslint-disable-next-line valid-jsdoc + /** + * @param {boolean} ancestorIsJSXElement + * @param {ResolvedConfig} resolvedConfig + * @returns {string} + */ + function defaultMessageId(ancestorIsJSXElement, resolvedConfig) { + if (resolvedConfig.noAttributeStrings && !ancestorIsJSXElement) { + return resolvedConfig.type === 'override' ? 'noStringsInAttributesInElement' : 'noStringsInAttributes'; + } + + if (resolvedConfig.noStrings) { + return resolvedConfig.type === 'override' ? 'noStringsInJSXInElement' : 'noStringsInJSX'; + } + + return resolvedConfig.type === 'override' ? 'literalNotInJSXExpressionInElement' : 'literalNotInJSXExpression'; + } + + // eslint-disable-next-line valid-jsdoc + /** + * @param {ASTNode} node + * @param {string} messageId + * @param {ResolvedConfig} resolvedConfig + */ + function reportLiteralNode(node, messageId, resolvedConfig) { + report(context, messages[messageId], messageId, { + node, + data: { + text: getText(context, node).trim(), + element: resolvedConfig.type === 'override' && 'name' in resolvedConfig ? resolvedConfig.name : undefined, + }, + }); + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return Object.assign(hasElementOverrides ? { + // Get renamed import local names mapped to their imported name + ImportDeclaration(node) { + node.specifiers + .filter((s) => s.type === 'ImportSpecifier') + .forEach((specifier) => { + renamedImportMap.set( + (specifier.local || specifier.imported).name, + specifier.imported.name + ); + }); + }, + + // Get renamed destructured local names mapped to their imported name + VariableDeclaration(node) { + node.declarations + .filter((d) => ( + d.type === 'VariableDeclarator' + && isRequireStatement(d.init) + && d.id.type === 'ObjectPattern' + )) + .forEach((declaration) => { + declaration.id.properties + .filter((property) => ( + property.type === 'Property' + && property.key.type === 'Identifier' + && property.value.type === 'Identifier' + )) + .forEach((property) => { + renamedImportMap.set(property.value.name, property.key.name); + }); + }); + }, + } : false, { + Literal(node) { + const resolvedConfig = getOverrideConfig(node) || config; + + const hasJSXParentOrGrandParent = hasJSXElementParentOrGrandParent(node); + if (hasJSXParentOrGrandParent && shouldAllowElement(resolvedConfig)) { + return; + } + + if (isViableTextNode(node, resolvedConfig)) { + if (hasJSXParentOrGrandParent || !config.ignoreProps) { + reportLiteralNode(node, defaultMessageId(hasJSXParentOrGrandParent, resolvedConfig), resolvedConfig); + } + } + }, + + JSXAttribute(node) { + const isLiteralString = node.value && node.value.type === 'Literal' + && typeof node.value.value === 'string'; + const isStringLiteral = node.value && node.value.type === 'StringLiteral'; + + if (isLiteralString || isStringLiteral) { + const resolvedConfig = getOverrideConfig(node) || config; + + if ( + resolvedConfig.noStrings + && !resolvedConfig.ignoreProps + && !resolvedConfig.allowedStrings.has(node.value.value) + ) { + const messageId = resolvedConfig.type === 'override' ? 'invalidPropValueInElement' : 'invalidPropValue'; + reportLiteralNode(node, messageId, resolvedConfig); + } + } + }, + + JSXText(node) { + const resolvedConfig = getOverrideConfig(node) || config; + + if (shouldAllowElement(resolvedConfig)) { + return; + } + + if (isViableTextNode(node, resolvedConfig)) { + const hasJSXParendOrGrantParent = hasJSXElementParentOrGrandParent(node); + reportLiteralNode(node, defaultMessageId(hasJSXParendOrGrantParent, resolvedConfig), resolvedConfig); + } + }, + + TemplateLiteral(node) { + const ancestors = getParentAndGrandParent(node); + const isParentJSXExpressionCont = ancestors.parent.type === 'JSXExpressionContainer'; + const isParentJSXElement = ancestors.grandParent.type === 'JSXElement'; + + if (isParentJSXExpressionCont) { + const resolvedConfig = getOverrideConfig(node) || config; + + if ( + resolvedConfig.noStrings + && (isParentJSXElement || !resolvedConfig.ignoreProps) + ) { + reportLiteralNode(node, defaultMessageId(isParentJSXElement, resolvedConfig), resolvedConfig); + } + } + }, + }); + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0c3207072fdcb354315e80f9521cb4aab5c3d25 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-script-url.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..aefb54941528b9572596f6bcc6c3cbdc9a20764d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-script-url.d.ts","sourceRoot":"","sources":["jsx-no-script-url.js"],"names":[],"mappings":"wBA6CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js new file mode 100644 index 0000000000000000000000000000000000000000..b18ce2ccb320864d956d60aaf79f7119e8dad215 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js @@ -0,0 +1,143 @@ +/** + * @fileoverview Prevent usage of `javascript:` URLs + * @author Sergei Startsev + */ + +'use strict'; + +const includes = require('array-includes'); +const docsUrl = require('../util/docsUrl'); +const linkComponentsUtil = require('../util/linkComponents'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +// https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30 +/* eslint-disable-next-line max-len, no-control-regex */ +const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; + +function hasJavaScriptProtocol(attr) { + return attr.value && attr.value.type === 'Literal' + && isJavaScriptProtocol.test(attr.value.value); +} + +function shouldVerifyProp(node, config) { + const name = node.name && node.name.name; + const parentName = node.parent.name && node.parent.name.name; + + if (!name || !parentName || !config.has(parentName)) return false; + + const attributes = config.get(parentName); + return includes(attributes, name); +} + +function parseLegacyOption(config, option) { + option.forEach((opt) => { + config.set(opt.name, opt.props); + }); +} + +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: { + description: 'Disallow usage of `javascript:` URLs', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-no-script-url'), + }, + + messages, + + schema: { + anyOf: [ + { + type: 'array', + items: [ + { + type: 'array', + uniqueItems: true, + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + props: { + type: 'array', + items: { + type: 'string', + uniqueItems: true, + }, + }, + }, + required: ['name', 'props'], + additionalProperties: false, + }, + }, + { + type: 'object', + properties: { + includeFromSettings: { + type: 'boolean', + }, + }, + additionalItems: false, + }, + ], + additionalItems: false, + }, + { + type: 'array', + items: [ + { + type: 'object', + properties: { + includeFromSettings: { + type: 'boolean', + }, + }, + additionalItems: false, + }, + ], + additionalItems: false, + }, + ], + }, + }, + + create(context) { + const options = context.options; + const hasLegacyOption = Array.isArray(options[0]); + const legacyOptions = hasLegacyOption ? options[0] : []; + // eslint-disable-next-line no-nested-ternary + const objectOption = (hasLegacyOption && options.length > 1) + ? options[1] + : (options.length > 0 + ? options[0] + : { + includeFromSettings: false, + } + ); + const includeFromSettings = objectOption.includeFromSettings; + + const linkComponents = linkComponentsUtil.getLinkComponents(includeFromSettings ? context : {}); + parseLegacyOption(linkComponents, legacyOptions); + + return { + JSXAttribute(node) { + if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) { + report(context, messages.noScriptURL, 'noScriptURL', { + node, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c36948e3a350fc496ae37a5fd329639792aa4486 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-target-blank.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..6ca559cd92a6efba03012b239a6fd789725b21c0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-target-blank.d.ts","sourceRoot":"","sources":["jsx-no-target-blank.js"],"names":[],"mappings":"wBAgIW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js new file mode 100644 index 0000000000000000000000000000000000000000..9f5d239d6cadcd237ae9d0f2099457a23a7cab40 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js @@ -0,0 +1,285 @@ +/** + * @fileoverview Forbid target='_blank' attribute + * @author Kevin Miller + */ + +'use strict'; + +const includes = require('array-includes'); +const docsUrl = require('../util/docsUrl'); +const linkComponentsUtil = require('../util/linkComponents'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function findLastIndex(arr, condition) { + for (let i = arr.length - 1; i >= 0; i -= 1) { + if (condition(arr[i])) { + return i; + } + } + + return -1; +} + +function attributeValuePossiblyBlank(attribute) { + if (!attribute || !attribute.value) { + return false; + } + const value = attribute.value; + if (value.type === 'Literal') { + return typeof value.value === 'string' && value.value.toLowerCase() === '_blank'; + } + if (value.type === 'JSXExpressionContainer') { + const expr = value.expression; + if (expr.type === 'Literal') { + return typeof expr.value === 'string' && expr.value.toLowerCase() === '_blank'; + } + if (expr.type === 'ConditionalExpression') { + if (expr.alternate.type === 'Literal' && expr.alternate.value && expr.alternate.value.toLowerCase() === '_blank') { + return true; + } + if (expr.consequent.type === 'Literal' && expr.consequent.value && expr.consequent.value.toLowerCase() === '_blank') { + return true; + } + } + } + return false; +} + +function hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex) { + const linkIndex = findLastIndex(node.attributes, (attr) => attr.name && includes(linkAttributes, attr.name.name)); + const foundExternalLink = linkIndex !== -1 && ((attr) => attr.value && attr.value.type === 'Literal' && /^(?:\w+:|\/\/)/.test(attr.value.value))( + node.attributes[linkIndex]); + return foundExternalLink || (warnOnSpreadAttributes && linkIndex < spreadAttributeIndex); +} + +function hasDynamicLink(node, linkAttributes) { + const dynamicLinkIndex = findLastIndex(node.attributes, (attr) => attr.name + && includes(linkAttributes, attr.name.name) + && attr.value + && attr.value.type === 'JSXExpressionContainer'); + if (dynamicLinkIndex !== -1) { + return true; + } +} + +/** + * Get the string(s) from a value + * @param {ASTNode} value The AST node being checked. + * @param {ASTNode} targetValue The AST node being checked. + * @returns {string | string[] | null} The string value, or null if not a string. + */ +function getStringFromValue(value, targetValue) { + if (value) { + if (value.type === 'Literal') { + return value.value; + } + if (value.type === 'JSXExpressionContainer') { + if (value.expression.type === 'TemplateLiteral') { + return value.expression.quasis[0].value.cooked; + } + const expr = value.expression; + if (expr && expr.type === 'ConditionalExpression') { + const relValues = [expr.consequent.value, expr.alternate.value]; + if (targetValue.type === 'JSXExpressionContainer' && targetValue.expression && targetValue.expression.type === 'ConditionalExpression') { + const targetTestCond = targetValue.expression.test.name; + const relTestCond = value.expression.test.name; + if (targetTestCond === relTestCond) { + const targetBlankIndex = [targetValue.expression.consequent.value, targetValue.expression.alternate.value].indexOf('_blank'); + return relValues[targetBlankIndex]; + } + } + return relValues; + } + return expr.value; + } + } + return null; +} + +function hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttributeIndex) { + const relIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'rel')); + const targetIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'target')); + if (relIndex === -1 || (warnOnSpreadAttributes && relIndex < spreadAttributeIndex)) { + return false; + } + + const relAttribute = node.attributes[relIndex]; + const targetAttributeValue = node.attributes[targetIndex] && node.attributes[targetIndex].value; + const value = getStringFromValue(relAttribute.value, targetAttributeValue); + return [].concat(value).every((item) => { + const tags = typeof item === 'string' ? item.toLowerCase().split(' ') : false; + const noreferrer = tags && tags.indexOf('noreferrer') >= 0; + if (noreferrer) { + return true; + } + const noopener = tags && tags.indexOf('noopener') >= 0; + return allowReferrer && noopener; + }); +} + +const messages = { + noTargetBlankWithoutNoreferrer: 'Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations', + 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', + docs: { + description: 'Disallow `target="_blank"` attribute without `rel="noreferrer"`', + category: 'Best Practices', + recommended: true, + url: docsUrl('jsx-no-target-blank'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowReferrer: { + type: 'boolean', + }, + enforceDynamicLinks: { + enum: ['always', 'never'], + }, + warnOnSpreadAttributes: { + type: 'boolean', + }, + links: { + type: 'boolean', + default: true, + }, + forms: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = Object.assign( + { + allowReferrer: false, + warnOnSpreadAttributes: false, + links: true, + forms: false, + }, + context.options[0] + ); + const allowReferrer = configuration.allowReferrer; + const warnOnSpreadAttributes = configuration.warnOnSpreadAttributes; + const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'; + const linkComponents = linkComponentsUtil.getLinkComponents(context); + const formComponents = linkComponentsUtil.getFormComponents(context); + + return { + JSXOpeningElement(node) { + const targetIndex = findLastIndex(node.attributes, (attr) => attr.name && attr.name.name === 'target'); + const spreadAttributeIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXSpreadAttribute')); + + if (linkComponents.has(node.name.name)) { + if (!attributeValuePossiblyBlank(node.attributes[targetIndex])) { + const hasSpread = spreadAttributeIndex >= 0; + + if (warnOnSpreadAttributes && hasSpread) { + // continue to check below + } else if ((hasSpread && targetIndex < spreadAttributeIndex) || !hasSpread || !warnOnSpreadAttributes) { + return; + } + } + + const linkAttributes = linkComponents.get(node.name.name); + const hasDangerousLink = hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex) + || (enforceDynamicLinks === 'always' && hasDynamicLink(node, linkAttributes)); + if (hasDangerousLink && !hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttributeIndex)) { + const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer'; + const relValue = allowReferrer ? 'noopener' : 'noreferrer'; + report(context, messages[messageId], messageId, { + node, + fix(fixer) { + // eslint 5 uses `node.attributes`; eslint 6+ uses `node.parent.attributes` + const nodeWithAttrs = node.parent.attributes ? node.parent : node; + // eslint 5 does not provide a `name` property on JSXSpreadElements + const relAttribute = nodeWithAttrs.attributes.find((attr) => attr.name && attr.name.name === 'rel'); + + if (targetIndex < spreadAttributeIndex || (spreadAttributeIndex >= 0 && !relAttribute)) { + return null; + } + + if (!relAttribute) { + return fixer.insertTextAfter(nodeWithAttrs.attributes.slice(-1)[0], ` rel="${relValue}"`); + } + + if (!relAttribute.value) { + return fixer.insertTextAfter(relAttribute, `="${relValue}"`); + } + + if (relAttribute.value.type === 'Literal') { + const parts = relAttribute.value.value + .split('noreferrer') + .filter(Boolean); + return fixer.replaceText(relAttribute.value, `"${parts.concat('noreferrer').join(' ')}"`); + } + + if (relAttribute.value.type === 'JSXExpressionContainer') { + if (relAttribute.value.expression.type === 'Literal') { + if (typeof relAttribute.value.expression.value === 'string') { + const parts = relAttribute.value.expression.value + .split('noreferrer') + .filter(Boolean); + return fixer.replaceText(relAttribute.value.expression, `"${parts.concat('noreferrer').join(' ')}"`); + } + + // for undefined, boolean, number, symbol, bigint, and null + return fixer.replaceText(relAttribute.value, '"noreferrer"'); + } + } + + return null; + }, + }); + } + } + if (formComponents.has(node.name.name)) { + if (!attributeValuePossiblyBlank(node.attributes[targetIndex])) { + const hasSpread = spreadAttributeIndex >= 0; + + if (warnOnSpreadAttributes && hasSpread) { + // continue to check below + } else if ( + (hasSpread && targetIndex < spreadAttributeIndex) + || !hasSpread + || !warnOnSpreadAttributes + ) { + return; + } + } + + if (!configuration.forms || hasSecureRel(node)) { + return; + } + + const formAttributes = formComponents.get(node.name.name); + + if ( + hasExternalLink(node, formAttributes) + || (enforceDynamicLinks === 'always' && hasDynamicLink(node, formAttributes)) + ) { + const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer'; + report(context, messages[messageId], messageId, { + node, + }); + } + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0acd79628d116cef4967cf4bd62221b5c14887a1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-undef.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..bdea38d195b0ff0ddc8ff23f16ef6eb0c4b8c41b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-undef.d.ts","sourceRoot":"","sources":["jsx-no-undef.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js new file mode 100644 index 0000000000000000000000000000000000000000..a15de8e8540a5090a6edcb4208e62675db527f4d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js @@ -0,0 +1,117 @@ +/** + * @fileoverview Disallow undeclared variables in JSX + * @author Yannick Croissant + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const eslintUtil = require('../util/eslint'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + undefined: '\'{{identifier}}\' is not defined.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow undeclared variables in JSX', + category: 'Possible Errors', + recommended: true, + url: docsUrl('jsx-no-undef'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowGlobals: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const config = context.options[0] || {}; + const allowGlobals = config.allowGlobals || false; + + /** + * Compare an identifier with the variables declared in the scope + * @param {ASTNode} node - Identifier or JSXIdentifier node + * @returns {void} + */ + function checkIdentifierInJSX(node) { + let scope = eslintUtil.getScope(context, node); + const sourceCode = eslintUtil.getSourceCode(context); + const sourceType = sourceCode.ast.sourceType; + const scopeUpperBound = !allowGlobals && sourceType === 'module' ? 'module' : 'global'; + let variables = scope.variables; + let i; + let len; + + // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX) + if (node.name === 'this') { + return; + } + + while (scope.type !== scopeUpperBound && scope.type !== 'global') { + scope = scope.upper; + variables = scope.variables.concat(variables); + } + if (scope.childScopes.length) { + variables = scope.childScopes[0].variables.concat(variables); + // Temporary fix for babel-eslint + if (scope.childScopes[0].childScopes.length) { + variables = scope.childScopes[0].childScopes[0].variables.concat(variables); + } + } + + for (i = 0, len = variables.length; i < len; i++) { + if (variables[i].name === node.name) { + return; + } + } + + report(context, messages.undefined, 'undefined', { + node, + data: { + identifier: node.name, + }, + }); + } + + return { + JSXOpeningElement(node) { + switch (node.name.type) { + case 'JSXIdentifier': + if (jsxUtil.isDOMComponent(node)) { + return; + } + node = node.name; + break; + case 'JSXMemberExpression': + node = node.name; + do { + node = node.object; + } while (node && node.type !== 'JSXIdentifier'); + break; + case 'JSXNamespacedName': + return; + default: + break; + } + checkIdentifierInJSX(node); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..124f1d37d7d920faed82dc0d9f60a5bcbc9628c7 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-no-useless-fragment.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..0d11e13ceeb37cde3baa3bb76473f463622b8e3f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-no-useless-fragment.d.ts","sourceRoot":"","sources":["jsx-no-useless-fragment.js"],"names":[],"mappings":"wBAsFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js new file mode 100644 index 0000000000000000000000000000000000000000..9827560f0e1a9861d4e581618c2b761e9a414d37 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js @@ -0,0 +1,259 @@ +/** + * @fileoverview Disallow useless fragments + */ + +'use strict'; + +const arrayIncludes = require('array-includes'); + +const pragmaUtil = require('../util/pragma'); +const astUtil = require('../util/ast'); +const jsxUtil = require('../util/jsx'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const getText = require('../util/eslint').getText; + +function isJSXText(node) { + return !!node && (node.type === 'JSXText' || node.type === 'Literal'); +} + +/** + * @param {string} text + * @returns {boolean} + */ +function isOnlyWhitespace(text) { + return text.trim().length === 0; +} + +/** + * @param {ASTNode} node + * @returns {boolean} + */ +function isNonspaceJSXTextOrJSXCurly(node) { + return (isJSXText(node) && !isOnlyWhitespace(node.raw)) || node.type === 'JSXExpressionContainer'; +} + +/** + * Somehow fragment like this is useful: ee eeee eeee ...} /> + * @param {ASTNode} node + * @returns {boolean} + */ +function isFragmentWithOnlyTextAndIsNotChild(node) { + return node.children.length === 1 + && isJSXText(node.children[0]) + && !(node.parent.type === 'JSXElement' || node.parent.type === 'JSXFragment'); +} + +/** + * @param {string} text + * @returns {string} + */ +function trimLikeReact(text) { + const leadingSpaces = /^\s*/.exec(text)[0]; + const trailingSpaces = /\s*$/.exec(text)[0]; + + const start = arrayIncludes(leadingSpaces, '\n') ? leadingSpaces.length : 0; + const end = arrayIncludes(trailingSpaces, '\n') ? text.length - trailingSpaces.length : text.length; + + return text.slice(start, end); +} + +/** + * Test if node is like `_` + * @param {JSXElement} node + * @returns {boolean} + */ +function isKeyedElement(node) { + return node.type === 'JSXElement' + && node.openingElement.attributes + && node.openingElement.attributes.some(jsxUtil.isJSXAttributeKey); +} + +/** + * @param {ASTNode} node + * @returns {boolean} + */ +function containsCallExpression(node) { + return node + && node.type === 'JSXExpressionContainer' + && astUtil.isCallExpression(node.expression); +} + +const messages = { + NeedsMoreChildren: 'Fragments should contain more than one child - otherwise, there’s no need for a Fragment at all.', + ChildOfHtmlElement: 'Passing a fragment to an HTML element is useless.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + fixable: 'code', + docs: { + description: 'Disallow unnecessary fragments', + category: 'Possible Errors', + recommended: false, + url: docsUrl('jsx-no-useless-fragment'), + }, + messages, + schema: [{ + type: 'object', + properties: { + allowExpressions: { + type: 'boolean', + }, + }, + }], + }, + + create(context) { + const config = context.options[0] || {}; + const allowExpressions = config.allowExpressions || false; + + const reactPragma = pragmaUtil.getFromContext(context); + const fragmentPragma = pragmaUtil.getFragmentFromContext(context); + + /** + * Test whether a node is an padding spaces trimmed by react runtime. + * @param {ASTNode} node + * @returns {boolean} + */ + function isPaddingSpaces(node) { + return isJSXText(node) + && isOnlyWhitespace(node.raw) + && arrayIncludes(node.raw, '\n'); + } + + function isFragmentWithSingleExpression(node) { + const children = node && node.children.filter((child) => !isPaddingSpaces(child)); + return ( + children + && children.length === 1 + && children[0].type === 'JSXExpressionContainer' + ); + } + + /** + * Test whether a JSXElement has less than two children, excluding paddings spaces. + * @param {JSXElement|JSXFragment} node + * @returns {boolean} + */ + function hasLessThanTwoChildren(node) { + if (!node || !node.children) { + return true; + } + + /** @type {ASTNode[]} */ + const nonPaddingChildren = node.children.filter( + (child) => !isPaddingSpaces(child) + ); + + if (nonPaddingChildren.length < 2) { + return !containsCallExpression(nonPaddingChildren[0]); + } + } + + /** + * @param {JSXElement|JSXFragment} node + * @returns {boolean} + */ + function isChildOfHtmlElement(node) { + return node.parent.type === 'JSXElement' + && node.parent.openingElement.name.type === 'JSXIdentifier' + && /^[a-z]+$/.test(node.parent.openingElement.name.name); + } + + /** + * @param {JSXElement|JSXFragment} node + * @return {boolean} + */ + function isChildOfComponentElement(node) { + return node.parent.type === 'JSXElement' + && !isChildOfHtmlElement(node) + && !jsxUtil.isFragment(node.parent, reactPragma, fragmentPragma); + } + + /** + * @param {ASTNode} node + * @returns {boolean} + */ + function canFix(node) { + // Not safe to fix fragments without a jsx parent. + if (!(node.parent.type === 'JSXElement' || node.parent.type === 'JSXFragment')) { + // const a = <> + if (node.children.length === 0) { + return false; + } + + // const a = <>cat {meow} + if (node.children.some(isNonspaceJSXTextOrJSXCurly)) { + return false; + } + } + + // Not safe to fix `<>foo` because `Eeee` might require its children be a ReactElement. + if (isChildOfComponentElement(node)) { + return false; + } + + // old TS parser can't handle this one + if (node.type === 'JSXFragment' && (!node.openingFragment || !node.closingFragment)) { + return false; + } + + return true; + } + + /** + * @param {ASTNode} node + * @returns {Function | undefined} + */ + function getFix(node) { + if (!canFix(node)) { + return undefined; + } + + return function fix(fixer) { + const opener = node.type === 'JSXFragment' ? node.openingFragment : node.openingElement; + const closer = node.type === 'JSXFragment' ? node.closingFragment : node.closingElement; + + const childrenText = opener.selfClosing ? '' : getText(context).slice(opener.range[1], closer.range[0]); + + return fixer.replaceText(node, trimLikeReact(childrenText)); + }; + } + + function checkNode(node) { + if (isKeyedElement(node)) { + return; + } + + if ( + hasLessThanTwoChildren(node) + && !isFragmentWithOnlyTextAndIsNotChild(node) + && !(allowExpressions && isFragmentWithSingleExpression(node)) + ) { + report(context, messages.NeedsMoreChildren, 'NeedsMoreChildren', { + node, + fix: getFix(node), + }); + } + + if (isChildOfHtmlElement(node)) { + report(context, messages.ChildOfHtmlElement, 'ChildOfHtmlElement', { + node, + fix: getFix(node), + }); + } + } + + return { + JSXElement(node) { + if (jsxUtil.isFragment(node, reactPragma, fragmentPragma)) { + checkNode(node); + } + }, + JSXFragment: checkNode, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3da73341c9aa089c52a04167bbf4714409354d9b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-one-expression-per-line.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..a1f895dbfd09ed56a7e6d5cb219b5342b4b9a741 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-one-expression-per-line.d.ts","sourceRoot":"","sources":["jsx-one-expression-per-line.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js new file mode 100644 index 0000000000000000000000000000000000000000..3ae61e3d9671ff6c0601cdee35a1f4754d69b5d2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js @@ -0,0 +1,249 @@ +/** + * @fileoverview Limit to one expression per line in JSX + * @author Mark Ivan Allen + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +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 +// ------------------------------------------------------------------------------ + +const optionDefaults = { + allow: 'none', +}; + +const messages = { + moveToNewLine: '`{{descriptor}}` must be placed on a new line', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Require one JSX element per line', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-one-expression-per-line'), + }, + fixable: 'whitespace', + + messages, + + schema: [ + { + type: 'object', + properties: { + allow: { + enum: ['none', 'literal', 'single-child', 'non-jsx'], + }, + }, + default: optionDefaults, + additionalProperties: false, + }, + ], + }, + + create(context) { + const options = Object.assign({}, optionDefaults, context.options[0]); + + function nodeKey(node) { + return `${node.loc.start.line},${node.loc.start.column}`; + } + + /** + * @param {ASTNode} n + * @returns {string} + */ + function nodeDescriptor(n) { + return n.openingElement ? n.openingElement.name.name : getText(context, n).replace(/\n/g, ''); + } + + function handleJSX(node) { + const children = node.children; + + if (!children || !children.length) { + return; + } + + if ( + options.allow === 'non-jsx' + && !children.find((child) => (child.type === 'JSXFragment' || child.type === 'JSXElement')) + ) { + return; + } + + const openingElement = node.openingElement || node.openingFragment; + const closingElement = node.closingElement || node.closingFragment; + const openingElementStartLine = openingElement.loc.start.line; + const openingElementEndLine = openingElement.loc.end.line; + const closingElementStartLine = closingElement.loc.start.line; + const closingElementEndLine = closingElement.loc.end.line; + + if (children.length === 1) { + const child = children[0]; + if ( + openingElementStartLine === openingElementEndLine + && openingElementEndLine === closingElementStartLine + && closingElementStartLine === closingElementEndLine + && closingElementEndLine === child.loc.start.line + && child.loc.start.line === child.loc.end.line + ) { + if ( + options.allow === 'single-child' + || (options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText')) + ) { + return; + } + } + } + + const childrenGroupedByLine = {}; + const fixDetailsByNode = {}; + + children.forEach((child) => { + let countNewLinesBeforeContent = 0; + let countNewLinesAfterContent = 0; + + if (child.type === 'Literal' || child.type === 'JSXText') { + if (jsxUtil.isWhiteSpaces(child.raw)) { + return; + } + + countNewLinesBeforeContent = (child.raw.match(/^\s*\n/g) || []).length; + countNewLinesAfterContent = (child.raw.match(/\n\s*$/g) || []).length; + } + + const startLine = child.loc.start.line + countNewLinesBeforeContent; + const endLine = child.loc.end.line - countNewLinesAfterContent; + + if (startLine === endLine) { + if (!childrenGroupedByLine[startLine]) { + childrenGroupedByLine[startLine] = []; + } + childrenGroupedByLine[startLine].push(child); + } else { + if (!childrenGroupedByLine[startLine]) { + childrenGroupedByLine[startLine] = []; + } + childrenGroupedByLine[startLine].push(child); + if (!childrenGroupedByLine[endLine]) { + childrenGroupedByLine[endLine] = []; + } + childrenGroupedByLine[endLine].push(child); + } + }); + + Object.keys(childrenGroupedByLine).forEach((_line) => { + const line = parseInt(_line, 10); + const firstIndex = 0; + const lastIndex = childrenGroupedByLine[line].length - 1; + + childrenGroupedByLine[line].forEach((child, i) => { + let prevChild; + let nextChild; + + if (i === firstIndex) { + if (line === openingElementEndLine) { + prevChild = openingElement; + } + } else { + prevChild = childrenGroupedByLine[line][i - 1]; + } + + if (i === lastIndex) { + if (line === closingElementStartLine) { + nextChild = closingElement; + } + } else { + // We don't need to append a trailing because the next child will prepend a leading. + // nextChild = childrenGroupedByLine[line][i + 1]; + } + + function spaceBetweenPrev() { + return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && / $/.test(prevChild.raw)) + || ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw)) + || 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)) + || getSourceCode(context).isSpaceBetweenTokens(child, nextChild); + } + + if (!prevChild && !nextChild) { + return; + } + + const source = getText(context, child); + const leadingSpace = !!(prevChild && spaceBetweenPrev()); + const trailingSpace = !!(nextChild && spaceBetweenNext()); + const leadingNewLine = !!prevChild; + const trailingNewLine = !!nextChild; + + const key = nodeKey(child); + + if (!fixDetailsByNode[key]) { + fixDetailsByNode[key] = { + node: child, + source, + descriptor: nodeDescriptor(child), + }; + } + + if (leadingSpace) { + fixDetailsByNode[key].leadingSpace = true; + } + if (leadingNewLine) { + fixDetailsByNode[key].leadingNewLine = true; + } + if (trailingNewLine) { + fixDetailsByNode[key].trailingNewLine = true; + } + if (trailingSpace) { + fixDetailsByNode[key].trailingSpace = true; + } + }); + }); + + Object.keys(fixDetailsByNode).forEach((key) => { + const details = fixDetailsByNode[key]; + + const nodeToReport = details.node; + const descriptor = details.descriptor; + const source = details.source.replace(/(^ +| +(?=\n)*$)/g, ''); + + const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : ''; + const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : ''; + const leadingNewLineString = details.leadingNewLine ? '\n' : ''; + const trailingNewLineString = details.trailingNewLine ? '\n' : ''; + + const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`; + + report(context, messages.moveToNewLine, 'moveToNewLine', { + node: nodeToReport, + data: { + descriptor, + }, + fix(fixer) { + return fixer.replaceText(nodeToReport, replaceText); + }, + }); + }); + } + + return { + JSXElement: handleJSX, + JSXFragment: handleJSX, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ba2690ea930ccae8b48b847a20251c94dca9448 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-pascal-case.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..7c4713ef3c64088fcab533e16d582b29a33d9d88 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-pascal-case.d.ts","sourceRoot":"","sources":["jsx-pascal-case.js"],"names":[],"mappings":"wBA8EW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js new file mode 100644 index 0000000000000000000000000000000000000000..efeef4032a97a6918e275d68600d644a242c2b83 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js @@ -0,0 +1,164 @@ +/** + * @fileoverview Enforce PascalCase for user-defined JSX components + * @author Jake Marsh + */ + +'use strict'; + +const elementType = require('jsx-ast-utils/elementType'); +const minimatch = require('minimatch'); +const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); + +function testDigit(char) { + const charCode = char.charCodeAt(0); + return charCode >= 48 && charCode <= 57; +} + +function testUpperCase(char) { + const upperCase = char.toUpperCase(); + return char === upperCase && upperCase !== char.toLowerCase(); +} + +function testLowerCase(char) { + const lowerCase = char.toLowerCase(); + return char === lowerCase && lowerCase !== char.toUpperCase(); +} + +function testPascalCase(name) { + if (!testUpperCase(name.charAt(0))) { + return false; + } + const anyNonAlphaNumeric = Array.prototype.some.call( + name.slice(1), + (char) => char.toLowerCase() === char.toUpperCase() && !testDigit(char) + ); + if (anyNonAlphaNumeric) { + return false; + } + return Array.prototype.some.call( + name.slice(1), + (char) => testLowerCase(char) || testDigit(char) + ); +} + +function testAllCaps(name) { + const firstChar = name.charAt(0); + if (!(testUpperCase(firstChar) || testDigit(firstChar))) { + return false; + } + for (let i = 1; i < name.length - 1; i += 1) { + const char = name.charAt(i); + if (!(testUpperCase(char) || testDigit(char) || char === '_')) { + return false; + } + } + const lastChar = name.charAt(name.length - 1); + if (!(testUpperCase(lastChar) || testDigit(lastChar))) { + return false; + } + return true; +} + +function ignoreCheck(ignore, name) { + return ignore.some( + (entry) => name === entry || minimatch(name, entry, { noglobstar: true }) + ); +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + usePascalCase: 'Imported JSX component {{name}} must be in PascalCase', + usePascalOrSnakeCase: 'Imported JSX component {{name}} must be in PascalCase or SCREAMING_SNAKE_CASE', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce PascalCase for user-defined JSX components', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-pascal-case'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowAllCaps: { + type: 'boolean', + }, + allowLeadingUnderscore: { + type: 'boolean', + }, + allowNamespace: { + type: 'boolean', + }, + ignore: { + items: [ + { + type: 'string', + }, + ], + minItems: 0, + type: 'array', + uniqueItems: true, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const allowAllCaps = configuration.allowAllCaps || false; + const allowLeadingUnderscore = configuration.allowLeadingUnderscore || false; + const allowNamespace = configuration.allowNamespace || false; + const ignore = configuration.ignore || []; + + return { + JSXOpeningElement(node) { + const isCompatTag = jsxUtil.isDOMComponent(node); + if (isCompatTag) return undefined; + + const name = elementType(node); + let checkNames = [name]; + let index = 0; + + if (name.lastIndexOf(':') > -1) { + checkNames = name.split(':'); + } else if (name.lastIndexOf('.') > -1) { + checkNames = name.split('.'); + } + + do { + const splitName = checkNames[index]; + if (splitName.length === 1) return undefined; + const isIgnored = ignoreCheck(ignore, splitName); + + const checkName = allowLeadingUnderscore && splitName.startsWith('_') ? splitName.slice(1) : splitName; + const isPascalCase = testPascalCase(checkName); + const isAllowedAllCaps = allowAllCaps && testAllCaps(checkName); + + if (!isPascalCase && !isAllowedAllCaps && !isIgnored) { + const messageId = allowAllCaps ? 'usePascalOrSnakeCase' : 'usePascalCase'; + report(context, messages[messageId], messageId, { + node, + data: { + name: splitName, + }, + }); + break; + } + index += 1; + } while (index < checkNames.length && !allowNamespace); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f58e5d0d390147b2ab37dcb3d1858cb068c8b29 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-props-no-multi-spaces.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..60f4d2b5169be238078f455b3b4d339ec1e99163 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-props-no-multi-spaces.d.ts","sourceRoot":"","sources":["jsx-props-no-multi-spaces.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js new file mode 100644 index 0000000000000000000000000000000000000000..8402c6d4ff561ac1e4b73321a7e202dea48c9e1e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js @@ -0,0 +1,144 @@ +/** + * @fileoverview Disallow multiple spaces between inline JSX props + * @author Adrian Moennich + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const eslintUtil = require('../util/eslint'); +const report = require('../util/report'); +const propsUtil = require('../util/props'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”', + onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow multiple spaces between inline JSX props', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-props-no-multi-spaces'), + }, + fixable: 'code', + + messages, + + schema: [], + }, + + create(context) { + const sourceCode = getSourceCode(context); + + function getPropName(propNode) { + switch (propNode.type) { + case 'JSXSpreadAttribute': + return getText(context, propNode.argument); + case 'JSXIdentifier': + return propNode.name; + case 'JSXMemberExpression': + return `${getPropName(propNode.object)}.${propNode.property.name}`; + default: + return propNode.name + ? propNode.name.name + : `${getText(context, propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser + } + } + + // First and second must be adjacent nodes + function hasEmptyLines(first, second) { + const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : []; + const nodes = [].concat(first, comments, second); + + for (let i = 1; i < nodes.length; i += 1) { + const prev = nodes[i - 1]; + const curr = nodes[i]; + if (curr.loc.start.line - prev.loc.end.line >= 2) { + return true; + } + } + + return false; + } + + function checkSpacing(prev, node) { + if (hasEmptyLines(prev, node)) { + report(context, messages.noLineGap, 'noLineGap', { + node, + data: { + prop1: getPropName(prev), + prop2: getPropName(node), + }, + }); + } + + if (prev.loc.end.line !== node.loc.end.line) { + return; + } + + const between = getSourceCode(context).text.slice(prev.range[1], node.range[0]); + + if (between !== ' ') { + report(context, messages.onlyOneSpace, 'onlyOneSpace', { + node, + data: { + prop1: getPropName(prev), + prop2: getPropName(node), + }, + fix(fixer) { + return fixer.replaceTextRange([prev.range[1], node.range[0]], ' '); + }, + }); + } + } + + function containsGenericType(node) { + const nodeTypeArguments = propsUtil.getTypeArguments(node); + if (typeof nodeTypeArguments === 'undefined') { + return false; + } + + return nodeTypeArguments.type === 'TSTypeParameterInstantiation'; + } + + function getGenericNode(node) { + const name = node.name; + if (containsGenericType(node)) { + const nodeTypeArguments = propsUtil.getTypeArguments(node); + + return Object.assign( + {}, + node, + { + range: [ + name.range[0], + nodeTypeArguments.range[1], + ], + } + ); + } + + return name; + } + + return { + JSXOpeningElement(node) { + node.attributes.reduce((prev, prop) => { + checkSpacing(prev, prop); + return prop; + }, getGenericNode(node)); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e9ecb76e9975f2cb10f8e1a55fde704dcec7602 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-props-no-spread-multi.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..ae4206e6602889e0bbcbbdccedcdbab57a1850cd --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-props-no-spread-multi.d.ts","sourceRoot":"","sources":["jsx-props-no-spread-multi.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js new file mode 100644 index 0000000000000000000000000000000000000000..3103be86da1bf447c358b491c6bfe9c917561c5a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js @@ -0,0 +1,54 @@ +/** + * @fileoverview Prevent JSX prop spreading the same expression multiple times + * @author Simon Schick + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noMultiSpreading: 'Spreading the same expression multiple times is forbidden', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow JSX prop spreading the same identifier multiple times', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-props-no-spread-multi'), + }, + messages, + }, + + create(context) { + return { + JSXOpeningElement(node) { + const spreads = node.attributes.filter( + (attr) => attr.type === 'JSXSpreadAttribute' + && attr.argument.type === 'Identifier' + ); + if (spreads.length < 2) { + return; + } + // We detect duplicate expressions by their identifier + const identifierNames = new Set(); + spreads.forEach((spread) => { + if (identifierNames.has(spread.argument.name)) { + report(context, messages.noMultiSpreading, 'noMultiSpreading', { + node: spread, + }); + } + identifierNames.add(spread.argument.name); + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ed3469d2f315865a272ea46cde73cf919fba48c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-props-no-spreading.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..3db995403aedd8396fd13ce18311e6cda0d68f78 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-props-no-spreading.d.ts","sourceRoot":"","sources":["jsx-props-no-spreading.js"],"names":[],"mappings":"wBAwCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js new file mode 100644 index 0000000000000000000000000000000000000000..1aa8ab4ace578cf6eb7a075b134cda198ef044bd --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js @@ -0,0 +1,145 @@ +/** + * @fileoverview Prevent JSX prop spreading + * @author Ashish Gambhir + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const OPTIONS = { ignore: 'ignore', enforce: 'enforce' }; +const DEFAULTS = { + html: OPTIONS.enforce, + custom: OPTIONS.enforce, + explicitSpread: OPTIONS.enforce, + exceptions: [], +}; + +const isException = (tag, allExceptions) => allExceptions.indexOf(tag) !== -1; +const isProperty = (property) => property.type === 'Property'; +const getTagNameFromMemberExpression = (node) => { + if (node.property.parent) { + return `${node.property.parent.object.name}.${node.property.name}`; + } + // for eslint 3 + return `${node.object.name}.${node.property.name}`; +}; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noSpreading: 'Prop spreading is forbidden', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow JSX prop spreading', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-props-no-spreading'), + }, + + messages, + + schema: [{ + allOf: [{ + type: 'object', + properties: { + html: { + enum: [OPTIONS.enforce, OPTIONS.ignore], + }, + custom: { + enum: [OPTIONS.enforce, OPTIONS.ignore], + }, + explicitSpread: { + enum: [OPTIONS.enforce, OPTIONS.ignore], + }, + exceptions: { + type: 'array', + items: { + type: 'string', + uniqueItems: true, + }, + }, + }, + }, { + not: { + type: 'object', + required: ['html', 'custom'], + properties: { + html: { + enum: [OPTIONS.ignore], + }, + custom: { + enum: [OPTIONS.ignore], + }, + exceptions: { + type: 'array', + minItems: 0, + maxItems: 0, + }, + }, + }, + }], + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const ignoreHtmlTags = (configuration.html || DEFAULTS.html) === OPTIONS.ignore; + const ignoreCustomTags = (configuration.custom || DEFAULTS.custom) === OPTIONS.ignore; + const ignoreExplicitSpread = (configuration.explicitSpread || DEFAULTS.explicitSpread) === OPTIONS.ignore; + const exceptions = configuration.exceptions || DEFAULTS.exceptions; + return { + JSXSpreadAttribute(node) { + const jsxOpeningElement = node.parent.name; + const type = jsxOpeningElement.type; + + let tagName; + if (type === 'JSXIdentifier') { + tagName = jsxOpeningElement.name; + } else if (type === 'JSXMemberExpression') { + tagName = getTagNameFromMemberExpression(jsxOpeningElement); + } else { + tagName = undefined; + } + + const isHTMLTag = tagName && tagName[0] !== tagName[0].toUpperCase(); + const isCustomTag = tagName && (tagName[0] === tagName[0].toUpperCase() || tagName.includes('.')); + if ( + isHTMLTag + && ((ignoreHtmlTags && !isException(tagName, exceptions)) + || (!ignoreHtmlTags && isException(tagName, exceptions))) + ) { + return; + } + if ( + isCustomTag + && ((ignoreCustomTags && !isException(tagName, exceptions)) + || (!ignoreCustomTags && isException(tagName, exceptions))) + ) { + return; + } + if ( + ignoreExplicitSpread + && node.argument.type === 'ObjectExpression' + && node.argument.properties.every(isProperty) + ) { + return; + } + report(context, messages.noSpreading, 'noSpreading', { + node, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..8db526cc097e3758478390c07a98f1d93e90707f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-sort-default-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..af11f3370cef1e279ef9c818993d53829a20964d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-sort-default-props.d.ts","sourceRoot":"","sources":["jsx-sort-default-props.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js new file mode 100644 index 0000000000000000000000000000000000000000..f990f7718928fad69f1230abf3e9cfb0aba5c1f6 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js @@ -0,0 +1,195 @@ +/** + * @fileoverview Enforce default props alphabetical sorting + * @author Vladimir Kattsov + * @deprecated + */ + +'use strict'; + +const variableUtil = require('../util/variable'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const log = require('../util/log'); +const eslintUtil = require('../util/eslint'); + +const getFirstTokens = eslintUtil.getFirstTokens; +const getText = eslintUtil.getText; + +let isWarnedForDeprecation = false; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + propsNotSorted: 'Default prop types declarations should be sorted alphabetically', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + deprecated: true, + replacedBy: ['sort-default-props'], + docs: { + description: 'Enforce defaultProps declarations alphabetical sorting', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-sort-default-props'), + }, + // fixable: 'code', + + messages, + + schema: [{ + type: 'object', + properties: { + ignoreCase: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || false; + + /** + * Get properties name + * @param {Object} node - Property. + * @returns {string} Property name. + */ + function getPropertyName(node) { + if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) { + return node.key.name; + } + if (node.type === 'MemberExpression') { + return node.property.name; + // Special case for class properties + // (babel-eslint@5 does not expose property name so we have to rely on tokens) + } + if (node.type === 'ClassProperty') { + const tokens = getFirstTokens(context, node, 2); + return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; + } + return ''; + } + + /** + * Checks if the Identifier node passed in looks like a defaultProps declaration. + * @param {ASTNode} node The node to check. Must be an Identifier node. + * @returns {boolean} `true` if the node is a defaultProps declaration, `false` if not + */ + function isDefaultPropsDeclaration(node) { + const propName = getPropertyName(node); + return (propName === 'defaultProps' || propName === 'getDefaultProps'); + } + + function getKey(node) { + return getText(context, node.key || node.argument); + } + + /** + * 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(node, name) { + const variable = variableUtil + .getVariableFromContext(context, node, name); + + if (!variable || !variable.defs[0] || !variable.defs[0].node) { + return null; + } + + if (variable.defs[0].node.type === 'TypeAlias') { + return variable.defs[0].node.right; + } + + return variable.defs[0].node.init; + } + + /** + * Checks if defaultProps declarations are sorted + * @param {Array} declarations The array of AST nodes being checked. + * @returns {void} + */ + function checkSorted(declarations) { + // function fix(fixer) { + // return propTypesSortUtil.fixPropTypesSort(context, fixer, declarations, ignoreCase); + // } + + declarations.reduce((prev, curr, idx, decls) => { + if (/Spread(?:Property|Element)$/.test(curr.type)) { + return decls[idx + 1]; + } + + let prevPropName = getKey(prev); + let currentPropName = getKey(curr); + + if (ignoreCase) { + prevPropName = prevPropName.toLowerCase(); + currentPropName = currentPropName.toLowerCase(); + } + + if (currentPropName < prevPropName) { + report(context, messages.propsNotSorted, 'propsNotSorted', { + node: curr, + // fix + }); + + return prev; + } + + return curr; + }, declarations[0]); + } + + function checkNode(node) { + if (!node) { + return; + } + if (node.type === 'ObjectExpression') { + checkSorted(node.properties); + } else if (node.type === 'Identifier') { + const propTypesObject = findVariableByName(node, node.name); + if (propTypesObject && propTypesObject.properties) { + checkSorted(propTypesObject.properties); + } + } + } + + // -------------------------------------------------------------------------- + // Public API + // -------------------------------------------------------------------------- + + return { + 'ClassProperty, PropertyDefinition'(node) { + if (!isDefaultPropsDeclaration(node)) { + return; + } + + checkNode(node.value); + }, + + MemberExpression(node) { + if (!isDefaultPropsDeclaration(node)) { + return; + } + + checkNode('right' in node.parent && node.parent.right); + }, + + Program() { + if (isWarnedForDeprecation) { + return; + } + + log('The react/jsx-sort-default-props rule is deprecated. It has been renamed to `react/sort-default-props`.'); + isWarnedForDeprecation = true; + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7dda5aae5fb74f9f7ebc59afcf14414dc2e39d0 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-sort-props.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..ec19545c4ef95c9a75592b7f518b3a8e57af548f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-sort-props.d.ts","sourceRoot":"","sources":["jsx-sort-props.js"],"names":[],"mappings":"wBAoVW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js new file mode 100644 index 0000000000000000000000000000000000000000..ec9d869b747f8845b4f8ed4593a76eba43e6b5f1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js @@ -0,0 +1,531 @@ +/** + * @fileoverview Enforce props alphabetical sorting + * @author Ilya Volodin, Yannick Croissant + */ + +'use strict'; + +const propName = require('jsx-ast-utils/propName'); +const includes = require('array-includes'); +const toSorted = require('array.prototype.tosorted'); + +const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); +const propTypesSortUtil = require('../util/propTypesSort'); +const eslintUtil = require('../util/eslint'); + +const getText = eslintUtil.getText; +const getSourceCode = eslintUtil.getSourceCode; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function isMultilineProp(node) { + return node.loc.start.line !== node.loc.end.line; +} + +const messages = { + noUnreservedProps: 'A customized reserved first list must only contain a subset of React reserved props. Remove: {{unreservedWords}}', + listIsEmpty: 'A customized reserved first list must not be empty', + listReservedPropsFirst: 'Reserved props must be listed before all other props', + listCallbacksLast: 'Callbacks must be listed after all other props', + listShorthandFirst: 'Shorthand props must be listed before all other props', + listShorthandLast: 'Shorthand props must be listed after all other props', + listMultilineFirst: 'Multiline props must be listed before all other props', + listMultilineLast: 'Multiline props must be listed after all other props', + sortPropsByAlpha: 'Props should be sorted alphabetically', +}; + +const RESERVED_PROPS_LIST = [ + 'children', + 'dangerouslySetInnerHTML', + 'key', + 'ref', +]; + +function isReservedPropName(name, list) { + return list.indexOf(name) >= 0; +} + +let attributeMap; +// attributeMap = { end: endrange, hasComment: true||false if comment in between nodes exists, it needs to be sorted to end } + +function shouldSortToEnd(node) { + const attr = attributeMap.get(node); + return !!attr && !!attr.hasComment; +} + +function contextCompare(a, b, options) { + let aProp = propName(a); + let bProp = propName(b); + + const aSortToEnd = shouldSortToEnd(a); + const bSortToEnd = shouldSortToEnd(b); + if (aSortToEnd && !bSortToEnd) { + return 1; + } + if (!aSortToEnd && bSortToEnd) { + return -1; + } + + if (options.reservedFirst) { + const aIsReserved = isReservedPropName(aProp, options.reservedList); + const bIsReserved = isReservedPropName(bProp, options.reservedList); + if (aIsReserved && !bIsReserved) { + return -1; + } + if (!aIsReserved && bIsReserved) { + return 1; + } + } + + if (options.callbacksLast) { + const aIsCallback = propTypesSortUtil.isCallbackPropName(aProp); + const bIsCallback = propTypesSortUtil.isCallbackPropName(bProp); + if (aIsCallback && !bIsCallback) { + return 1; + } + if (!aIsCallback && bIsCallback) { + return -1; + } + } + + if (options.shorthandFirst || options.shorthandLast) { + const shorthandSign = options.shorthandFirst ? -1 : 1; + if (!a.value && b.value) { + return shorthandSign; + } + if (a.value && !b.value) { + return -shorthandSign; + } + } + + if (options.multiline !== 'ignore') { + const multilineSign = options.multiline === 'first' ? -1 : 1; + const aIsMultiline = isMultilineProp(a); + const bIsMultiline = isMultilineProp(b); + if (aIsMultiline && !bIsMultiline) { + return multilineSign; + } + if (!aIsMultiline && bIsMultiline) { + return -multilineSign; + } + } + + if (options.noSortAlphabetically) { + return 0; + } + + const actualLocale = options.locale === 'auto' ? undefined : options.locale; + + if (options.ignoreCase) { + aProp = aProp.toLowerCase(); + bProp = bProp.toLowerCase(); + return aProp.localeCompare(bProp, actualLocale); + } + if (aProp === bProp) { + return 0; + } + if (options.locale === 'auto') { + return aProp < bProp ? -1 : 1; + } + return aProp.localeCompare(bProp, actualLocale); +} + +/** + * Create an array of arrays where each subarray is composed of attributes + * that are considered sortable. + * @param {Array} attributes + * @param {Object} context The context of the rule + * @return {Array>} + */ +function getGroupsOfSortableAttributes(attributes, context) { + const sourceCode = getSourceCode(context); + + const sortableAttributeGroups = []; + let groupCount = 0; + function addtoSortableAttributeGroups(attribute) { + sortableAttributeGroups[groupCount - 1].push(attribute); + } + + for (let i = 0; i < attributes.length; i++) { + const attribute = attributes[i]; + const nextAttribute = attributes[i + 1]; + const attributeline = attribute.loc.start.line; + let comment = []; + try { + comment = sourceCode.getCommentsAfter(attribute); + } catch (e) { /**/ } + const lastAttr = attributes[i - 1]; + const attrIsSpread = attribute.type === 'JSXSpreadAttribute'; + + // If we have no groups or if the last attribute was JSXSpreadAttribute + // then we start a new group. Append attributes to the group until we + // come across another JSXSpreadAttribute or exhaust the array. + if ( + !lastAttr + || (lastAttr.type === 'JSXSpreadAttribute' && !attrIsSpread) + ) { + groupCount += 1; + sortableAttributeGroups[groupCount - 1] = []; + } + if (!attrIsSpread) { + if (comment.length === 0) { + attributeMap.set(attribute, { end: attribute.range[1], hasComment: false }); + addtoSortableAttributeGroups(attribute); + } else { + const firstComment = comment[0]; + const commentline = firstComment.loc.start.line; + if (comment.length === 1) { + if (attributeline + 1 === commentline && nextAttribute) { + attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true }); + addtoSortableAttributeGroups(attribute); + i += 1; + } else if (attributeline === commentline) { + if (firstComment.type === 'Block' && nextAttribute) { + attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true }); + i += 1; + } else if (firstComment.type === 'Block') { + attributeMap.set(attribute, { end: firstComment.range[1], hasComment: true }); + } else { + attributeMap.set(attribute, { end: firstComment.range[1], hasComment: false }); + } + addtoSortableAttributeGroups(attribute); + } + } else if (comment.length > 1 && attributeline + 1 === comment[1].loc.start.line && nextAttribute) { + const commentNextAttribute = sourceCode.getCommentsAfter(nextAttribute); + attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true }); + if ( + commentNextAttribute.length === 1 + && nextAttribute.loc.start.line === commentNextAttribute[0].loc.start.line + ) { + attributeMap.set(attribute, { end: commentNextAttribute[0].range[1], hasComment: true }); + } + addtoSortableAttributeGroups(attribute); + i += 1; + } + } + } + } + return sortableAttributeGroups; +} + +function generateFixerFunction(node, context, reservedList) { + const attributes = node.attributes.slice(0); + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || false; + const callbacksLast = configuration.callbacksLast || false; + const shorthandFirst = configuration.shorthandFirst || false; + const shorthandLast = configuration.shorthandLast || false; + const multiline = configuration.multiline || 'ignore'; + const noSortAlphabetically = configuration.noSortAlphabetically || false; + const reservedFirst = configuration.reservedFirst || false; + const locale = configuration.locale || 'auto'; + + // Sort props according to the context. Only supports ignoreCase. + // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides), + // we only consider groups of sortable attributes. + const options = { + ignoreCase, + callbacksLast, + shorthandFirst, + shorthandLast, + multiline, + noSortAlphabetically, + reservedFirst, + reservedList, + locale, + }; + const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes, context); + const sortedAttributeGroups = sortableAttributeGroups + .slice(0) + .map((group) => toSorted(group, (a, b) => contextCompare(a, b, options))); + + return function fixFunction(fixer) { + const fixers = []; + let source = getText(context); + + sortableAttributeGroups.forEach((sortableGroup, ii) => { + sortableGroup.forEach((attr, jj) => { + const sortedAttr = sortedAttributeGroups[ii][jj]; + const sortedAttrText = source.slice(sortedAttr.range[0], attributeMap.get(sortedAttr).end); + fixers.push({ + range: [attr.range[0], attributeMap.get(attr).end], + text: sortedAttrText, + }); + }); + }); + + fixers.sort((a, b) => b.range[0] - a.range[0]); + + const firstFixer = fixers[0]; + const lastFixer = fixers[fixers.length - 1]; + const rangeStart = lastFixer ? lastFixer.range[0] : 0; + const rangeEnd = firstFixer ? firstFixer.range[1] : -0; + + fixers.forEach((fix) => { + source = `${source.slice(0, fix.range[0])}${fix.text}${source.slice(fix.range[1])}`; + }); + + return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd)); + }; +} + +/** + * Checks if the `reservedFirst` option is valid + * @param {Object} context The context of the rule + * @param {boolean | string[]} reservedFirst The `reservedFirst` option + * @return {Function | undefined} If an error is detected, a function to generate the error message, otherwise, `undefined` + */ +// eslint-disable-next-line consistent-return +function validateReservedFirstConfig(context, reservedFirst) { + if (reservedFirst) { + if (Array.isArray(reservedFirst)) { + // Only allow a subset of reserved words in customized lists + const nonReservedWords = reservedFirst.filter((word) => !isReservedPropName( + word, + RESERVED_PROPS_LIST + )); + + if (reservedFirst.length === 0) { + return function Report(decl) { + report(context, messages.listIsEmpty, 'listIsEmpty', { + node: decl, + }); + }; + } + if (nonReservedWords.length > 0) { + return function Report(decl) { + report(context, messages.noUnreservedProps, 'noUnreservedProps', { + node: decl, + data: { + unreservedWords: nonReservedWords.toString(), + }, + }); + }; + } + } + } +} + +const reportedNodeAttributes = new WeakMap(); +/** + * Check if the current node attribute has already been reported with the same error type + * if that's the case then we don't report a new error + * otherwise we report the error + * @param {Object} nodeAttribute The node attribute to be reported + * @param {string} errorType The error type to be reported + * @param {Object} node The parent node for the node attribute + * @param {Object} context The context of the rule + * @param {Array} reservedList The list of reserved props + */ +function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedList) { + const errors = reportedNodeAttributes.get(nodeAttribute) || []; + + if (includes(errors, errorType)) { + return; + } + + errors.push(errorType); + + reportedNodeAttributes.set(nodeAttribute, errors); + + report(context, messages[errorType], errorType, { + node: nodeAttribute.name, + fix: generateFixerFunction(node, context, reservedList), + }); +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Enforce props alphabetical sorting', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-sort-props'), + }, + fixable: 'code', + + messages, + + schema: [{ + type: 'object', + properties: { + // Whether callbacks (prefixed with "on") should be listed at the very end, + // after all other props. Supersedes shorthandLast. + callbacksLast: { + type: 'boolean', + }, + // Whether shorthand properties (without a value) should be listed first + shorthandFirst: { + type: 'boolean', + }, + // Whether shorthand properties (without a value) should be listed last + shorthandLast: { + type: 'boolean', + }, + // Whether multiline properties should be listed first or last + multiline: { + enum: ['ignore', 'first', 'last'], + default: 'ignore', + }, + ignoreCase: { + type: 'boolean', + }, + // Whether alphabetical sorting should be enforced + noSortAlphabetically: { + type: 'boolean', + }, + reservedFirst: { + type: ['array', 'boolean'], + }, + locale: { + type: 'string', + default: 'auto', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || false; + const callbacksLast = configuration.callbacksLast || false; + const shorthandFirst = configuration.shorthandFirst || false; + const shorthandLast = configuration.shorthandLast || false; + const multiline = configuration.multiline || 'ignore'; + const noSortAlphabetically = configuration.noSortAlphabetically || false; + const reservedFirst = configuration.reservedFirst || false; + const reservedFirstError = validateReservedFirstConfig(context, reservedFirst); + const reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST; + const locale = configuration.locale || 'auto'; + + return { + Program() { + attributeMap = new WeakMap(); + }, + + JSXOpeningElement(node) { + // `dangerouslySetInnerHTML` is only "reserved" on DOM components + const nodeReservedList = reservedFirst && !jsxUtil.isDOMComponent(node) ? reservedList.filter((prop) => prop !== 'dangerouslySetInnerHTML') : reservedList; + + node.attributes.reduce((memo, decl, idx, attrs) => { + if (decl.type === 'JSXSpreadAttribute') { + return attrs[idx + 1]; + } + + let previousPropName = propName(memo); + let currentPropName = propName(decl); + const previousValue = memo.value; + const currentValue = decl.value; + const previousIsCallback = propTypesSortUtil.isCallbackPropName(previousPropName); + const currentIsCallback = propTypesSortUtil.isCallbackPropName(currentPropName); + + if (ignoreCase) { + previousPropName = previousPropName.toLowerCase(); + currentPropName = currentPropName.toLowerCase(); + } + + if (reservedFirst) { + if (reservedFirstError) { + reservedFirstError(decl); + return memo; + } + + const previousIsReserved = isReservedPropName(previousPropName, nodeReservedList); + const currentIsReserved = isReservedPropName(currentPropName, nodeReservedList); + + if (previousIsReserved && !currentIsReserved) { + return decl; + } + if (!previousIsReserved && currentIsReserved) { + reportNodeAttribute(decl, 'listReservedPropsFirst', node, context, nodeReservedList); + + return memo; + } + } + + if (callbacksLast) { + if (!previousIsCallback && currentIsCallback) { + // Entering the callback prop section + return decl; + } + if (previousIsCallback && !currentIsCallback) { + // Encountered a non-callback prop after a callback prop + reportNodeAttribute(memo, 'listCallbacksLast', node, context, nodeReservedList); + + return memo; + } + } + + if (shorthandFirst) { + if (currentValue && !previousValue) { + return decl; + } + if (!currentValue && previousValue) { + reportNodeAttribute(decl, 'listShorthandFirst', node, context, nodeReservedList); + + return memo; + } + } + + if (shorthandLast) { + if (!currentValue && previousValue) { + return decl; + } + if (currentValue && !previousValue) { + reportNodeAttribute(memo, 'listShorthandLast', node, context, nodeReservedList); + + return memo; + } + } + + const previousIsMultiline = isMultilineProp(memo); + const currentIsMultiline = isMultilineProp(decl); + if (multiline === 'first') { + if (previousIsMultiline && !currentIsMultiline) { + // Exiting the multiline prop section + return decl; + } + if (!previousIsMultiline && currentIsMultiline) { + // Encountered a non-multiline prop before a multiline prop + reportNodeAttribute(decl, 'listMultilineFirst', node, context, nodeReservedList); + + return memo; + } + } else if (multiline === 'last') { + if (!previousIsMultiline && currentIsMultiline) { + // Entering the multiline prop section + return decl; + } + if (previousIsMultiline && !currentIsMultiline) { + // Encountered a non-multiline prop after a multiline prop + reportNodeAttribute(memo, 'listMultilineLast', node, context, nodeReservedList); + + return memo; + } + } + + if ( + !noSortAlphabetically + && ( + (ignoreCase || locale !== 'auto') + ? previousPropName.localeCompare(currentPropName, locale === 'auto' ? undefined : locale) > 0 + : previousPropName > currentPropName + ) + ) { + reportNodeAttribute(decl, 'sortPropsByAlpha', node, context, nodeReservedList); + + return memo; + } + + return decl; + }, node.attributes[0]); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..393dcb0f08b1b0d7f40310069de26ff48cf0435b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-space-before-closing.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..243cb044d992f3e75134201f5a91a87c8a25e993 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-space-before-closing.d.ts","sourceRoot":"","sources":["jsx-space-before-closing.js"],"names":[],"mappings":"wBAyBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js new file mode 100644 index 0000000000000000000000000000000000000000..2cef238841efc021bbb136d62b9d4fe29997a25e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js @@ -0,0 +1,98 @@ +/** + * @fileoverview Validate spacing before closing bracket in JSX. + * @author ryym + * @deprecated + */ + +'use strict'; + +const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket'); +const docsUrl = require('../util/docsUrl'); +const log = require('../util/log'); +const report = require('../util/report'); +const getSourceCode = require('../util/eslint').getSourceCode; + +let isWarnedForDeprecation = false; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noSpaceBeforeClose: 'A space is forbidden before closing bracket', + needSpaceBeforeClose: 'A space is required before closing bracket', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + deprecated: true, + replacedBy: ['jsx-tag-spacing'], + docs: { + description: 'Enforce spacing before closing bracket in JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-space-before-closing'), + }, + fixable: 'code', + + messages, + + schema: [{ + enum: ['always', 'never'], + }], + }, + + create(context) { + const configuration = context.options[0] || 'always'; + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXOpeningElement(node) { + if (!node.selfClosing) { + return; + } + + const sourceCode = getSourceCode(context); + + const leftToken = getTokenBeforeClosingBracket(node); + const closingSlash = /** @type {import('eslint').AST.Token} */ (sourceCode.getTokenAfter(leftToken)); + + if (leftToken.loc.end.line !== closingSlash.loc.start.line) { + return; + } + + if (configuration === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) { + report(context, messages.needSpaceBeforeClose, 'needSpaceBeforeClose', { + loc: closingSlash.loc.start, + fix(fixer) { + return fixer.insertTextBefore(closingSlash, ' '); + }, + }); + } else if (configuration === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) { + report(context, messages.noSpaceBeforeClose, 'noSpaceBeforeClose', { + loc: closingSlash.loc.start, + fix(fixer) { + const previousToken = sourceCode.getTokenBefore(closingSlash); + return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]); + }, + }); + } + }, + + Program() { + if (isWarnedForDeprecation) { + return; + } + + log('The react/jsx-space-before-closing rule is deprecated. ' + + 'Please use the react/jsx-tag-spacing rule with the ' + + '"beforeSelfClosing" option instead.'); + isWarnedForDeprecation = true; + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bfcf83cab701cf5eb692a864e88cd3ea621e4d9 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-tag-spacing.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..a6f1d0c83fde700412728eec0a16439469199b2f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-tag-spacing.d.ts","sourceRoot":"","sources":["jsx-tag-spacing.js"],"names":[],"mappings":"wBAqQW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js new file mode 100644 index 0000000000000000000000000000000000000000..ac7c6932789f0198f4cc2a276b351b4e4aed5ded --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js @@ -0,0 +1,328 @@ +/** + * @fileoverview Validates whitespace in and around the JSX opening and closing brackets + * @author Diogo Franco (Kovensky) + */ + +'use strict'; + +const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const eslintUtil = require('../util/eslint'); + +const getFirstTokens = eslintUtil.getFirstTokens; +const getSourceCode = eslintUtil.getSourceCode; + +const messages = { + selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`', + selfCloseSlashNeedSpace: 'Whitespace is required between `/` and `>`; write `/ >`', + closeSlashNoSpace: 'Whitespace is forbidden between `<` and `/`; write ` 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: { + docs: { + description: 'Disallow variables used in JSX to be incorrectly marked as unused', + category: 'Best Practices', + recommended: true, + url: docsUrl('jsx-uses-vars'), + }, + schema: [], + }, + + create(context) { + return { + JSXOpeningElement(node) { + let name; + if (node.name.namespace) { + // + return; + } + if (node.name.name) { + // + name = node.name.name; + // Exclude lowercase tag names like
+ if (isTagName(name)) { + return; + } + } else if (node.name.object) { + // + let parent = node.name.object; + while (parent.object) { + parent = parent.object; + } + name = parent.name; + } else { + return; + } + + markVariableAsUsed(name, node, context); + }, + + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b7b8013fa02f0843aa15398bb8da30734e421e8 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=jsx-wrap-multilines.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..abab2ad088f38cc890a9839743e1b34edd967055 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"jsx-wrap-multilines.d.ts","sourceRoot":"","sources":["jsx-wrap-multilines.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js new file mode 100644 index 0000000000000000000000000000000000000000..6f5ad50bbf5c162122a079fbd6f353ad58b776ae --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js @@ -0,0 +1,275 @@ +/** + * @fileoverview Prevent missing parentheses around multilines JSX + * @author Yannick Croissant + */ + +'use strict'; + +const has = require('hasown'); +const docsUrl = require('../util/docsUrl'); +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 +// ------------------------------------------------------------------------------ + +const DEFAULTS = { + declaration: 'parens', + assignment: 'parens', + return: 'parens', + arrow: 'parens', + condition: 'ignore', + logical: 'ignore', + prop: 'ignore', +}; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + missingParens: 'Missing parentheses around multilines JSX', + extraParens: 'Expected no parentheses around multilines JSX', + parensOnNewLines: 'Parentheses around JSX should be on separate lines', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow missing parentheses around multiline JSX', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('jsx-wrap-multilines'), + }, + fixable: 'code', + + messages, + + schema: [{ + type: 'object', + // true/false are for backwards compatibility + properties: { + declaration: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + assignment: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + return: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + arrow: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + condition: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + logical: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + prop: { + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + function getOption(type) { + const userOptions = context.options[0] || {}; + if (has(userOptions, type)) { + return userOptions[type]; + } + return DEFAULTS[type]; + } + + function isEnabled(type) { + const option = getOption(type); + return option && option !== 'ignore'; + } + + function needsOpeningNewLine(node) { + const previousToken = getSourceCode(context).getTokenBefore(node); + + if (!isParenthesized(context, node)) { + return false; + } + + if (previousToken.loc.end.line === node.loc.start.line) { + return true; + } + + return false; + } + + function needsClosingNewLine(node) { + const nextToken = getSourceCode(context).getTokenAfter(node); + + if (!isParenthesized(context, node)) { + return false; + } + + if (node.loc.end.line === nextToken.loc.end.line) { + return true; + } + + return false; + } + + function isMultilines(node) { + return node.loc.start.line !== node.loc.end.line; + } + + function report(node, messageId, fix) { + reportC(context, messages[messageId], messageId, { + node, + fix, + }); + } + + function trimTokenBeforeNewline(node, tokenBefore) { + // if the token before the jsx is a bracket or curly brace + // we don't want a space between the opening parentheses and the multiline jsx + const isBracket = tokenBefore.value === '{' || tokenBefore.value === '['; + return `${tokenBefore.value.trim()}${isBracket ? '' : ' '}`; + } + + function check(node, type) { + if (!node || !jsxUtil.isJSX(node)) { + return; + } + + const sourceCode = getSourceCode(context); + const option = getOption(type); + + if ((option === true || option === 'parens') && !isParenthesized(context, node) && isMultilines(node)) { + report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(${getText(context, node)})`)); + } + + if (option === 'parens-new-line' && isMultilines(node)) { + if (!isParenthesized(context, node)) { + const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true }); + const tokenAfter = sourceCode.getTokenAfter(node, { includeComments: true }); + const start = node.loc.start; + if (tokenBefore.loc.end.line < start.line) { + // Strip newline after operator if parens newline is specified + report( + node, + '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) : ''}${getText(context, node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})` + ) + ); + } else { + 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 = getText(context, node); + let fixed = text; + if (needsOpening) { + fixed = `\n${fixed}`; + } + if (needsClosing) { + fixed = `${fixed}\n`; + } + return fixer.replaceText(node, fixed); + }); + } + } + } + + if (option === 'never' && isParenthesized(context, node)) { + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + report(node, 'extraParens', (fixer) => fixer.replaceTextRange( + [tokenBefore.range[0], tokenAfter.range[1]], + getText(context, node) + )); + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + + VariableDeclarator(node) { + const type = 'declaration'; + if (!isEnabled(type)) { + return; + } + if (!isEnabled('condition') && node.init && node.init.type === 'ConditionalExpression') { + check(node.init.consequent, type); + check(node.init.alternate, type); + return; + } + check(node.init, type); + }, + + AssignmentExpression(node) { + const type = 'assignment'; + if (!isEnabled(type)) { + return; + } + if (!isEnabled('condition') && node.right.type === 'ConditionalExpression') { + check(node.right.consequent, type); + check(node.right.alternate, type); + return; + } + check(node.right, type); + }, + + ReturnStatement(node) { + const type = 'return'; + if (isEnabled(type)) { + check(node.argument, type); + } + }, + + 'ArrowFunctionExpression:exit': (node) => { + const arrowBody = node.body; + const type = 'arrow'; + + if (isEnabled(type) && arrowBody.type !== 'BlockStatement') { + check(arrowBody, type); + } + }, + + ConditionalExpression(node) { + const type = 'condition'; + if (isEnabled(type)) { + check(node.consequent, type); + check(node.alternate, type); + } + }, + + LogicalExpression(node) { + const type = 'logical'; + if (isEnabled(type)) { + check(node.right, type); + } + }, + + JSXAttribute(node) { + const type = 'prop'; + if (isEnabled(type) && node.value && node.value.type === 'JSXExpressionContainer') { + check(node.value.expression, type); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..68bb9eb6c0bdbc469601aa3e5ee98b161f36f6df --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-access-state-in-setstate.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..af8e61bbb7f24725856dbc0cdbf2fa5dfb9d39c9 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-access-state-in-setstate.d.ts","sourceRoot":"","sources":["no-access-state-in-setstate.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js new file mode 100644 index 0000000000000000000000000000000000000000..1b23dd88eee8d853ef171776e0c13e28d71e09a7 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js @@ -0,0 +1,211 @@ +/** + * @fileoverview Prevent usage of this.state within setState + * @author Rolf Erik Lekang, Jørgen Aaberg + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const report = require('../util/report'); +const getScope = require('../util/eslint').getScope; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + useCallback: 'Use callback in setState when referencing the previous state.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow when this.state is accessed within setState', + category: 'Possible Errors', + recommended: false, + url: docsUrl('no-access-state-in-setstate'), + }, + + messages, + }, + + create(context) { + function isSetStateCall(node) { + return astUtil.isCallExpression(node) + && node.callee.property + && node.callee.property.name === 'setState' + && node.callee.object.type === 'ThisExpression'; + } + + function isFirstArgumentInSetStateCall(current, node) { + if (!isSetStateCall(current)) { + return false; + } + while (node && node.parent !== current) { + node = node.parent; + } + return current.arguments[0] === node; + } + + /** + * @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 + // or that are calling another method or function using this.state + const methods = []; + // The vars array contains all variables that contains this.state + const vars = []; + return { + CallExpression(node) { + if (!isClassComponent(node)) { + return; + } + // Appends all the methods that are calling another + // method containing this.state to the methods array + methods.forEach((method) => { + if ('name' in node.callee && node.callee.name === method.methodName) { + let current = node.parent; + while (current.type !== 'Program') { + if (current.type === 'MethodDefinition') { + methods.push({ + methodName: 'name' in current.key ? current.key.name : undefined, + node: method.node, + }); + break; + } + current = current.parent; + } + } + }); + + // Finding all CallExpressions that is inside a setState + // to further check if they contains this.state + let current = node.parent; + while (current.type !== 'Program') { + if (isFirstArgumentInSetStateCall(current, node)) { + const methodName = 'name' in node.callee ? node.callee.name : undefined; + methods.forEach((method) => { + if (method.methodName === methodName) { + report(context, messages.useCallback, 'useCallback', { + node: method.node, + }); + } + }); + + break; + } + current = current.parent; + } + }, + + MemberExpression(node) { + if ( + 'name' in node.property + && node.property.name === 'state' + && node.object.type === 'ThisExpression' + && isClassComponent(node) + ) { + /** @type {import('eslint').Rule.Node} */ + let current = node; + while (current.type !== 'Program') { + // Reporting if this.state is directly within this.setState + if (isFirstArgumentInSetStateCall(current, node)) { + report(context, messages.useCallback, 'useCallback', { + node, + }); + break; + } + + // Storing all functions and methods that contains this.state + if (current.type === 'MethodDefinition') { + methods.push({ + methodName: 'name' in current.key ? current.key.name : undefined, + node, + }); + break; + } else if ( + current.type === 'FunctionExpression' + && 'key' in current.parent + && current.parent.key + ) { + methods.push({ + methodName: 'name' in current.parent.key ? current.parent.key.name : undefined, + node, + }); + break; + } + + // Storing all variables containing this.state + if (current.type === 'VariableDeclarator') { + vars.push({ + node, + scope: getScope(context, node), + variableName: 'name' in current.id ? current.id.name : undefined, + }); + break; + } + + current = current.parent; + } + } + }, + + Identifier(node) { + // Checks if the identifier is a variable within an object + /** @type {import('eslint').Rule.Node} */ + let current = node; + while (current.parent.type === 'BinaryExpression') { + current = current.parent; + } + if ( + ('value' in current.parent && current.parent.value === current) + || ('object' in current.parent && current.parent.object === current) + ) { + while (current.type !== 'Program') { + if (isFirstArgumentInSetStateCall(current, node)) { + vars + .filter((v) => v.scope === getScope(context, node) && v.variableName === node.name) + .forEach((v) => { + report(context, messages.useCallback, 'useCallback', { + node: v.node, + }); + }); + } + current = current.parent; + } + } + }, + + ObjectPattern(node) { + const isDerivedFromThis = 'init' in node.parent && node.parent.init && node.parent.init.type === 'ThisExpression'; + node.properties.forEach((property) => { + if ( + property + && 'key' in property + && property.key + && 'name' in property.key + && property.key.name === 'state' + && isDerivedFromThis + ) { + vars.push({ + node: property.key, + scope: getScope(context, node), + variableName: property.key.name, + }); + } + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..50e434940a6010c3193ec0b286ccbfa6dee41e04 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-adjacent-inline-elements.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..745cc94765d87334c08bfc34f6dd15740b67a29a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-adjacent-inline-elements.d.ts","sourceRoot":"","sources":["no-adjacent-inline-elements.js"],"names":[],"mappings":"wBA+EW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..8110d8563b72bbeacb38ab25446b3c0fbc9644ce --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js @@ -0,0 +1,127 @@ +/** + * @fileoverview Prevent adjacent inline elements not separated by whitespace. + * @author Sean Hayes + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); +const astUtil = require('../util/ast'); + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements +const inlineNames = [ + 'a', + 'b', + 'big', + 'i', + 'small', + 'tt', + 'abbr', + 'acronym', + 'cite', + 'code', + 'dfn', + 'em', + 'kbd', + 'strong', + 'samp', + 'time', + 'var', + 'bdo', + 'br', + 'img', + 'map', + 'object', + 'q', + 'script', + 'span', + 'sub', + 'sup', + 'button', + 'input', + 'label', + 'select', + 'textarea', +]; +// Note: raw   will be transformed into \u00a0. +const whitespaceRegex = /(?:^\s|\s$)/; + +function isInline(node) { + if (node.type === 'Literal') { + // Regular whitespace will be removed. + const value = node.value; + // To properly separate inline elements, each end of the literal will need + // whitespace. + return !whitespaceRegex.test(value); + } + if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) { + return true; + } + if (astUtil.isCallExpression(node) && inlineNames.indexOf(node.arguments[0].value) > -1) { + return true; + } + return false; +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + inlineElement: 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow adjacent inline elements not separated by whitespace.', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-adjacent-inline-elements'), + }, + schema: [], + + messages, + }, + create(context) { + function validate(node, children) { + let currentIsInline = false; + let previousIsInline = false; + if (!children) { + return; + } + for (let i = 0; i < children.length; i++) { + currentIsInline = isInline(children[i]); + if (previousIsInline && currentIsInline) { + report(context, messages.inlineElement, 'inlineElement', { + node, + }); + return; + } + previousIsInline = currentIsInline; + } + } + return { + JSXElement(node) { + validate(node, node.children); + }, + CallExpression(node) { + if (!isCreateElement(context, node)) { + return; + } + if (node.arguments.length < 2 || !node.arguments[2]) { + return; + } + const children = 'elements' in node.arguments[2] ? node.arguments[2].elements : undefined; + validate(node, children); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7487572540920aa6bf7f486ca76d902192bc265b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-array-index-key.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..b64273ac0146eb197e934096f16329a54d774938 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-array-index-key.d.ts","sourceRoot":"","sources":["no-array-index-key.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js new file mode 100644 index 0000000000000000000000000000000000000000..90381b3a08549532602dcc31af33325edd67c54c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js @@ -0,0 +1,293 @@ +/** + * @fileoverview Prevent usage of Array index in keys + * @author Joe Lencioni + */ + +'use strict'; + +const has = require('hasown'); +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const pragma = require('../util/pragma'); +const report = require('../util/report'); +const variableUtil = require('../util/variable'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function isCreateCloneElement(node, context) { + if (!node) { + return false; + } + + if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') { + return node.object + && node.object.name === pragma.getFromContext(context) + && ['createElement', 'cloneElement'].indexOf(node.property.name) !== -1; + } + + if (node.type === 'Identifier') { + const variable = variableUtil.findVariableByName(context, node, node.name); + if (variable && variable.type === 'ImportSpecifier') { + return variable.parent.source.value === 'react'; + } + } + + return false; +} + +const messages = { + noArrayIndex: 'Do not use Array index in keys', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of Array index in keys', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-array-index-key'), + }, + + messages, + + schema: [], + }, + + create(context) { + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + const indexParamNames = []; + const iteratorFunctionsToIndexParamPosition = { + every: 1, + filter: 1, + find: 1, + findIndex: 1, + flatMap: 1, + forEach: 1, + map: 1, + reduce: 2, + reduceRight: 2, + some: 1, + }; + + function isArrayIndex(node) { + return node.type === 'Identifier' + && indexParamNames.indexOf(node.name) !== -1; + } + + function isUsingReactChildren(node) { + const callee = node.callee; + if ( + !callee + || !callee.property + || !callee.object + ) { + return null; + } + + const isReactChildMethod = ['map', 'forEach'].indexOf(callee.property.name) > -1; + if (!isReactChildMethod) { + return null; + } + + const obj = callee.object; + if (obj && obj.name === 'Children') { + return true; + } + if (obj && obj.object && obj.object.name === pragma.getFromContext(context)) { + return true; + } + + return false; + } + + function getMapIndexParamName(node) { + const callee = node.callee; + if (callee.type !== 'MemberExpression' && callee.type !== 'OptionalMemberExpression') { + return null; + } + if (callee.property.type !== 'Identifier') { + return null; + } + if (!has(iteratorFunctionsToIndexParamPosition, callee.property.name)) { + return null; + } + + const name = /** @type {keyof iteratorFunctionsToIndexParamPosition} */ (callee.property.name); + + const callbackArg = isUsingReactChildren(node) + ? node.arguments[1] + : node.arguments[0]; + + if (!callbackArg) { + return null; + } + + if (!astUtil.isFunctionLikeExpression(callbackArg)) { + return null; + } + + const params = callbackArg.params; + + const indexParamPosition = iteratorFunctionsToIndexParamPosition[name]; + if (params.length < indexParamPosition + 1) { + return null; + } + + return params[indexParamPosition].name; + } + + function getIdentifiersFromBinaryExpression(side) { + if (side.type === 'Identifier') { + return side; + } + + if (side.type === 'BinaryExpression') { + // recurse + const left = getIdentifiersFromBinaryExpression(side.left); + const right = getIdentifiersFromBinaryExpression(side.right); + return [].concat(left, right).filter(Boolean); + } + + return null; + } + + function checkPropValue(node) { + if (isArrayIndex(node)) { + // key={bar} + report(context, messages.noArrayIndex, 'noArrayIndex', { + node, + }); + return; + } + + if (node.type === 'TemplateLiteral') { + // key={`foo-${bar}`} + node.expressions.filter(isArrayIndex).forEach(() => { + report(context, messages.noArrayIndex, 'noArrayIndex', { + node, + }); + }); + + return; + } + + if (node.type === 'BinaryExpression') { + // key={'foo' + bar} + const identifiers = getIdentifiersFromBinaryExpression(node); + + identifiers.filter(isArrayIndex).forEach(() => { + report(context, messages.noArrayIndex, 'noArrayIndex', { + node, + }); + }); + + return; + } + + if ( + astUtil.isCallExpression(node) + && node.callee + && node.callee.type === 'MemberExpression' + && node.callee.object + && isArrayIndex(node.callee.object) + && node.callee.property + && node.callee.property.type === 'Identifier' + && node.callee.property.name === 'toString' + ) { + // key={bar.toString()} + report(context, messages.noArrayIndex, 'noArrayIndex', { + node, + }); + return; + } + + if ( + astUtil.isCallExpression(node) + && node.callee + && node.callee.type === 'Identifier' + && node.callee.name === 'String' + && Array.isArray(node.arguments) + && node.arguments.length > 0 + && isArrayIndex(node.arguments[0]) + ) { + // key={String(bar)} + report(context, messages.noArrayIndex, 'noArrayIndex', { + node: node.arguments[0], + }); + } + } + + function popIndex(node) { + const mapIndexParamName = getMapIndexParamName(node); + if (!mapIndexParamName) { + return; + } + + indexParamNames.pop(); + } + + return { + 'CallExpression, OptionalCallExpression'(node) { + if (isCreateCloneElement(node.callee, context) && node.arguments.length > 1) { + // React.createElement + if (!indexParamNames.length) { + return; + } + + const props = node.arguments[1]; + + if (props.type !== 'ObjectExpression') { + return; + } + + props.properties.forEach((prop) => { + if (!prop.key || prop.key.name !== 'key') { + // { ...foo } + // { foo: bar } + return; + } + + checkPropValue(prop.value); + }); + + return; + } + + const mapIndexParamName = getMapIndexParamName(node); + if (!mapIndexParamName) { + return; + } + + indexParamNames.push(mapIndexParamName); + }, + + JSXAttribute(node) { + if (node.name.name !== 'key') { + // foo={bar} + return; + } + + if (!indexParamNames.length) { + // Not inside a call expression that we think has an index param. + return; + } + + const value = node.value; + if (!value || value.type !== 'JSXExpressionContainer') { + // key='foo' or just simply 'key' + return; + } + + checkPropValue(value.expression); + }, + + 'CallExpression:exit': popIndex, + 'OptionalCallExpression:exit': popIndex, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3065993062254468189f7ca3d184d173fc15588 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-arrow-function-lifecycle.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..707d2b3e0add90163f21995bc46933ad5bede2e5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-arrow-function-lifecycle.d.ts","sourceRoot":"","sources":["no-arrow-function-lifecycle.js"],"names":[],"mappings":"wBAsCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js new file mode 100644 index 0000000000000000000000000000000000000000..e56cb78601dd1e2528f1dc11764774bdf26e3ed4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js @@ -0,0 +1,149 @@ +/** + * @fileoverview Lifecycle methods should be methods on the prototype, not class fields + * @author Tan Nguyen + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const lifecycleMethods = require('../util/lifecycleMethods'); +const report = require('../util/report'); +const eslintUtil = require('../util/eslint'); + +const getSourceCode = eslintUtil.getSourceCode; +const getText = eslintUtil.getText; + +function getRuleText(node) { + const params = node.value.params.map((p) => p.name); + + if (node.type === 'Property') { + return `: function(${params.join(', ')}) `; + } + + if (node.type === 'ClassProperty' || node.type === 'PropertyDefinition') { + return `(${params.join(', ')}) `; + } + + return null; +} + +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: { + description: 'Lifecycle methods should be methods on the prototype, not class fields', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-arrow-function-lifecycle'), + }, + messages, + schema: [], + fixable: 'code', + }, + + create: Components.detect((context, components) => { + /** + * @param {Array} properties list of component properties + */ + function reportNoArrowFunctionLifecycle(properties) { + properties.forEach((node) => { + if (!node || !node.value) { + return; + } + + const propertyName = astUtil.getPropertyName(node); + const nodeType = node.value.type; + const isLifecycleMethod = ( + node.static && !componentUtil.isES5Component(node, context) + ? lifecycleMethods.static + : lifecycleMethods.instance + ).indexOf(propertyName) > -1; + + if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) { + const body = node.value.body; + const isBlockBody = body.type === 'BlockStatement'; + const sourceCode = getSourceCode(context); + + let nextComment = []; + let previousComment = []; + let bodyRange; + if (!isBlockBody) { + const previousToken = sourceCode.getTokenBefore(body); + + if (sourceCode.getCommentsBefore) { + // eslint >=4.x + previousComment = sourceCode.getCommentsBefore(body); + } else { + // eslint 3.x + const potentialComment = sourceCode.getTokenBefore(body, { includeComments: true }); + previousComment = previousToken === potentialComment ? [] : [potentialComment]; + } + + if (sourceCode.getCommentsAfter) { + // eslint >=4.x + nextComment = sourceCode.getCommentsAfter(body); + } else { + // eslint 3.x + const potentialComment = sourceCode.getTokenAfter(body, { includeComments: true }); + const nextToken = sourceCode.getTokenAfter(body); + nextComment = nextToken === potentialComment ? [] : [potentialComment]; + } + bodyRange = [ + (previousComment.length > 0 ? previousComment[0] : body).range[0], + (nextComment.length > 0 ? nextComment[nextComment.length - 1] : body).range[1] + + (node.value.body.type === 'ObjectExpression' ? 1 : 0), // to account for a wrapped end paren + ]; + } + const headRange = [ + node.key.range[1], + (previousComment.length > 0 ? previousComment[0] : body).range[0], + ]; + const hasSemi = node.value.expression && getText(context, node).slice(node.value.range[1] - node.range[0]) === ';'; + + report( + context, + messages.lifecycle, + 'lifecycle', + { + node, + data: { + propertyName, + }, + fix(fixer) { + if (!sourceCode.getCommentsAfter) { + // eslint 3.x + return isBlockBody && fixer.replaceTextRange(headRange, getRuleText(node)); + } + return [].concat( + fixer.replaceTextRange(headRange, getRuleText(node)), + isBlockBody ? [] : fixer.replaceTextRange( + [bodyRange[0], bodyRange[1] + (hasSemi ? 1 : 0)], + `{ return ${previousComment.map((x) => getText(context, x)).join('')}${getText(context, body)}${nextComment.map((x) => getText(context, x)).join('')}; }` + ) + ); + }, + } + ); + } + }); + } + + return { + 'Program:exit'() { + values(components.list()).forEach((component) => { + const properties = astUtil.getComponentProperties(component.node); + reportNoArrowFunctionLifecycle(properties); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e1dee067dbb5acef10036fe40e73c2de227f09d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-children-prop.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..d656c9331bd035d393d1f8e1df6978a83aba8ac6 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-children-prop.d.ts","sourceRoot":"","sources":["no-children-prop.js"],"names":[],"mappings":"wBAuCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js new file mode 100644 index 0000000000000000000000000000000000000000..84ccbbf3a6696b94a5791e5dc4839e4b54d274b5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js @@ -0,0 +1,125 @@ +/** + * @fileoverview Prevent passing of children as props + * @author Benjamin Stepp + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * Checks if the node is a createElement call with a props literal. + * @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 with a props + * object literal, False if not. +*/ +function isCreateElementWithProps(node, context) { + return isCreateElement(context, node) + && node.arguments.length > 1 + && node.arguments[1].type === 'ObjectExpression'; +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + nestChildren: 'Do not pass children as props. Instead, nest children between the opening and closing tags.', + passChildrenAsArgs: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.', + nestFunction: 'Do not nest a function between the opening and closing tags. Instead, pass it as a prop.', + passFunctionAsArgs: 'Do not pass a function as an additional argument to React.createElement. Instead, pass it as a prop.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow passing of children as props', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-children-prop'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + allowFunctions: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }], + }, + create(context) { + const configuration = context.options[0] || {}; + + function isFunction(node) { + return configuration.allowFunctions && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression'); + } + + return { + JSXAttribute(node) { + if (node.name.name !== 'children') { + return; + } + + const value = node.value; + if (value && value.type === 'JSXExpressionContainer' && isFunction(value.expression)) { + return; + } + + report(context, messages.nestChildren, 'nestChildren', { + node, + }); + }, + CallExpression(node) { + if (!isCreateElementWithProps(node, context)) { + return; + } + + const props = 'properties' in node.arguments[1] ? node.arguments[1].properties : undefined; + const childrenProp = props.find((prop) => ( + 'key' in prop + && prop.key + && 'name' in prop.key + && prop.key.name === 'children' + )); + + if (childrenProp) { + if ('value' in childrenProp && childrenProp.value && !isFunction(childrenProp.value)) { + report(context, messages.passChildrenAsArgs, 'passChildrenAsArgs', { + node, + }); + } + } else if (node.arguments.length === 3) { + const children = node.arguments[2]; + if (isFunction(children)) { + report(context, messages.passFunctionAsArgs, 'passFunctionAsArgs', { + node, + }); + } + } + }, + JSXElement(node) { + const children = node.children; + if (children && children.length === 1 && children[0].type === 'JSXExpressionContainer') { + if (isFunction(children[0].expression)) { + report(context, messages.nestFunction, 'nestFunction', { + node, + }); + } + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..975773dd81de6d94cd05604cb625e9815ca03d43 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-danger-with-children.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..718406f27e21cc68f5c1b07c1a14b1cf2e768077 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-danger-with-children.d.ts","sourceRoot":"","sources":["no-danger-with-children.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js new file mode 100644 index 0000000000000000000000000000000000000000..3b01998502e7a6d5b4697b563cb6be571551a15c --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js @@ -0,0 +1,157 @@ +/** + * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML + * @author David Petersen + */ + +'use strict'; + +const variableUtil = require('../util/variable'); +const jsxUtil = require('../util/jsx'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ +const messages = { + dangerWithChildren: 'Only set one of `children` or `props.dangerouslySetInnerHTML`', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow when a DOM element is using both children and dangerouslySetInnerHTML', + category: 'Possible Errors', + recommended: true, + url: docsUrl('no-danger-with-children'), + }, + + messages, + + schema: [], // no options + }, + create(context) { + function findSpreadVariable(node, name) { + return variableUtil.getVariableFromContext(context, node, name); + } + /** + * Takes a ObjectExpression and returns the value of the prop if it has it + * @param {object} node - ObjectExpression node + * @param {string} propName - name of the prop to look for + * @param {string[]} seenProps + * @returns {object | boolean} + */ + function findObjectProp(node, propName, seenProps) { + if (!node.properties) { + return false; + } + return node.properties.find((prop) => { + if (prop.type === 'Property') { + return prop.key.name === propName; + } + if (prop.type === 'ExperimentalSpreadProperty' || prop.type === 'SpreadElement') { + 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; + } + const newSeenProps = seenProps.concat(prop.argument.name || []); + return findObjectProp(variable.defs[0].node.init, propName, newSeenProps); + } + } + return false; + }); + } + + /** + * Takes a JSXElement and returns the value of the prop if it has it + * @param {object} node - JSXElement node + * @param {string} propName - name of the prop to look for + * @returns {object | boolean} + */ + function findJsxProp(node, propName) { + const attributes = node.openingElement.attributes; + return attributes.find((attribute) => { + if (attribute.type === 'JSXSpreadAttribute') { + 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, []); + } + } + return attribute.name && attribute.name.name === propName; + }); + } + + /** + * Checks to see if a node is a line break + * @param {ASTNode} node The AST node being checked + * @returns {boolean} True if node is a line break, false if not + */ + function isLineBreak(node) { + const isLiteral = node.type === 'Literal' || node.type === 'JSXText'; + const isMultiline = node.loc.start.line !== node.loc.end.line; + const isWhiteSpaces = jsxUtil.isWhiteSpaces(node.value); + + return isLiteral && isMultiline && isWhiteSpaces; + } + + return { + JSXElement(node) { + let hasChildren = false; + + if (node.children.length && !isLineBreak(node.children[0])) { + hasChildren = true; + } else if (findJsxProp(node, 'children')) { + hasChildren = true; + } + + if ( + node.openingElement.attributes + && hasChildren + && findJsxProp(node, 'dangerouslySetInnerHTML') + ) { + report(context, messages.dangerWithChildren, 'dangerWithChildren', { + node, + }); + } + }, + CallExpression(node) { + if ( + node.callee + && node.callee.type === 'MemberExpression' + && 'name' in node.callee.property + && node.callee.property.name === 'createElement' + && node.arguments.length > 1 + ) { + let hasChildren = false; + + let props = node.arguments[1]; + + if (props.type === 'Identifier') { + const variable = variableUtil.getVariableFromContext(context, node, props.name); + if (variable && variable.defs.length && variable.defs[0].node.init) { + props = variable.defs[0].node.init; + } + } + + const dangerously = findObjectProp(props, 'dangerouslySetInnerHTML', []); + + if (node.arguments.length === 2) { + if (findObjectProp(props, 'children', [])) { + hasChildren = true; + } + } else { + hasChildren = true; + } + + if (dangerously && hasChildren) { + report(context, messages.dangerWithChildren, 'dangerWithChildren', { + node, + }); + } + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3953511f0e00f88ffb97df3290b03bd3e1cdb4b --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-danger.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..30086713a079224efbf3dbae1a99424ac03f75ef --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-danger.d.ts","sourceRoot":"","sources":["no-danger.js"],"names":[],"mappings":"wBA8CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-danger.js b/node_modules/eslint-plugin-react/lib/rules/no-danger.js new file mode 100644 index 0000000000000000000000000000000000000000..54dbed49cffd1912e2f99a141ff37515b8f1f7c2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-danger.js @@ -0,0 +1,96 @@ +/** + * @fileoverview Prevent usage of dangerous JSX props + * @author Scott Andrews + */ + +'use strict'; + +const has = require('hasown'); +const fromEntries = require('object.fromentries/polyfill')(); +const minimatch = require('minimatch'); + +const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DANGEROUS_PROPERTY_NAMES = [ + 'dangerouslySetInnerHTML', +]; + +const DANGEROUS_PROPERTIES = fromEntries(DANGEROUS_PROPERTY_NAMES.map((prop) => [prop, prop])); + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * Checks if a JSX attribute is dangerous. + * @param {string} name - Name of the attribute to check. + * @returns {boolean} Whether or not the attribute is dangerous. + */ +function isDangerous(name) { + return has(DANGEROUS_PROPERTIES, name); +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + dangerousProp: 'Dangerous property \'{{name}}\' found', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of dangerous JSX properties', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-danger'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + customComponentNames: { + items: { + type: 'string', + }, + minItems: 0, + type: 'array', + uniqueItems: true, + }, + }, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const customComponentNames = configuration.customComponentNames || []; + + return { + JSXAttribute(node) { + const nodeName = node.parent.name; + const functionName = nodeName.name || `${nodeName.object.name}.${nodeName.property.name}`; + + const enableCheckingCustomComponent = customComponentNames.some((name) => minimatch(functionName, name)); + + if ((enableCheckingCustomComponent || jsxUtil.isDOMComponent(node.parent)) && isDangerous(node.name.name)) { + report(context, messages.dangerousProp, 'dangerousProp', { + node, + data: { + name: node.name.name, + }, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6820ee94b1dd8889242ac40a6c78d0f0f1a8f223 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-deprecated.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..20b5970860774966349c181454b7b17bd44258a4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-deprecated.d.ts","sourceRoot":"","sources":["no-deprecated.js"],"names":[],"mappings":"wBAqHW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js new file mode 100644 index 0000000000000000000000000000000000000000..0c5931345f87841e43f0d28bf6f11e67e4842cef --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js @@ -0,0 +1,271 @@ +/** + * @fileoverview Prevent usage of deprecated methods + * @author Yannick Croissant + * @author Scott Feeney + * @author Sergei Startsev + */ + +'use strict'; + +const entries = require('object.entries'); +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const pragmaUtil = require('../util/pragma'); +const testReactVersion = require('../util/version').testReactVersion; +const report = require('../util/report'); +const getText = require('../util/eslint').getText; + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const MODULES = { + react: ['React'], + 'react-addons-perf': ['ReactPerf', 'Perf'], + 'react-dom': ['ReactDOM'], + 'react-dom/server': ['ReactDOMServer'], +}; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function getDeprecated(pragma) { + const deprecated = {}; + // 0.12.0 + deprecated[`${pragma}.renderComponent`] = ['0.12.0', `${pragma}.render`]; + deprecated[`${pragma}.renderComponentToString`] = ['0.12.0', `${pragma}.renderToString`]; + deprecated[`${pragma}.renderComponentToStaticMarkup`] = ['0.12.0', `${pragma}.renderToStaticMarkup`]; + deprecated[`${pragma}.isValidComponent`] = ['0.12.0', `${pragma}.isValidElement`]; + deprecated[`${pragma}.PropTypes.component`] = ['0.12.0', `${pragma}.PropTypes.element`]; + deprecated[`${pragma}.PropTypes.renderable`] = ['0.12.0', `${pragma}.PropTypes.node`]; + deprecated[`${pragma}.isValidClass`] = ['0.12.0']; + deprecated['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})']; + // 0.13.0 + deprecated[`${pragma}.addons.classSet`] = ['0.13.0', 'the npm module classnames']; + deprecated[`${pragma}.addons.cloneWithProps`] = ['0.13.0', `${pragma}.cloneElement`]; + // 0.14.0 + deprecated[`${pragma}.render`] = ['0.14.0', 'ReactDOM.render']; + deprecated[`${pragma}.unmountComponentAtNode`] = ['0.14.0', 'ReactDOM.unmountComponentAtNode']; + deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReactDOM.findDOMNode']; + deprecated[`${pragma}.renderToString`] = ['0.14.0', 'ReactDOMServer.renderToString']; + deprecated[`${pragma}.renderToStaticMarkup`] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup']; + // 15.0.0 + deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0']; + deprecated['ReactPerf.printDOM'] = ['15.0.0', 'ReactPerf.printOperations']; + deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations']; + deprecated['ReactPerf.getMeasurementsSummaryMap'] = ['15.0.0', 'ReactPerf.getWasted']; + deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted']; + // 15.5.0 + deprecated[`${pragma}.createClass`] = ['15.5.0', 'the npm module create-react-class']; + deprecated[`${pragma}.addons.TestUtils`] = ['15.5.0', 'ReactDOM.TestUtils']; + deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types']; + // 15.6.0 + deprecated[`${pragma}.DOM`] = ['15.6.0', 'the npm module react-dom-factories']; + // 16.9.0 + // For now the following life-cycle methods are just legacy, not deprecated: + // `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate` + // https://github.com/yannickcr/eslint-plugin-react/pull/1750#issuecomment-425975934 + deprecated.componentWillMount = [ + '16.9.0', + 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount. ' + + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.', + ]; + deprecated.componentWillReceiveProps = [ + '16.9.0', + 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops. ' + + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.', + ]; + deprecated.componentWillUpdate = [ + '16.9.0', + 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate. ' + + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.', + ]; + // 18.0.0 + // https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations + deprecated['ReactDOM.render'] = [ + '18.0.0', + 'createRoot', + 'https://reactjs.org/link/switch-to-createroot', + ]; + deprecated['ReactDOM.hydrate'] = [ + '18.0.0', + 'hydrateRoot', + 'https://reactjs.org/link/switch-to-createroot', + ]; + deprecated['ReactDOM.unmountComponentAtNode'] = [ + '18.0.0', + 'root.unmount', + 'https://reactjs.org/link/switch-to-createroot', + ]; + deprecated['ReactDOMServer.renderToNodeStream'] = [ + '18.0.0', + 'renderToPipeableStream', + 'https://reactjs.org/docs/react-dom-server.html#rendertonodestream', + ]; + + return deprecated; +} + +const messages = { + deprecated: '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}{{refs}}', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of deprecated methods', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-deprecated'), + }, + + messages, + + schema: [], + }, + + create(context) { + const pragma = pragmaUtil.getFromContext(context); + const deprecated = getDeprecated(pragma); + + function isDeprecated(method) { + return ( + deprecated + && deprecated[method] + && deprecated[method][0] + && testReactVersion(context, `>= ${deprecated[method][0]}`) + ); + } + + function checkDeprecation(node, methodName, methodNode) { + if (!isDeprecated(methodName)) { + return; + } + const version = deprecated[methodName][0]; + const newMethod = deprecated[methodName][1]; + const refs = deprecated[methodName][2]; + report(context, messages.deprecated, 'deprecated', { + node: methodNode || node, + data: { + oldMethod: methodName, + version, + newMethod: newMethod ? `, use ${newMethod} instead` : '', + refs: refs ? `, see ${refs}` : '', + }, + }); + } + + function getReactModuleName(node) { + let moduleName = false; + if (!node.init) { + return false; + } + + entries(MODULES).some((entry) => { + const key = entry[0]; + const moduleNames = entry[1]; + if ( + node.init.arguments + && node.init.arguments.length > 0 + && node.init.arguments[0] + && key === node.init.arguments[0].value + ) { + moduleName = MODULES[key][0]; + } else { + moduleName = moduleNames.find((name) => name === node.init.name); + } + return moduleName; + }); + + return moduleName; + } + + /** + * Returns life cycle methods if available + * @param {ASTNode} node The AST node being checked. + * @returns {Array} The array of methods. + */ + function getLifeCycleMethods(node) { + const properties = astUtil.getComponentProperties(node); + return properties.map((property) => ({ + name: astUtil.getPropertyName(property), + node: astUtil.getPropertyNameNode(property), + })); + } + + /** + * Checks life cycle methods + * @param {ASTNode} node The AST node being checked. + */ + function checkLifeCycleMethods(node) { + if ( + componentUtil.isES5Component(node, context) + || componentUtil.isES6Component(node, context) + ) { + const methods = getLifeCycleMethods(node); + methods.forEach((method) => checkDeprecation(node, method.name, method.node)); + } + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + MemberExpression(node) { + checkDeprecation(node, getText(context, node)); + }, + + ImportDeclaration(node) { + const isReactImport = typeof MODULES[node.source.value] !== 'undefined'; + if (!isReactImport) { + return; + } + node.specifiers.filter(((s) => 'imported' in s && s.imported)).forEach((specifier) => { + // TODO, semver-major: remove `in` check as part of jsdoc->tsdoc migration + checkDeprecation(node, 'imported' in specifier && 'name' in specifier.imported && `${MODULES[node.source.value][0]}.${specifier.imported.name}`, specifier); + }); + }, + + VariableDeclarator(node) { + const reactModuleName = getReactModuleName(node); + const isRequire = node.init + && 'callee' in node.init + && node.init.callee + && 'name' in node.init.callee + && node.init.callee.name === 'require'; + const isReactRequire = node.init + && 'arguments' in node.init + && node.init.arguments + && node.init.arguments.length + && typeof MODULES['value' in node.init.arguments[0] ? node.init.arguments[0].value : undefined] !== 'undefined'; + const isDestructuring = node.id && node.id.type === 'ObjectPattern'; + + if ( + !(isDestructuring && reactModuleName) + && !(isDestructuring && isRequire && isReactRequire) + ) { + return; + } + + ('properties' in node.id ? node.id.properties : undefined).filter((p) => p.type !== 'RestElement' && p.key).forEach((property) => { + checkDeprecation( + node, + 'key' in property && 'name' in property.key && `${reactModuleName || pragma}.${property.key.name}`, + property + ); + }); + }, + + ClassDeclaration: checkLifeCycleMethods, + ClassExpression: checkLifeCycleMethods, + ObjectExpression: checkLifeCycleMethods, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a20eb747a1c20564465c2a0b4c349a7c32a79483 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-did-mount-set-state.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..e257767c2eeb2755a96d01ed0d253e72a8434e19 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-did-mount-set-state.d.ts","sourceRoot":"","sources":["no-did-mount-set-state.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js new file mode 100644 index 0000000000000000000000000000000000000000..ad70c3eb7be8c89ceabe7a0110b9b59f28c3e167 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js @@ -0,0 +1,11 @@ +/** + * @fileoverview Prevent usage of setState in componentDidMount + * @author Yannick Croissant + */ + +'use strict'; + +const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = makeNoMethodSetStateRule('componentDidMount'); diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d0884d5a7ff7b9f2a5eb620921f0bed272125e2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-did-update-set-state.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..97a150b763621c5f3d439ad1b0d7c773de101bab --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-did-update-set-state.d.ts","sourceRoot":"","sources":["no-did-update-set-state.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js new file mode 100644 index 0000000000000000000000000000000000000000..d297fb2648acd53ab49651ef566185d1aeee4e85 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js @@ -0,0 +1,11 @@ +/** + * @fileoverview Prevent usage of setState in componentDidUpdate + * @author Yannick Croissant + */ + +'use strict'; + +const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = makeNoMethodSetStateRule('componentDidUpdate'); diff --git a/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..076e8a5d10a491c6ef798a610da28d6a69eaf781 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-direct-mutation-state.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..4f5ed6425439161b28f1283d82758b08cb681fdb --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-direct-mutation-state.d.ts","sourceRoot":"","sources":["no-direct-mutation-state.js"],"names":[],"mappings":"wBAuBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js new file mode 100644 index 0000000000000000000000000000000000000000..761151fbb8743af15cf21f9d43be064488f4a12d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js @@ -0,0 +1,155 @@ +/** + * @fileoverview Prevent direct mutation of this.state + * @author David Petersen + * @author Nicolas Fernandez <@burabure> + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noDirectMutation: 'Do not mutate state directly. Use setState().', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow direct mutation of this.state', + category: 'Possible Errors', + recommended: true, + url: docsUrl('no-direct-mutation-state'), + }, + + messages, + }, + + create: Components.detect((context, components, utils) => { + /** + * Checks if the component is valid + * @param {Object} component The component to process + * @returns {boolean} True if the component is valid, false if not. + */ + function isValid(component) { + return !!component && !component.mutateSetState; + } + + /** + * Reports undeclared proptypes for a given component + * @param {Object} component The component to process + */ + function reportMutations(component) { + let mutation; + for (let i = 0, j = component.mutations.length; i < j; i++) { + mutation = component.mutations[i]; + report(context, messages.noDirectMutation, 'noDirectMutation', { + node: mutation, + }); + } + } + + /** + * Walks through the MemberExpression to the top-most property. + * @param {Object} node The node to process + * @returns {Object} The outer-most MemberExpression + */ + function getOuterMemberExpression(node) { + while (node.object && node.object.property) { + node = node.object; + } + return node; + } + + /** + * Determine if we should currently ignore assignments in this component. + * @param {?Object} component The component to process + * @returns {boolean} True if we should skip assignment checks. + */ + function shouldIgnoreComponent(component) { + return !component || (component.inConstructor && !component.inCallExpression); + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + return { + MethodDefinition(node) { + if (node.kind === 'constructor') { + components.set(node, { + inConstructor: true, + }); + } + }, + + CallExpression(node) { + components.set(node, { + inCallExpression: true, + }); + }, + + AssignmentExpression(node) { + const component = components.get(utils.getParentComponent(node)); + if (shouldIgnoreComponent(component) || !node.left || !node.left.object) { + return; + } + const item = getOuterMemberExpression(node.left); + if (componentUtil.isStateMemberExpression(item)) { + const mutations = (component && component.mutations) || []; + mutations.push(node.left.object); + components.set(node, { + mutateSetState: true, + mutations, + }); + } + }, + + UpdateExpression(node) { + const component = components.get(utils.getParentComponent(node)); + if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') { + return; + } + const item = getOuterMemberExpression(node.argument); + if (componentUtil.isStateMemberExpression(item)) { + const mutations = (component && component.mutations) || []; + mutations.push(item); + components.set(node, { + mutateSetState: true, + mutations, + }); + } + }, + + 'CallExpression:exit'(node) { + components.set(node, { + inCallExpression: false, + }); + }, + + 'MethodDefinition:exit'(node) { + if (node.kind === 'constructor') { + components.set(node, { + inConstructor: false, + }); + } + }, + + 'Program:exit'() { + values(components.list()) + .filter((component) => !isValid(component)) + .forEach((component) => { + reportMutations(component); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ddc0a78ef1b89313f79119cfff55e908bb78bdc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-find-dom-node.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..7294175f5abfe596554007a0ce65e4c7548acc52 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-find-dom-node.d.ts","sourceRoot":"","sources":["no-find-dom-node.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js new file mode 100644 index 0000000000000000000000000000000000000000..eaf9e53a464a1c73382b72c1892e42a3e05cf29e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Prevent usage of findDOMNode + * @author Yannick Croissant + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noFindDOMNode: 'Do not use findDOMNode. It doesn’t work with function components and is deprecated in StrictMode. See https://reactjs.org/docs/react-dom.html#finddomnode', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of findDOMNode', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-find-dom-node'), + }, + + messages, + + schema: [], + }, + + create(context) { + return { + CallExpression(node) { + const callee = node.callee; + + const isFindDOMNode = ('name' in callee && callee.name === 'findDOMNode') || ( + 'property' in callee + && callee.property + && 'name' in callee.property + && callee.property.name === 'findDOMNode' + ); + + if (!isFindDOMNode) { + return; + } + + report(context, messages.noFindDOMNode, 'noFindDOMNode', { + node: callee, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9baffdcef17e8622089d81b9cb63f9fea5846344 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-invalid-html-attribute.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..cfee0473db588572b22f475236bf1217927e7fd1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-invalid-html-attribute.d.ts","sourceRoot":"","sources":["no-invalid-html-attribute.js"],"names":[],"mappings":"wBA+kBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js new file mode 100644 index 0000000000000000000000000000000000000000..b8a14d1ab15b762d9bcea68b97310a4e3b46aab7 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js @@ -0,0 +1,654 @@ +/** + * @fileoverview Check if tag attributes to have non-valid value + * @author Sebastian Malton + */ + +'use strict'; + +const matchAll = require('string.prototype.matchall'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const rel = new Map([ + ['alternate', new Set(['link', 'area', 'a'])], + ['apple-touch-icon', new Set(['link'])], + ['apple-touch-startup-image', new Set(['link'])], + ['author', new Set(['link', 'area', 'a'])], + ['bookmark', new Set(['area', 'a'])], + ['canonical', new Set(['link'])], + ['dns-prefetch', new Set(['link'])], + ['external', new Set(['area', 'a', 'form'])], + ['help', new Set(['link', 'area', 'a', 'form'])], + ['icon', new Set(['link'])], + ['license', new Set(['link', 'area', 'a', 'form'])], + ['manifest', new Set(['link'])], + ['mask-icon', new Set(['link'])], + ['modulepreload', new Set(['link'])], + ['next', new Set(['link', 'area', 'a', 'form'])], + ['nofollow', new Set(['area', 'a', 'form'])], + ['noopener', new Set(['area', 'a', 'form'])], + ['noreferrer', new Set(['area', 'a', 'form'])], + ['opener', new Set(['area', 'a', 'form'])], + ['pingback', new Set(['link'])], + ['preconnect', new Set(['link'])], + ['prefetch', new Set(['link'])], + ['preload', new Set(['link'])], + ['prerender', new Set(['link'])], + ['prev', new Set(['link', 'area', 'a', 'form'])], + ['search', new Set(['link', 'area', 'a', 'form'])], + ['shortcut', new Set(['link'])], // generally allowed but needs pair with "icon" + ['shortcut\u0020icon', new Set(['link'])], + ['stylesheet', new Set(['link'])], + ['tag', new Set(['area', 'a'])], +]); + +const pairs = new Map([ + ['shortcut', new Set(['icon'])], +]); + +/** + * Map between attributes and a mapping between valid values and a set of tags they are valid on + * @type {Map>>} + */ +const VALID_VALUES = new Map([ + ['rel', rel], +]); + +/** + * Map between attributes and a mapping between pair-values and a set of values they are valid with + * @type {Map>>} + */ +const VALID_PAIR_VALUES = new Map([ + ['rel', pairs], +]); + +/** + * The set of all possible HTML elements. Used for skipping custom types + * @type {Set} + */ +const HTML_ELEMENTS = new Set([ + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'basefont', + 'bdi', + 'bdo', + 'bgsound', + 'big', + 'blink', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'content', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'font', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'image', + 'img', + 'input', + 'ins', + 'kbd', + 'keygen', + 'label', + 'legend', + 'li', + 'link', + 'main', + 'map', + 'mark', + 'marquee', + 'math', + 'menu', + 'menuitem', + 'meta', + 'meter', + 'nav', + 'nobr', + 'noembed', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'param', + 'picture', + 'plaintext', + 'portal', + 'pre', + 'progress', + 'q', + 'rb', + 'rp', + 'rt', + 'rtc', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'shadow', + 'slot', + 'small', + 'source', + 'spacer', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'svg', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'xmp', +]); + +/** +* Map between attributes and set of tags that the attribute is valid on +* @type {Map>} +*/ +const COMPONENT_ATTRIBUTE_MAP = new Map([ + ['rel', new Set(['link', 'a', 'area', 'form'])], +]); + +/* eslint-disable eslint-plugin/no-unused-message-ids -- false positives, these messageIds are used */ +const messages = { + emptyIsMeaningless: 'An empty “{{attributeName}}” attribute is meaningless.', + neverValid: '“{{reportingValue}}” is never a valid “{{attributeName}}” attribute value.', + noEmpty: 'An empty “{{attributeName}}” attribute is meaningless.', + noMethod: 'The ”{{attributeName}}“ attribute cannot be a method.', + notAlone: '“{{reportingValue}}” must be directly followed by “{{missingValue}}”.', + notPaired: '“{{reportingValue}}” can not be directly followed by “{{secondValue}}” without “{{missingValue}}”.', + notValidFor: '“{{reportingValue}}” is not a valid “{{attributeName}}” attribute value for <{{elementName}}>.', + onlyMeaningfulFor: 'The ”{{attributeName}}“ attribute only has meaning on the tags: {{tagNames}}', + onlyStrings: '“{{attributeName}}” attribute only supports strings.', + spaceDelimited: '”{{attributeName}}“ attribute values should be space delimited.', + suggestRemoveDefault: '"remove {{attributeName}}"', + suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"', + suggestRemoveInvalid: '“remove invalid attribute {{reportingValue}}”', + suggestRemoveWhitespaces: 'remove whitespaces in “{{attributeName}}”', + suggestRemoveNonString: 'remove non-string value in “{{attributeName}}”', +}; + +function splitIntoRangedParts(node, regex) { + const valueRangeStart = node.range[0] + 1; // the plus one is for the initial quote + + return Array.from(matchAll(node.value, regex), (match) => { + const start = match.index + valueRangeStart; + const end = start + match[0].length; + + return { + reportingValue: `${match[1]}`, + value: match[1], + range: [start, end], + }; + }); +} + +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, + suggest: [{ + messageId: 'suggestRemoveNonString', + data, + fix(fixer) { return fixer.remove(parentNode); }, + }], + }); + return; + } + + if (!node.value.trim()) { + const data = { attributeName, reportingValue: node.value }; + + report(context, messages.noEmpty, 'noEmpty', { + node, + data, + suggest: [{ + messageId: 'suggestRemoveEmpty', + data, + fix(fixer) { return fixer.remove(node.parent); }, + }], + }); + return; + } + + const singleAttributeParts = splitIntoRangedParts(node, /(\S+)/g); + singleAttributeParts.forEach((singlePart) => { + const allowedTags = VALID_VALUES.get(attributeName).get(singlePart.value); + const reportingValue = singlePart.reportingValue; + + if (!allowedTags) { + const data = { + attributeName, + reportingValue, + }; + + const suggest = [{ + messageId: 'suggestRemoveInvalid', + data, + fix(fixer) { return fixer.removeRange(singlePart.range); }, + }]; + + report(context, messages.neverValid, 'neverValid', { + node, + data, + suggest, + }); + } else if (!allowedTags.has(parentNodeName)) { + const data = { + attributeName, + reportingValue, + elementName: parentNodeName, + }; + + const suggest = [{ + messageId: 'suggestRemoveInvalid', + data, + fix(fixer) { return fixer.removeRange(singlePart.range); }, + }]; + + report(context, messages.notValidFor, 'notValidFor', { + node, + data, + suggest, + }); + } + }); + + const allowedPairsForAttribute = VALID_PAIR_VALUES.get(attributeName); + if (allowedPairsForAttribute) { + const pairAttributeParts = splitIntoRangedParts(node, /(?=(\b\S+\s*\S+))/g); + pairAttributeParts.forEach((pairPart) => { + allowedPairsForAttribute.forEach((siblings, pairing) => { + const attributes = pairPart.reportingValue.split('\u0020'); + const firstValue = attributes[0]; + const secondValue = attributes[1]; + if (firstValue === pairing) { + const lastValue = attributes[attributes.length - 1]; // in case of multiple white spaces + if (!siblings.has(lastValue)) { + const message = secondValue ? messages.notPaired : messages.notAlone; + const messageId = secondValue ? 'notPaired' : 'notAlone'; + report(context, message, messageId, { + node, + data: { + reportingValue: firstValue, + secondValue, + missingValue: Array.from(siblings).join(', '), + }, + suggest: false, + }); + } + } + }); + }); + } + + const whitespaceParts = splitIntoRangedParts(node, /(\s+)/g); + whitespaceParts.forEach((whitespacePart) => { + const data = { attributeName }; + + if (whitespacePart.range[0] === (node.range[0] + 1) || whitespacePart.range[1] === (node.range[1] - 1)) { + report(context, messages.spaceDelimited, 'spaceDelimited', { + node, + data, + suggest: [{ + messageId: 'suggestRemoveWhitespaces', + data, + fix(fixer) { return fixer.removeRange(whitespacePart.range); }, + }], + }); + } else if (whitespacePart.value !== '\u0020') { + report(context, messages.spaceDelimited, 'spaceDelimited', { + node, + data, + suggest: [{ + messageId: 'suggestRemoveWhitespaces', + data, + fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); }, + }], + }); + } + }); +} + +const DEFAULT_ATTRIBUTES = ['rel']; + +function checkAttribute(context, node) { + const attribute = node.name.name; + + const parentNodeName = node.parent.name.name; + if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) { + const tagNames = Array.from( + COMPONENT_ATTRIBUTE_MAP.get(attribute).values(), + (tagName) => `"<${tagName}>"` + ).join(', '); + const data = { + attributeName: attribute, + tagNames, + }; + + report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { + node: node.name, + data, + suggest: [{ + messageId: 'suggestRemoveDefault', + data, + fix(fixer) { return fixer.remove(node); }, + }], + }); + return; + } + + function fix(fixer) { return fixer.remove(node); } + + if (!node.value) { + const data = { attributeName: attribute }; + + report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', { + node: node.name, + data, + suggest: [{ + messageId: 'suggestRemoveEmpty', + data, + fix, + }], + }); + return; + } + + if (node.value.type === 'Literal') { + return checkLiteralValueNode(context, attribute, node.value, node, parentNodeName); + } + + if (node.value.expression.type === 'Literal') { + return checkLiteralValueNode(context, attribute, node.value.expression, node, parentNodeName); + } + + if (node.value.type !== 'JSXExpressionContainer') { + return; + } + + if (node.value.expression.type === 'ObjectExpression') { + const data = { attributeName: attribute }; + + report(context, messages.onlyStrings, 'onlyStrings', { + node: node.value, + data, + suggest: [{ + messageId: 'suggestRemoveDefault', + 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, + suggest: [{ + messageId: 'suggestRemoveDefault', + data, + fix, + }], + }); + } +} + +function isValidCreateElement(node) { + return node.callee + && node.callee.type === 'MemberExpression' + && node.callee.object.name === 'React' + && node.callee.property.name === 'createElement' + && node.arguments.length > 0; +} + +function checkPropValidValue(context, node, value, attribute) { + const validTags = VALID_VALUES.get(attribute); + + if (value.type !== 'Literal') { + return; // cannot check non-literals + } + + const validTagSet = validTags.get(value.value); + if (!validTagSet) { + const data = { + attributeName: attribute, + reportingValue: value.value, + }; + + report(context, messages.neverValid, 'neverValid', { + node: value, + data, + suggest: [{ + messageId: 'suggestRemoveInvalid', + data, + fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); }, + }], + }); + } else if (!validTagSet.has(node.arguments[0].value)) { + report(context, messages.notValidFor, 'notValidFor', { + node: value, + data: { + attributeName: attribute, + reportingValue: value.raw, + elementName: node.arguments[0].value, + }, + suggest: false, + }); + } +} + +/** + * + * @param {*} context + * @param {*} node + * @param {string} attribute + */ +function checkCreateProps(context, node, attribute) { + const propsArg = node.arguments[1]; + + if (!propsArg || propsArg.type !== 'ObjectExpression') { + return; // can't check variables, computed, or shorthands + } + + for (const prop of propsArg.properties) { + if (!prop.key || prop.key.type !== 'Identifier') { + // eslint-disable-next-line no-continue + continue; // cannot check computed keys + } + + if (prop.key.name !== attribute) { + // eslint-disable-next-line no-continue + continue; // ignore not this attribute + } + + if (!COMPONENT_ATTRIBUTE_MAP.get(attribute).has(node.arguments[0].value)) { + const tagNames = Array.from( + COMPONENT_ATTRIBUTE_MAP.get(attribute).values(), + (tagName) => `"<${tagName}>"` + ).join(', '); + + report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { + node: prop.key, + data: { + attributeName: attribute, + tagNames, + }, + suggest: false, + }); + + // eslint-disable-next-line no-continue + continue; + } + + if (prop.method) { + report(context, messages.noMethod, 'noMethod', { + node: prop, + data: { + attributeName: attribute, + }, + suggest: false, + }); + + // eslint-disable-next-line no-continue + continue; + } + + if (prop.shorthand || prop.computed) { + // eslint-disable-next-line no-continue + continue; // cannot check these + } + + if (prop.value.type === 'ArrayExpression') { + prop.value.elements.forEach((value) => { + checkPropValidValue(context, node, value, attribute); + }); + + // eslint-disable-next-line no-continue + continue; + } + + checkPropValidValue(context, node, prop.value, attribute); + } +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of invalid attributes', + category: 'Possible Errors', + url: docsUrl('no-invalid-html-attribute'), + }, + messages, + schema: [{ + type: 'array', + uniqueItems: true, + items: { + enum: ['rel'], + }, + }], + type: 'suggestion', + hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions + }, + + create(context) { + return { + JSXAttribute(node) { + const attributes = new Set(context.options[0] || DEFAULT_ATTRIBUTES); + + // ignore attributes that aren't configured to be checked + if (!attributes.has(node.name.name)) { + return; + } + + // ignore non-HTML elements + if (!HTML_ELEMENTS.has(node.parent.name.name)) { + return; + } + + checkAttribute(context, node); + }, + + CallExpression(node) { + if (!isValidCreateElement(node)) { + return; + } + + const elemNameArg = node.arguments[0]; + + if (!elemNameArg || elemNameArg.type !== 'Literal') { + return; // can only check literals + } + + // ignore non-HTML elements + if (typeof elemNameArg.value === 'string' && !HTML_ELEMENTS.has(elemNameArg.value)) { + return; + } + + const attributes = new Set(context.options[0] || DEFAULT_ATTRIBUTES); + + attributes.forEach((attribute) => { + checkCreateProps(context, node, attribute); + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4892198b82b67dbdb6a24539d6e6d94e184e286 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-is-mounted.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..df27f4a8419324c77297924a82a0f237bbdffb91 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-is-mounted.d.ts","sourceRoot":"","sources":["no-is-mounted.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js new file mode 100644 index 0000000000000000000000000000000000000000..24d9a37d02b00eee06fda59a79b58b6dda990d1f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Prevent usage of isMounted + * @author Joe Lencioni + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getAncestors = require('../util/eslint').getAncestors; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noIsMounted: 'Do not use isMounted', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of isMounted', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-is-mounted'), + }, + + messages, + + schema: [], + }, + + create(context) { + return { + CallExpression(node) { + const callee = node.callee; + if (callee.type !== 'MemberExpression') { + return; + } + if ( + callee.object.type !== 'ThisExpression' + || !('name' in callee.property) + || callee.property.name !== 'isMounted' + ) { + return; + } + 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', { + node: callee, + }); + break; + } + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..73770afd1816d7fe066fee88d15e055692271ffc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-multi-comp.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..b62343ee261033db8a794857cb483df180137888 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-multi-comp.d.ts","sourceRoot":"","sources":["no-multi-comp.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js new file mode 100644 index 0000000000000000000000000000000000000000..8cf73c90bce43a80b39fb30c3255c9d7b1983be4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js @@ -0,0 +1,81 @@ +/** + * @fileoverview Prevent multiple component definition per file + * @author Yannick Croissant + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + onlyOneComponent: 'Declare only one React component per file', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow multiple component definition per file', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('no-multi-comp'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + ignoreStateless: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create: Components.detect((context, components, utils) => { + const configuration = context.options[0] || {}; + const ignoreStateless = configuration.ignoreStateless || false; + + /** + * Checks if the component is ignored + * @param {Object} component The component being checked. + * @returns {boolean} True if the component is ignored, false if not. + */ + function isIgnored(component) { + return ( + ignoreStateless && ( + /Function/.test(component.node.type) + || utils.isPragmaComponentWrapper(component.node) + ) + ); + } + + return { + 'Program:exit'() { + if (components.length() <= 1) { + return; + } + + values(components.list()) + .filter((component) => !isIgnored(component)) + .slice(1) + .forEach((component) => { + report(context, messages.onlyOneComponent, 'onlyOneComponent', { + node: component.node, + }); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c55685dbda6858f3486f2827d748b4344def12e --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-namespace.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..b56a75f79009c4358c2365cf68526174fe312972 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-namespace.d.ts","sourceRoot":"","sources":["no-namespace.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-namespace.js b/node_modules/eslint-plugin-react/lib/rules/no-namespace.js new file mode 100644 index 0000000000000000000000000000000000000000..20ca5d93241c1956cbbb079287ee64ae1aec2ed6 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-namespace.js @@ -0,0 +1,62 @@ +/** + * @fileoverview Enforce that namespaces are not used in React elements + * @author Yacine Hmito + */ + +'use strict'; + +const elementType = require('jsx-ast-utils/elementType'); +const docsUrl = require('../util/docsUrl'); +const isCreateElement = require('../util/isCreateElement'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +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: { + description: 'Enforce that namespaces are not used in React elements', + category: 'Possible Errors', + recommended: false, + url: docsUrl('no-namespace'), + }, + + messages, + + schema: [], + }, + + create(context) { + return { + CallExpression(node) { + 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', { + node, + data: { + name, + }, + }); + } + }, + JSXOpeningElement(node) { + const name = elementType(node); + if (typeof name !== 'string' || name.indexOf(':') === -1) return undefined; + report(context, messages.noNamespace, 'noNamespace', { + node, + data: { + name, + }, + }); + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..03a21901c4c5ff731fcee12ad899079e7cc8c173 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-object-type-as-default-prop.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..218aa6e29e36d5c73dd39ad7a1e37272e237e56a --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-object-type-as-default-prop.d.ts","sourceRoot":"","sources":["no-object-type-as-default-prop.js"],"names":[],"mappings":"wBAiFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js new file mode 100644 index 0000000000000000000000000000000000000000..2683bfea4cba4c9cb4e286954d164731fc949127 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js @@ -0,0 +1,105 @@ +/** + * @fileoverview Prevent usage of referential-type variables as default param in functional component + * @author Chang Yan + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const astUtil = require('../util/ast'); +const report = require('../util/report'); + +const FORBIDDEN_TYPES_MAP = { + ArrowFunctionExpression: 'arrow function', + FunctionExpression: 'function expression', + ObjectExpression: 'object literal', + ArrayExpression: 'array literal', + ClassExpression: 'class expression', + NewExpression: 'construction expression', + JSXElement: 'JSX element', +}; + +const FORBIDDEN_TYPES = new Set(Object.keys(FORBIDDEN_TYPES_MAP)); +const MESSAGE_ID = 'forbiddenTypeDefaultParam'; + +const messages = { + [MESSAGE_ID]: '{{propName}} has a/an {{forbiddenType}} as default prop. This could lead to potential infinite render loop in React. Use a variable reference instead of {{forbiddenType}}.', +}; +function hasUsedObjectDestructuringSyntax(params) { + return ( + params != null + && params.length >= 1 + && params[0].type === 'ObjectPattern' + ); +} + +function verifyDefaultPropsDestructuring(context, properties) { + // Loop through each of the default params + properties.filter((prop) => prop.type === 'Property' && prop.value.type === 'AssignmentPattern').forEach((prop) => { + const propName = prop.key.name; + const propDefaultValue = prop.value; + + const propDefaultValueType = propDefaultValue.right.type; + + if ( + propDefaultValueType === 'Literal' + && propDefaultValue.right.regex != null + ) { + report(context, messages[MESSAGE_ID], MESSAGE_ID, { + node: propDefaultValue, + data: { + propName, + forbiddenType: 'regex literal', + }, + }); + } else if ( + astUtil.isCallExpression(propDefaultValue.right) + && propDefaultValue.right.callee.type === 'Identifier' + && propDefaultValue.right.callee.name === 'Symbol' + ) { + report(context, messages[MESSAGE_ID], MESSAGE_ID, { + node: propDefaultValue, + data: { + propName, + forbiddenType: 'Symbol literal', + }, + }); + } else if (FORBIDDEN_TYPES.has(propDefaultValueType)) { + report(context, messages[MESSAGE_ID], MESSAGE_ID, { + node: propDefaultValue, + data: { + propName, + forbiddenType: FORBIDDEN_TYPES_MAP[propDefaultValueType], + }, + }); + } + }); +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of referential-type variables as default param in functional component', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-object-type-as-default-prop'), + }, + messages, + }, + create: Components.detect((context, components) => ({ + 'Program:exit'() { + const list = components.list(); + values(list) + .filter((component) => hasUsedObjectDestructuringSyntax(component.node.params)) + .forEach((component) => { + const node = component.node; + const properties = node.params[0].properties; + verifyDefaultPropsDestructuring(context, properties); + }); + }, + })), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..721c683ffabb04c71cfa18f14c91fdb79a1a2e8f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-redundant-should-component-update.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..8e2e6b17fe0cdc874bd4cf7819dc33e92e493e1d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-redundant-should-component-update.d.ts","sourceRoot":"","sources":["no-redundant-should-component-update.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js new file mode 100644 index 0000000000000000000000000000000000000000..e7a439d8e836a098621c92407a585a9ed9568c13 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js @@ -0,0 +1,88 @@ +/** + * @fileoverview Flag shouldComponentUpdate when extending PureComponent + */ + +'use strict'; + +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noShouldCompUpdate: '{{component}} does not need shouldComponentUpdate when extending React.PureComponent.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of shouldComponentUpdate when extending React.PureComponent', + category: 'Possible Errors', + recommended: false, + url: docsUrl('no-redundant-should-component-update'), + }, + + messages, + + schema: [], + }, + + create(context) { + /** + * Checks for shouldComponentUpdate property + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} Whether or not the property exists. + */ + function hasShouldComponentUpdate(node) { + const properties = astUtil.getComponentProperties(node); + return properties.some((property) => { + const name = astUtil.getPropertyName(property); + return name === 'shouldComponentUpdate'; + }); + } + + /** + * Get name of node if available + * @param {ASTNode} node The AST node being checked. + * @return {string} The name of the node + */ + function getNodeName(node) { + if (node.id) { + return node.id.name; + } + if (node.parent && node.parent.id) { + return node.parent.id.name; + } + return ''; + } + + /** + * Checks for violation of rule + * @param {ASTNode} node The AST node being checked. + */ + function checkForViolation(node) { + if (componentUtil.isPureComponent(node, context)) { + const hasScu = hasShouldComponentUpdate(node); + if (hasScu) { + const className = getNodeName(node); + report(context, messages.noShouldCompUpdate, 'noShouldCompUpdate', { + node, + data: { + component: className, + }, + }); + } + } + } + + return { + ClassDeclaration: checkForViolation, + ClassExpression: checkForViolation, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3fc9f179648e9fe029fab0af7d87c7aa9462f81 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-render-return-value.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..e8929411742cf8da09215b9731a1c6cc78ace921 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-render-return-value.d.ts","sourceRoot":"","sources":["no-render-return-value.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js new file mode 100644 index 0000000000000000000000000000000000000000..9e0f9d4046768885bc6a9103d66aa35408f03b96 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js @@ -0,0 +1,82 @@ +/** + * @fileoverview Prevent usage of the return value of React.render + * @author Dustan Kasten + */ + +'use strict'; + +const testReactVersion = require('../util/version').testReactVersion; +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noReturnValue: 'Do not depend on the return value from {{node}}.render', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of the return value of ReactDOM.render', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-render-return-value'), + }, + + messages, + + schema: [], + }, + + create(context) { + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + let calleeObjectName = /^ReactDOM$/; + if (testReactVersion(context, '>= 15.0.0')) { + calleeObjectName = /^ReactDOM$/; + } else if (testReactVersion(context, '^0.14.0')) { + calleeObjectName = /^React(DOM)?$/; + } else if (testReactVersion(context, '^0.13.0')) { + calleeObjectName = /^React$/; + } + + return { + CallExpression(node) { + const callee = node.callee; + const parent = node.parent; + if (callee.type !== 'MemberExpression') { + return; + } + + if ( + callee.object.type !== 'Identifier' + || !calleeObjectName.test(callee.object.name) + || (!('name' in callee.property) || callee.property.name !== 'render') + ) { + return; + } + + if ( + parent.type === 'VariableDeclarator' + || parent.type === 'Property' + || parent.type === 'ReturnStatement' + || parent.type === 'ArrowFunctionExpression' + || parent.type === 'AssignmentExpression' + ) { + report(context, messages.noReturnValue, 'noReturnValue', { + node: callee, + data: { + node: callee.object.name, + }, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9825f5b1433c7f9494b78f58f77a040e815cc44 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-set-state.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..f47d09a5d37d0a0a04f517c01c371188c719083f --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-set-state.d.ts","sourceRoot":"","sources":["no-set-state.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-set-state.js b/node_modules/eslint-plugin-react/lib/rules/no-set-state.js new file mode 100644 index 0000000000000000000000000000000000000000..44967bf03cba12524a79e871f2256c3b94579f42 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-set-state.js @@ -0,0 +1,88 @@ +/** + * @fileoverview Prevent usage of setState + * @author Mark Dalgleish + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noSetState: 'Do not use setState', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of setState', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('no-set-state'), + }, + + messages, + + schema: [], + }, + + create: Components.detect((context, components, utils) => { + /** + * Checks if the component is valid + * @param {Object} component The component to process + * @returns {boolean} True if the component is valid, false if not. + */ + function isValid(component) { + return !!component && !component.useSetState; + } + + /** + * Reports usages of setState for a given component + * @param {Object} component The component to process + */ + function reportSetStateUsages(component) { + for (let i = 0, j = component.setStateUsages.length; i < j; i++) { + const setStateUsage = component.setStateUsages[i]; + report(context, messages.noSetState, 'noSetState', { + node: setStateUsage, + }); + } + } + + return { + CallExpression(node) { + const callee = node.callee; + if ( + callee.type !== 'MemberExpression' + || callee.object.type !== 'ThisExpression' + || callee.property.name !== 'setState' + ) { + return; + } + const component = components.get(utils.getParentComponent(node)); + const setStateUsages = (component && component.setStateUsages) || []; + setStateUsages.push(callee); + components.set(node, { + useSetState: true, + setStateUsages, + }); + }, + + 'Program:exit'() { + values(components.list()) + .filter((component) => !isValid(component)) + .forEach((component) => { + reportSetStateUsages(component); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e44ed1d84beacc9d9a5c80e9130a3b3476ecdea --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-string-refs.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..6af2d5b2ee5d52ddcee81576c0144a9f735888aa --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-string-refs.d.ts","sourceRoot":"","sources":["no-string-refs.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js new file mode 100644 index 0000000000000000000000000000000000000000..145ad25780cf2ca507649de1779e2e6a3b617dab --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js @@ -0,0 +1,117 @@ +/** + * @fileoverview Prevent string definitions for references and prevent referencing this.refs + * @author Tom Hastjarjanto + */ + +'use strict'; + +const componentUtil = require('../util/componentUtil'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); +const testReactVersion = require('../util/version').testReactVersion; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + thisRefsDeprecated: 'Using this.refs is deprecated.', + stringInRefDeprecated: 'Using string literals in ref attributes is deprecated.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow using string references', + category: 'Best Practices', + recommended: true, + url: docsUrl('no-string-refs'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + noTemplateLiterals: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const checkRefsUsage = testReactVersion(context, '< 18.3.0'); // `this.refs` is writable in React 18.3.0 and later, see https://github.com/facebook/react/pull/28867 + const detectTemplateLiterals = context.options[0] ? context.options[0].noTemplateLiterals : false; + /** + * Checks if we are using refs + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if we are using refs, false if not. + */ + function isRefsUsage(node) { + return !!( + (componentUtil.getParentES6Component(context, node) || componentUtil.getParentES5Component(context, node)) + && node.object.type === 'ThisExpression' + && node.property.name === 'refs' + ); + } + + /** + * Checks if we are using a ref attribute + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if we are using a ref attribute, false if not. + */ + function isRefAttribute(node) { + return node.type === 'JSXAttribute' + && !!node.name + && node.name.name === 'ref'; + } + + /** + * Checks if a node contains a string value + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if the node contains a string value, false if not. + */ + function containsStringLiteral(node) { + return !!node.value + && node.value.type === 'Literal' + && typeof node.value.value === 'string'; + } + + /** + * Checks if a node contains a string value within a jsx expression + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} True if the node contains a string value within a jsx expression, false if not. + */ + function containsStringExpressionContainer(node) { + return !!node.value + && node.value.type === 'JSXExpressionContainer' + && node.value.expression + && ((node.value.expression.type === 'Literal' && typeof node.value.expression.value === 'string') + || (node.value.expression.type === 'TemplateLiteral' && detectTemplateLiterals)); + } + + return { + MemberExpression(node) { + if (checkRefsUsage && isRefsUsage(node)) { + report(context, messages.thisRefsDeprecated, 'thisRefsDeprecated', { + node, + }); + } + }, + + JSXAttribute(node) { + if ( + isRefAttribute(node) + && (containsStringLiteral(node) || containsStringExpressionContainer(node)) + ) { + report(context, messages.stringInRefDeprecated, 'stringInRefDeprecated', { + node, + }); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..37e259395ee82fa3f82aaa17f0dd7752f9aa39b5 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-this-in-sfc.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..0dbc892aee85ac5426b3fb949ed0fa6c6769a16d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-this-in-sfc.d.ts","sourceRoot":"","sources":["no-this-in-sfc.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js new file mode 100644 index 0000000000000000000000000000000000000000..c520abd31cf9255fc5c0285cbaa9d3cf5224dee8 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js @@ -0,0 +1,47 @@ +/** + * @fileoverview Report "this" being used in stateless functional components. + */ + +'use strict'; + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + noThisInSFC: 'Stateless functional components should not use `this`', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow `this` from being used in stateless functional components', + category: 'Possible Errors', + recommended: false, + url: docsUrl('no-this-in-sfc'), + }, + + messages, + + schema: [], + }, + + create: Components.detect((context, components, utils) => ({ + MemberExpression(node) { + if (node.object.type === 'ThisExpression') { + const component = components.get(utils.getParentStatelessComponent(node)); + if (!component || (component.node && component.node.parent && component.node.parent.type === 'Property')) { + return; + } + report(context, messages.noThisInSFC, 'noThisInSFC', { + node, + }); + } + }, + })), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7f262b03b914cba224cf9cdad86bbd8b660edcb --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-typos.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..0bebb0383a12c784cf7306bb6df7874475821cfc --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-typos.d.ts","sourceRoot":"","sources":["no-typos.js"],"names":[],"mappings":"wBA+BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-typos.js b/node_modules/eslint-plugin-react/lib/rules/no-typos.js new file mode 100644 index 0000000000000000000000000000000000000000..a598a8e1142c8f96ccb966ab7ddabe8dda09b6f4 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-typos.js @@ -0,0 +1,260 @@ +/** + * @fileoverview Prevent common casing typos + */ + +'use strict'; + +const PROP_TYPES = Object.keys(require('prop-types')); +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const astUtil = require('../util/ast'); +const componentUtil = require('../util/componentUtil'); +const report = require('../util/report'); +const lifecycleMethods = require('../util/lifecycleMethods'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps']; + +const messages = { + typoPropTypeChain: 'Typo in prop type chain qualifier: {{name}}', + typoPropType: 'Typo in declared prop type: {{name}}', + typoStaticClassProp: 'Typo in static class property declaration', + typoPropDeclaration: 'Typo in property declaration', + typoLifecycleMethod: 'Typo in component lifecycle method declaration: {{actual}} should be {{expected}}', + staticLifecycleMethod: 'Lifecycle method should be static: {{method}}', + noPropTypesBinding: '`\'prop-types\'` imported without a local `PropTypes` binding.', + noReactBinding: '`\'react\'` imported without a local `React` binding.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow common typos', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('no-typos'), + }, + + messages, + + schema: [], + }, + + create: Components.detect((context, components, utils) => { + let propTypesPackageName = null; + let reactPackageName = null; + + function checkValidPropTypeQualifier(node) { + if (node.name !== 'isRequired') { + report(context, messages.typoPropTypeChain, 'typoPropTypeChain', { + node, + data: { name: node.name }, + }); + } + } + + function checkValidPropType(node) { + if (node.name && !PROP_TYPES.some((propTypeName) => propTypeName === node.name)) { + report(context, messages.typoPropType, 'typoPropType', { + node, + data: { name: node.name }, + }); + } + } + + function isPropTypesPackage(node) { + return ( + node.type === 'Identifier' + && node.name === propTypesPackageName + ) || ( + node.type === 'MemberExpression' + && node.property.name === 'PropTypes' + && node.object.name === reactPackageName + ); + } + + /* eslint-disable no-use-before-define */ + + function checkValidCallExpression(node) { + const callee = node.callee; + if (callee.type === 'MemberExpression' && callee.property.name === 'shape') { + checkValidPropObject(node.arguments[0]); + } else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') { + const args = node.arguments[0]; + if (args && args.type === 'ArrayExpression') { + args.elements.forEach((el) => { + checkValidProp(el); + }); + } + } + } + + function checkValidProp(node) { + if ((!propTypesPackageName && !reactPackageName) || !node) { + return; + } + + if (node.type === 'MemberExpression') { + if ( + node.object.type === 'MemberExpression' + && isPropTypesPackage(node.object.object) + ) { // PropTypes.myProp.isRequired + checkValidPropType(node.object.property); + checkValidPropTypeQualifier(node.property); + } else if ( + isPropTypesPackage(node.object) + && node.property.name !== 'isRequired' + ) { // PropTypes.myProp + checkValidPropType(node.property); + } else if (astUtil.isCallExpression(node.object)) { + checkValidPropTypeQualifier(node.property); + checkValidCallExpression(node.object); + } + } else if (astUtil.isCallExpression(node)) { + checkValidCallExpression(node); + } + } + + /* eslint-enable no-use-before-define */ + + function checkValidPropObject(node) { + if (node && node.type === 'ObjectExpression') { + node.properties.forEach((prop) => checkValidProp(prop.value)); + } + } + + function reportErrorIfPropertyCasingTypo(propertyValue, propertyKey, isClassProperty) { + const propertyName = propertyKey.name; + if (propertyName === 'propTypes' || propertyName === 'contextTypes' || propertyName === 'childContextTypes') { + checkValidPropObject(propertyValue); + } + STATIC_CLASS_PROPERTIES.forEach((CLASS_PROP) => { + if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) { + const messageId = isClassProperty + ? 'typoStaticClassProp' + : 'typoPropDeclaration'; + report(context, messages[messageId], messageId, { + node: propertyKey, + }); + } + }); + } + + function reportErrorIfLifecycleMethodCasingTypo(node) { + const key = node.key; + let nodeKeyName = key.name; + if (key.type === 'Literal') { + nodeKeyName = key.value; + } + if (key.type === 'PrivateName' || (node.computed && typeof nodeKeyName !== 'string')) { + return; + } + + lifecycleMethods.static.forEach((method) => { + if (!node.static && nodeKeyName && nodeKeyName.toLowerCase() === method.toLowerCase()) { + report(context, messages.staticLifecycleMethod, 'staticLifecycleMethod', { + node, + data: { + method: nodeKeyName, + }, + }); + } + }); + + lifecycleMethods.instance.concat(lifecycleMethods.static).forEach((method) => { + if (nodeKeyName && method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { + report(context, messages.typoLifecycleMethod, 'typoLifecycleMethod', { + node, + data: { actual: nodeKeyName, expected: method }, + }); + } + }); + } + + return { + ImportDeclaration(node) { + if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types" + if (node.specifiers.length > 0) { + propTypesPackageName = node.specifiers[0].local.name; + } else { + report(context, messages.noPropTypesBinding, 'noPropTypesBinding', { + node, + }); + } + } else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react" + if (node.specifiers.length > 0) { + reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"` + } else { + report(context, messages.noReactBinding, 'noReactBinding', { + node, + }); + } + if (node.specifiers.length >= 1) { + const propTypesSpecifier = node.specifiers.find((specifier) => ( + specifier.imported + && specifier.imported.name === 'PropTypes' + )); + if (propTypesSpecifier) { + propTypesPackageName = propTypesSpecifier.local.name; + } + } + } + }, + + 'ClassProperty, PropertyDefinition'(node) { + if (!node.static || !componentUtil.isES6Component(node.parent.parent, context)) { + return; + } + + reportErrorIfPropertyCasingTypo(node.value, node.key, true); + }, + + MemberExpression(node) { + const propertyName = node.property.name; + + if ( + !propertyName + || STATIC_CLASS_PROPERTIES.map((prop) => prop.toLocaleLowerCase()).indexOf(propertyName.toLowerCase()) === -1 + ) { + return; + } + + const relatedComponent = utils.getRelatedComponent(node); + + if ( + relatedComponent + && (componentUtil.isES6Component(relatedComponent.node, context) || ( + relatedComponent.node.type !== 'ClassDeclaration' && utils.isReturningJSX(relatedComponent.node))) + && (node.parent && node.parent.type === 'AssignmentExpression' && node.parent.right) + ) { + reportErrorIfPropertyCasingTypo(node.parent.right, node.property, true); + } + }, + + MethodDefinition(node) { + if (!componentUtil.isES6Component(node.parent.parent, context)) { + return; + } + + reportErrorIfLifecycleMethodCasingTypo(node); + }, + + ObjectExpression(node) { + const component = componentUtil.isES5Component(node, context) && components.get(node); + + if (!component) { + return; + } + + node.properties.filter((property) => property.type !== 'SpreadElement').forEach((property) => { + reportErrorIfPropertyCasingTypo(property.value, property.key, false); + reportErrorIfLifecycleMethodCasingTypo(property); + }); + }, + }; + }), +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..baaf17d86608bb9a00d51fe4ca8d5887d0d106a1 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-unescaped-entities.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..130436b918c7bbf80210be09f4fe88023514ab1d --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-unescaped-entities.d.ts","sourceRoot":"","sources":["no-unescaped-entities.js"],"names":[],"mappings":"wBAwCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js new file mode 100644 index 0000000000000000000000000000000000000000..3ec2cb23b6b2984ac9a236bd32db3e4b91eec6ec --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js @@ -0,0 +1,157 @@ +/** + * @fileoverview HTML special characters should be escaped. + * @author Patrick Hayes + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); +const getSourceCode = require('../util/eslint').getSourceCode; +const jsxUtil = require('../util/jsx'); +const report = require('../util/report'); +const getMessageData = require('../util/message'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +// NOTE: '<' and '{' are also problematic characters, but they do not need +// to be included here because it is a syntax error when these characters are +// included accidentally. +const DEFAULTS = [{ + char: '>', + alternatives: ['>'], +}, { + char: '"', + alternatives: ['"', '“', '"', '”'], +}, { + char: '\'', + alternatives: [''', '‘', ''', '’'], +}, { + char: '}', + alternatives: ['}'], +}]; + +const messages = { + unescapedEntity: 'HTML entity, `{{entity}}` , must be escaped.', + unescapedEntityAlts: '`{{entity}}` can be escaped with {{alts}}.', + replaceWithAlt: 'Replace with `{{alt}}`.', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + hasSuggestions: true, + docs: { + description: 'Disallow unescaped HTML entities from appearing in markup', + category: 'Possible Errors', + recommended: true, + url: docsUrl('no-unescaped-entities'), + }, + + messages, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + anyOf: [{ + type: 'string', + }, { + type: 'object', + properties: { + char: { + type: 'string', + }, + alternatives: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, + }, + }], + }, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + function reportInvalidEntity(node) { + const configuration = context.options[0] || {}; + const entities = configuration.forbid || DEFAULTS; + + // HTML entities are already escaped in node.value (as well as node.raw), + // so pull the raw text from getSourceCode(context) + for (let i = node.loc.start.line; i <= node.loc.end.line; i++) { + let rawLine = getSourceCode(context).lines[i - 1]; + let start = 0; + let end = rawLine.length; + if (i === node.loc.start.line) { + start = node.loc.start.column; + } + if (i === node.loc.end.line) { + end = node.loc.end.column; + } + rawLine = rawLine.slice(start, end); + for (let j = 0; j < entities.length; j++) { + for (let index = 0; index < rawLine.length; index++) { + const c = rawLine[index]; + if (typeof entities[j] === 'string') { + if (c === entities[j]) { + report(context, messages.unescapedEntity, 'unescapedEntity', { + node, + loc: { line: i, column: start + index }, + data: { + entity: entities[j], + }, + }); + } + } else if (c === entities[j].char) { + report(context, messages.unescapedEntityAlts, 'unescapedEntityAlts', { + node, + loc: { line: i, column: start + index }, + data: { + entity: entities[j].char, + alts: entities[j].alternatives.map((alt) => `\`${alt}\``).join(', '), + }, + suggest: entities[j].alternatives.map((alt) => Object.assign( + getMessageData('replaceWithAlt', messages.replaceWithAlt), + { + data: { alt }, + fix(fixer) { + const lineToChange = i - node.loc.start.line; + + const newText = node.raw.split('\n').map((line, idx) => { + if (idx === lineToChange) { + return line.slice(0, index) + alt + line.slice(index + 1); + } + + return line; + }).join('\n'); + + return fixer.replaceText(node, newText); + }, + } + )), + }); + } + } + } + } + } + + return { + 'Literal, JSXText'(node) { + if (jsxUtil.isJSX(node.parent)) { + reportInvalidEntity(node); + } + }, + }; + }, +}; diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..49a219c3ddd4da24fe1c2918e94d572cc3cfc0cb --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts @@ -0,0 +1,3 @@ +declare const _exports: import('eslint').Rule.RuleModule; +export = _exports; +//# sourceMappingURL=no-unknown-property.d.ts.map \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map new file mode 100644 index 0000000000000000000000000000000000000000..e1e1e49a9fa3ca81b63977bc1c49f449d0fe83cf --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"no-unknown-property.d.ts","sourceRoot":"","sources":["no-unknown-property.js"],"names":[],"mappings":"wBAshBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"} \ No newline at end of file diff --git a/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js new file mode 100644 index 0000000000000000000000000000000000000000..d702cc43039f0fdce6dae36d690079d67fcf8be2 --- /dev/null +++ b/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js @@ -0,0 +1,671 @@ +/** + * @fileoverview Prevent usage of unknown DOM property + * @author Yannick Croissant + */ + +'use strict'; + +const has = require('hasown'); +const docsUrl = require('../util/docsUrl'); +const getText = require('../util/eslint').getText; +const testReactVersion = require('../util/version').testReactVersion; +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +const DEFAULTS = { + ignore: [], + requireDataLowercase: false, +}; + +const DOM_ATTRIBUTE_NAMES = { + 'accept-charset': 'acceptCharset', + class: 'className', + 'http-equiv': 'httpEquiv', + crossorigin: 'crossOrigin', + for: 'htmlFor', + nomodule: 'noModule', +}; + +const ATTRIBUTE_TAGS_MAP = { + abbr: ['th', 'td'], + charset: ['meta'], + checked: ['input'], + // image is required for SVG support, all other tags are HTML. + crossOrigin: ['script', 'img', 'video', 'audio', 'link', 'image'], + displaystyle: ['math'], + // https://html.spec.whatwg.org/multipage/links.html#downloading-resources + download: ['a', 'area'], + fill: [ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + // Fill color + 'altGlyph', + 'circle', + 'ellipse', + 'g', + 'line', + 'marker', + 'mask', + 'path', + 'polygon', + 'polyline', + 'rect', + 'svg', + 'symbol', + 'text', + 'textPath', + 'tref', + 'tspan', + 'use', + // Animation final state + 'animate', + 'animateColor', + 'animateMotion', + 'animateTransform', + 'set', + ], + focusable: ['svg'], + imageSizes: ['link'], + imageSrcSet: ['link'], + property: ['meta'], + viewBox: ['marker', 'pattern', 'svg', 'symbol', 'view'], + as: ['link'], + align: ['applet', 'caption', 'col', 'colgroup', 'hr', 'iframe', 'img', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'], // deprecated, but known + valign: ['tr', 'td', 'th', 'thead', 'tbody', 'tfoot', 'colgroup', 'col'], // deprecated, but known + noModule: ['script'], + // Media events allowed only on audio and video tags, see https://github.com/facebook/react/blob/256aefbea1449869620fb26f6ec695536ab453f5/CHANGELOG.md#notable-enhancements + onAbort: ['audio', 'video'], + onCancel: ['dialog'], + onCanPlay: ['audio', 'video'], + onCanPlayThrough: ['audio', 'video'], + onClose: ['dialog'], + onDurationChange: ['audio', 'video'], + onEmptied: ['audio', 'video'], + onEncrypted: ['audio', 'video'], + onEnded: ['audio', 'video'], + onError: ['audio', 'video', 'img', 'link', 'source', 'script', 'picture', 'iframe'], + onLoad: ['script', 'img', 'link', 'picture', 'iframe', 'object', 'source'], + onLoadedData: ['audio', 'video'], + onLoadedMetadata: ['audio', 'video'], + onLoadStart: ['audio', 'video'], + onPause: ['audio', 'video'], + onPlay: ['audio', 'video'], + onPlaying: ['audio', 'video'], + onProgress: ['audio', 'video'], + onRateChange: ['audio', 'video'], + onResize: ['audio', 'video'], + onSeeked: ['audio', 'video'], + onSeeking: ['audio', 'video'], + onStalled: ['audio', 'video'], + onSuspend: ['audio', 'video'], + onTimeUpdate: ['audio', 'video'], + onVolumeChange: ['audio', 'video'], + onWaiting: ['audio', 'video'], + autoPictureInPicture: ['video'], + controls: ['audio', 'video'], + controlsList: ['audio', 'video'], + disablePictureInPicture: ['video'], + disableRemotePlayback: ['audio', 'video'], + loop: ['audio', 'video'], + muted: ['audio', 'video'], + playsInline: ['video'], + allowFullScreen: ['iframe', 'video'], + webkitAllowFullScreen: ['iframe', 'video'], + mozAllowFullScreen: ['iframe', 'video'], + poster: ['video'], + preload: ['audio', 'video'], + scrolling: ['iframe'], + returnValue: ['dialog'], + webkitDirectory: ['input'], + shadowrootmode: ['template'], + shadowrootclonable: ['template'], + shadowrootdelegatesfocus: ['template'], + shadowrootserializable: ['template'], + 'transform-origin': ['rect'], +}; + +const SVGDOM_ATTRIBUTE_NAMES = { + 'accent-height': 'accentHeight', + 'alignment-baseline': 'alignmentBaseline', + 'arabic-form': 'arabicForm', + 'baseline-shift': 'baselineShift', + 'cap-height': 'capHeight', + 'clip-path': 'clipPath', + 'clip-rule': 'clipRule', + 'color-interpolation': 'colorInterpolation', + 'color-interpolation-filters': 'colorInterpolationFilters', + 'color-profile': 'colorProfile', + 'color-rendering': 'colorRendering', + 'dominant-baseline': 'dominantBaseline', + 'enable-background': 'enableBackground', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'flood-color': 'floodColor', + 'flood-opacity': 'floodOpacity', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-size-adjust': 'fontSizeAdjust', + 'font-stretch': 'fontStretch', + 'font-style': 'fontStyle', + 'font-variant': 'fontVariant', + 'font-weight': 'fontWeight', + 'glyph-name': 'glyphName', + 'glyph-orientation-horizontal': 'glyphOrientationHorizontal', + 'glyph-orientation-vertical': 'glyphOrientationVertical', + 'horiz-adv-x': 'horizAdvX', + 'horiz-origin-x': 'horizOriginX', + 'image-rendering': 'imageRendering', + 'letter-spacing': 'letterSpacing', + 'lighting-color': 'lightingColor', + 'marker-end': 'markerEnd', + 'marker-mid': 'markerMid', + 'marker-start': 'markerStart', + 'overline-position': 'overlinePosition', + 'overline-thickness': 'overlineThickness', + 'paint-order': 'paintOrder', + 'panose-1': 'panose1', + 'pointer-events': 'pointerEvents', + 'rendering-intent': 'renderingIntent', + 'shape-rendering': 'shapeRendering', + 'stop-color': 'stopColor', + 'stop-opacity': 'stopOpacity', + 'strikethrough-position': 'strikethroughPosition', + 'strikethrough-thickness': 'strikethroughThickness', + 'stroke-dasharray': 'strokeDasharray', + 'stroke-dashoffset': 'strokeDashoffset', + 'stroke-linecap': 'strokeLinecap', + 'stroke-linejoin': 'strokeLinejoin', + 'stroke-miterlimit': 'strokeMiterlimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-anchor': 'textAnchor', + 'text-decoration': 'textDecoration', + 'text-rendering': 'textRendering', + 'underline-position': 'underlinePosition', + 'underline-thickness': 'underlineThickness', + 'unicode-bidi': 'unicodeBidi', + 'unicode-range': 'unicodeRange', + 'units-per-em': 'unitsPerEm', + 'v-alphabetic': 'vAlphabetic', + 'v-hanging': 'vHanging', + 'v-ideographic': 'vIdeographic', + 'v-mathematical': 'vMathematical', + 'vector-effect': 'vectorEffect', + 'vert-adv-y': 'vertAdvY', + 'vert-origin-x': 'vertOriginX', + 'vert-origin-y': 'vertOriginY', + 'word-spacing': 'wordSpacing', + 'writing-mode': 'writingMode', + 'x-height': 'xHeight', + 'xlink:actuate': 'xlinkActuate', + 'xlink:arcrole': 'xlinkArcrole', + 'xlink:href': 'xlinkHref', + 'xlink:role': 'xlinkRole', + 'xlink:show': 'xlinkShow', + 'xlink:title': 'xlinkTitle', + 'xlink:type': 'xlinkType', + 'xml:base': 'xmlBase', + 'xml:lang': 'xmlLang', + 'xml:space': 'xmlSpace', +}; + +const DOM_PROPERTY_NAMES_ONE_WORD = [ + // Global attributes - can be used on any HTML/DOM element + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes + 'dir', 'draggable', 'hidden', 'id', 'lang', 'nonce', 'part', 'slot', 'style', 'title', 'translate', 'inert', + // Element specific attributes + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too) + // To be considered if these should be added also to ATTRIBUTE_TAGS_MAP + 'accept', 'action', 'allow', 'alt', 'as', 'async', 'buffered', 'capture', 'challenge', 'cite', 'code', 'cols', + 'content', 'coords', 'csp', 'data', 'decoding', 'default', 'defer', 'disabled', 'form', + 'headers', 'height', 'high', 'href', 'icon', 'importance', 'integrity', 'kind', 'label', + 'language', 'loading', 'list', 'loop', 'low', 'manifest', 'max', 'media', 'method', 'min', 'multiple', 'muted', + 'name', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'profile', + 'rel', 'required', 'reversed', 'role', 'rows', 'sandbox', 'scope', 'seamless', 'selected', 'shape', 'size', 'sizes', + 'span', 'src', 'start', 'step', 'summary', 'target', 'type', 'value', 'width', 'wmode', 'wrap', + // SVG attributes + // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute + 'accumulate', 'additive', 'alphabetic', 'amplitude', 'ascent', 'azimuth', 'bbox', 'begin', + 'bias', 'by', 'clip', 'color', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'direction', + 'display', 'divisor', 'dur', 'dx', 'dy', 'elevation', 'end', 'exponent', 'fill', 'filter', + 'format', 'from', 'fr', 'fx', 'fy', 'g1', 'g2', 'hanging', 'height', 'hreflang', 'ideographic', + 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'local', 'mask', 'mode', + 'offset', 'opacity', 'operator', 'order', 'orient', 'orientation', 'origin', 'overflow', 'path', + 'ping', 'points', 'r', 'radius', 'rel', 'restart', 'result', 'rotate', 'rx', 'ry', 'scale', + 'seed', 'slope', 'spacing', 'speed', 'stemh', 'stemv', 'string', 'stroke', 'to', 'transform', + 'u1', 'u2', 'unicode', 'values', 'version', 'visibility', 'widths', 'x', 'x1', 'x2', 'xmlns', + 'y', 'y1', 'y2', 'z', + // OpenGraph meta tag attributes + 'property', + // React specific attributes + 'ref', 'key', 'children', + // Non-standard + 'results', 'security', + // Video specific + 'controls', + // popovers + 'popover', 'popovertarget', 'popovertargetaction', +]; + +const DOM_PROPERTY_NAMES_TWO_WORDS = [ + // Global attributes - can be used on any HTML/DOM element + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes + 'accessKey', 'autoCapitalize', 'autoFocus', 'contentEditable', 'enterKeyHint', 'exportParts', + 'inputMode', 'itemID', 'itemRef', 'itemProp', 'itemScope', 'itemType', 'spellCheck', 'tabIndex', + // Element specific attributes + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too) + // To be considered if these should be added also to ATTRIBUTE_TAGS_MAP + 'acceptCharset', 'autoComplete', 'autoPlay', 'border', 'cellPadding', 'cellSpacing', 'classID', 'codeBase', + 'colSpan', 'contextMenu', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', + 'frameBorder', 'hrefLang', 'httpEquiv', 'imageSizes', 'imageSrcSet', 'isMap', 'keyParams', 'keyType', 'marginHeight', 'marginWidth', + 'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'onAnimationEnd', 'onAnimationIteration', 'onAnimationStart', + 'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart', + 'onCompositionUpdate', 'onCut', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', + 'onError', 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoad', 'onWheel', 'onDragOver', + 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', + 'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onBeforeToggle', 'onToggle', 'onTransitionEnd', 'radioGroup', + 'readOnly', 'referrerPolicy', 'rowSpan', 'srcDoc', 'srcLang', 'srcSet', 'useMap', 'fetchPriority', + // SVG attributes + // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute + 'crossOrigin', 'accentHeight', 'alignmentBaseline', 'arabicForm', 'attributeName', + 'attributeType', 'baseFrequency', 'baselineShift', 'baseProfile', 'calcMode', 'capHeight', + 'clipPathUnits', 'clipPath', 'clipRule', 'colorInterpolation', 'colorInterpolationFilters', + 'colorProfile', 'colorRendering', 'contentScriptType', 'contentStyleType', 'diffuseConstant', + 'dominantBaseline', 'edgeMode', 'enableBackground', 'fillOpacity', 'fillRule', 'filterRes', + 'filterUnits', 'floodColor', 'floodOpacity', 'fontFamily', 'fontSize', 'fontSizeAdjust', + 'fontStretch', 'fontStyle', 'fontVariant', 'fontWeight', 'glyphName', + 'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', + 'gradientUnits', 'horizAdvX', 'horizOriginX', 'imageRendering', 'kernelMatrix', + 'kernelUnitLength', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', + 'lightingColor', 'limitingConeAngle', 'markerEnd', 'markerMid', 'markerStart', 'markerHeight', + 'markerUnits', 'markerWidth', 'maskContentUnits', 'maskUnits', 'mathematical', 'numOctaves', + 'overlinePosition', 'overlineThickness', 'panose1', 'paintOrder', 'pathLength', + 'patternContentUnits', 'patternTransform', 'patternUnits', 'pointerEvents', 'pointsAtX', + 'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', + 'referrerPolicy', 'refX', 'refY', 'rendering-intent', 'repeatCount', 'repeatDur', + 'requiredExtensions', 'requiredFeatures', 'shapeRendering', 'specularConstant', + 'specularExponent', 'spreadMethod', 'startOffset', 'stdDeviation', 'stitchTiles', 'stopColor', + 'stopOpacity', 'strikethroughPosition', 'strikethroughThickness', 'strokeDasharray', + 'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', 'strokeOpacity', + 'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', 'targetY', + 'textAnchor', 'textDecoration', 'textRendering', 'textLength', 'transformOrigin', + 'underlinePosition', 'underlineThickness', 'unicodeBidi', 'unicodeRange', 'unitsPerEm', + 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', 'vectorEffect', 'vertAdvY', + 'vertOriginX', 'vertOriginY', 'viewBox', 'viewTarget', 'wordSpacing', 'writingMode', 'xHeight', + 'xChannelSelector', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', 'xlinkShow', + 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlnsXlink', 'xmlSpace', 'yChannelSelector', + 'zoomAndPan', + // Safari/Apple specific, no listing available + 'autoCorrect', // https://stackoverflow.com/questions/47985384/html-autocorrect-for-text-input-is-not-working + 'autoSave', // https://stackoverflow.com/questions/25456396/what-is-autosave-attribute-supposed-to-do-how-do-i-use-it + // React specific attributes https://reactjs.org/docs/dom-elements.html#differences-in-attributes + 'className', 'dangerouslySetInnerHTML', 'defaultValue', 'defaultChecked', 'htmlFor', + // Events' capture events + 'onBeforeInput', 'onChange', + 'onInvalid', 'onReset', 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'suppressContentEditableWarning', 'suppressHydrationWarning', + 'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', + 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onResize', + 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting', + 'onCopyCapture', 'onCutCapture', 'onPasteCapture', 'onCompositionEndCapture', 'onCompositionStartCapture', 'onCompositionUpdateCapture', + 'onFocusCapture', 'onBlurCapture', 'onChangeCapture', 'onBeforeInputCapture', 'onInputCapture', 'onResetCapture', 'onSubmitCapture', + 'onInvalidCapture', 'onLoadCapture', 'onErrorCapture', 'onKeyDownCapture', 'onKeyPressCapture', 'onKeyUpCapture', + 'onAbortCapture', 'onCanPlayCapture', 'onCanPlayThroughCapture', 'onDurationChangeCapture', 'onEmptiedCapture', 'onEncryptedCapture', + 'onEndedCapture', 'onLoadedDataCapture', 'onLoadedMetadataCapture', 'onLoadStartCapture', 'onPauseCapture', 'onPlayCapture', + 'onPlayingCapture', 'onProgressCapture', 'onRateChangeCapture', 'onSeekedCapture', 'onSeekingCapture', 'onStalledCapture', 'onSuspendCapture', + 'onTimeUpdateCapture', 'onVolumeChangeCapture', 'onWaitingCapture', 'onSelectCapture', 'onTouchCancelCapture', 'onTouchEndCapture', + 'onTouchMoveCapture', 'onTouchStartCapture', 'onScrollCapture', 'onWheelCapture', 'onAnimationEndCapture', 'onAnimationIteration', + 'onAnimationStartCapture', 'onTransitionEndCapture', + 'onAuxClick', 'onAuxClickCapture', 'onClickCapture', 'onContextMenuCapture', 'onDoubleClickCapture', + 'onDragCapture', 'onDragEndCapture', 'onDragEnterCapture', 'onDragExitCapture', 'onDragLeaveCapture', + 'onDragOverCapture', 'onDragStartCapture', 'onDropCapture', 'onMouseDown', 'onMouseDownCapture', + 'onMouseMoveCapture', 'onMouseOutCapture', 'onMouseOverCapture', 'onMouseUpCapture', + // Video specific + 'autoPictureInPicture', 'controlsList', 'disablePictureInPicture', 'disableRemotePlayback', + // popovers + 'popoverTarget', 'popoverTargetAction', +]; + +const DOM_PROPERTIES_IGNORE_CASE = ['charset', 'allowFullScreen', 'webkitAllowFullScreen', 'mozAllowFullScreen', 'webkitDirectory', 'popoverTarget', 'popoverTargetAction']; + +const ARIA_PROPERTIES = [ + // See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes + // Global attributes + 'aria-atomic', 'aria-braillelabel', 'aria-brailleroledescription', 'aria-busy', 'aria-controls', 'aria-current', + 'aria-describedby', 'aria-description', 'aria-details', + 'aria-disabled', 'aria-dropeffect', 'aria-errormessage', 'aria-flowto', 'aria-grabbed', 'aria-haspopup', + 'aria-hidden', 'aria-invalid', 'aria-keyshortcuts', 'aria-label', 'aria-labelledby', 'aria-live', + 'aria-owns', 'aria-relevant', 'aria-roledescription', + // Widget attributes + 'aria-autocomplete', 'aria-checked', 'aria-expanded', 'aria-level', 'aria-modal', 'aria-multiline', 'aria-multiselectable', + 'aria-orientation', 'aria-placeholder', 'aria-pressed', 'aria-readonly', 'aria-required', 'aria-selected', + 'aria-sort', 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext', + // Relationship attributes + 'aria-activedescendant', 'aria-colcount', 'aria-colindex', 'aria-colindextext', 'aria-colspan', + 'aria-posinset', 'aria-rowcount', 'aria-rowindex', 'aria-rowindextext', 'aria-rowspan', 'aria-setsize', +]; + +const REACT_ON_PROPS = [ + 'onGotPointerCapture', + 'onGotPointerCaptureCapture', + 'onLostPointerCapture', + 'onLostPointerCapture', + 'onLostPointerCaptureCapture', + 'onPointerCancel', + 'onPointerCancelCapture', + 'onPointerDown', + 'onPointerDownCapture', + 'onPointerEnter', + 'onPointerEnterCapture', + 'onPointerLeave', + 'onPointerLeaveCapture', + 'onPointerMove', + 'onPointerMoveCapture', + 'onPointerOut', + 'onPointerOutCapture', + 'onPointerOver', + 'onPointerOverCapture', + 'onPointerUp', + 'onPointerUpCapture', +]; + +function getDOMPropertyNames(context) { + return [].concat( + DOM_PROPERTY_NAMES_TWO_WORDS, + DOM_PROPERTY_NAMES_ONE_WORD, + + testReactVersion(context, '>= 16.1.0') ? [].concat( + testReactVersion(context, '>= 16.4.0') ? [].concat( + // these were added in React v16.4.0, see https://reactjs.org/blog/2018/05/23/react-v-16-4.html and https://github.com/facebook/react/pull/12507 + REACT_ON_PROPS, + testReactVersion(context, '>= 19') ? [ + // precedence was added in React v19, see https://react.dev/blog/2024/04/25/react-19#support-for-stylesheets + 'precedence', + ] : [] + ) : [] + ) : [ + // this was removed in React v16.1+, see https://github.com/facebook/react/pull/10823 + 'allowTransparency', + ] + ); +} + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * Checks if a node's parent is a JSX tag that is written with lowercase letters, + * and is not a custom web component. Custom web components have a hyphen in tag name, + * or have an `is="some-elem"` attribute. + * + * Note: does not check if a tag's parent against a list of standard HTML/DOM tags. For example, + * a ``'s child would return `true` because "fake" is written only with lowercase letters + * without a hyphen and does not have a `is="some-elem"` attribute. + * + * @param {Object} childNode - JSX element being tested. + * @returns {boolean} Whether or not the node name match the JSX tag convention. + */ +function isValidHTMLTagInJSX(childNode) { + const tagConvention = /^[a-z][^-]*$/; + if (tagConvention.test(childNode.parent.name.name)) { + return !childNode.parent.attributes.some((attrNode) => ( + attrNode.type === 'JSXAttribute' + && attrNode.name.type === 'JSXIdentifier' + && attrNode.name.name === 'is' + // To learn more about custom web components and `is` attribute, + // see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example + + )); + } + return false; +} + +/** + * Checks if the attribute name is included in the attributes that are excluded + * from the camel casing. + * + * // returns 'charSet' + * @example normalizeAttributeCase('charset') + * + * Note - these exclusions are not made by React core team, but `eslint-plugin-react` community. + * + * @param {string} name - Attribute name to be normalized + * @returns {string} Result + */ +function normalizeAttributeCase(name) { + return DOM_PROPERTIES_IGNORE_CASE.find((element) => element.toLowerCase() === name.toLowerCase()) || name; +} + +/** + * Checks if an attribute name is a valid `data-*` attribute: + * if the name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them), + * not start with any casing of "xml", and separated by hyphens (-) (which is also called "kebab case" or "dash case"), + * then the attribute is a valid data attribute. + * + * @param {string} name - Attribute name to be tested + * @returns {boolean} Result + */ +function isValidDataAttribute(name) { + return !/^data-xml/i.test(name) && /^data-[^:]*$/.test(name); +} + +/** + * Checks if an attribute name has at least one uppercase characters + * + * @param {string} name + * @returns {boolean} Result + */ +function hasUpperCaseCharacter(name) { + return name.toLowerCase() !== name; +} + +/** + * Checks if an attribute name is a standard aria attribute by compering it to a list + * of standard aria property names + * + * @param {string} name - Attribute name to be tested + * @returns {boolean} Result + */ + +function isValidAriaAttribute(name) { + return ARIA_PROPERTIES.some((element) => element === name); +} + +/** + * Extracts the tag name for the JSXAttribute + * @param {JSXAttribute} node - JSXAttribute being tested. + * @returns {string | null} tag name + */ +function getTagName(node) { + if ( + node + && node.parent + && node.parent.name + ) { + return node.parent.name.name; + } + return null; +} + +/** + * Test wether the tag name for the JSXAttribute is + * something like + * @param {JSXAttribute} node - JSXAttribute being tested. + * @returns {boolean} result + */ +function tagNameHasDot(node) { + return !!( + node.parent + && node.parent.name + && node.parent.name.type === 'JSXMemberExpression' + ); +} + +/** + * Get the standard name of the attribute. + * @param {string} name - Name of the attribute. + * @param {object} context - eslint context + * @returns {string | undefined} The standard name of the attribute, or undefined if no standard name was found. + */ +function getStandardName(name, context) { + if (has(DOM_ATTRIBUTE_NAMES, name)) { + return DOM_ATTRIBUTE_NAMES[/** @type {keyof DOM_ATTRIBUTE_NAMES} */ (name)]; + } + if (has(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. + return names.find((element) => element.toLowerCase() === name.toLowerCase()); +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + invalidPropOnTag: 'Invalid property \'{{name}}\' found on tag \'{{tagName}}\', but it is only allowed on: {{allowedTags}}', + unknownPropWithStandardName: 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead', + unknownProp: 'Unknown property \'{{name}}\' found', + dataLowercaseRequired: 'React does not recognize data-* props with uppercase characters on a DOM element. Found \'{{name}}\', use \'{{lowerCaseName}}\' instead', +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + docs: { + description: 'Disallow usage of unknown DOM property', + category: 'Possible Errors', + recommended: true, + url: docsUrl('no-unknown-property'), + }, + fixable: 'code', + + messages, + + schema: [{ + type: 'object', + properties: { + ignore: { + type: 'array', + items: { + type: 'string', + }, + }, + requireDataLowercase: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + function getIgnoreConfig() { + return (context.options[0] && context.options[0].ignore) || DEFAULTS.ignore; + } + + function getRequireDataLowercase() { + return (context.options[0] && typeof context.options[0].requireDataLowercase !== 'undefined') + ? !!context.options[0].requireDataLowercase + : DEFAULTS.requireDataLowercase; + } + + return { + JSXAttribute(node) { + const ignoreNames = getIgnoreConfig(); + const actualName = getText(context, node.name); + if (ignoreNames.indexOf(actualName) >= 0) { + return; + } + const name = normalizeAttributeCase(actualName); + + // Ignore tags like + if (tagNameHasDot(node)) { + return; + } + + if (isValidDataAttribute(name)) { + if (getRequireDataLowercase() && hasUpperCaseCharacter(name)) { + report(context, messages.dataLowercaseRequired, 'dataLowercaseRequired', { + node, + data: { + name: actualName, + lowerCaseName: actualName.toLowerCase(), + }, + }); + } + + return; + } + + if (isValidAriaAttribute(name)) { return; } + + const tagName = getTagName(node); + + if (tagName === 'fbt' || tagName === 'fbs') { return; } // fbt/fbs nodes are bonkers, let's not go there + + if (!isValidHTMLTagInJSX(node)) { return; } + + // Let's dive deeper into tags that are HTML/DOM elements (`