main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

22
node_modules/eslint-plugin-react/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Yannick Croissant
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.

423
node_modules/eslint-plugin-react/README.md generated vendored Normal file
View File

@@ -0,0 +1,423 @@
# `eslint-plugin-react` <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
===================
[![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*`) <a id="configuration"></a>
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 <pragma>), 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": "<pragma>"} // sets `object` to whatever value `settings.react.pragma` is set to
],
"formComponents": [
// Components used as alternatives to <form> for forms, eg. <Form endpoint={ url } />
"CustomForm",
{"name": "SimpleForm", "formAttribute": "endpoint"},
{"name": "Form", "formAttribute": ["registerEndpoint", "loginEndpoint"]}, // allows specifying multiple properties if necessary
],
"linkComponents": [
// Components used as alternatives to <a> for linking, eg. <Link to={ url } />
"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,
- <https://eslint.org/blog/2022/08/new-config-system-part-1/>
- <https://eslint.org/blog/2022/08/new-config-system-part-2/>
- <https://eslint.org/blog/2022/08/new-config-system-part-3/>
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.
<!-- markdownlint-disable-next-line no-duplicate-heading -->
### 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
<!-- begin auto-generated rules list -->
💼 [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. `<img />`, `<br />`) from receiving children | | | | | |
<!-- end auto-generated rules list -->
## 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

13
node_modules/eslint-plugin-react/configs/all.js generated vendored Normal file
View File

@@ -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 });

View File

@@ -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 });

View File

@@ -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 });

713
node_modules/eslint-plugin-react/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,713 @@
export declare namespace configs {
export const flat: {
recommended: {
plugins: {
react: {
deprecatedRules: any;
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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
missingRefParameter: string;
addRefParameter: string;
removeForwardRef: string;
};
schema: undefined[];
type: string;
hasSuggestions: boolean;
};
create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
};
'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': {
meta: import("eslint").Rule.RuleMetaData;
create(context: any): (false & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
}) | ({
ImportDeclaration(node: any): void;
VariableDeclaration(node: any): void;
} & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
});
};
'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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
noMultiSpreading: string;
};
};
create(context: any): {
JSXOpeningElement(node: any): void;
};
};
'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;
};
configs: {
recommended: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: {
'react/display-name': number;
'react/jsx-key': number;
'react/jsx-no-comment-textnodes': number;
'react/jsx-no-duplicate-props': number;
'react/jsx-no-target-blank': number;
'react/jsx-no-undef': number;
'react/jsx-uses-react': number;
'react/jsx-uses-vars': number;
'react/no-children-prop': number;
'react/no-danger-with-children': number;
'react/no-deprecated': number;
'react/no-direct-mutation-state': number;
'react/no-find-dom-node': number;
'react/no-is-mounted': number;
'react/no-render-return-value': number;
'react/no-string-refs': number;
'react/no-unescaped-entities': number;
'react/no-unknown-property': number;
'react/no-unsafe': number;
'react/prop-types': number;
'react/react-in-jsx-scope': number;
'react/require-render-return': number;
};
};
all: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: Record<string, 2>;
};
'jsx-runtime': {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
jsxPragma: any;
};
rules: {
'react/react-in-jsx-scope': number;
'react/jsx-uses-react': number;
};
};
};
};
};
rules: {
'react/display-name': number;
'react/jsx-key': number;
'react/jsx-no-comment-textnodes': number;
'react/jsx-no-duplicate-props': number;
'react/jsx-no-target-blank': number;
'react/jsx-no-undef': number;
'react/jsx-uses-react': number;
'react/jsx-uses-vars': number;
'react/no-children-prop': number;
'react/no-danger-with-children': number;
'react/no-deprecated': number;
'react/no-direct-mutation-state': number;
'react/no-find-dom-node': number;
'react/no-is-mounted': number;
'react/no-render-return-value': number;
'react/no-string-refs': number;
'react/no-unescaped-entities': number;
'react/no-unknown-property': number;
'react/no-unsafe': number;
'react/prop-types': number;
'react/react-in-jsx-scope': number;
'react/require-render-return': number;
};
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
};
};
all: {
plugins: {
react: {
deprecatedRules: any;
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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
missingRefParameter: string;
addRefParameter: string;
removeForwardRef: string;
};
schema: undefined[];
type: string;
hasSuggestions: boolean;
};
create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
};
'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': {
meta: import("eslint").Rule.RuleMetaData;
create(context: any): (false & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
}) | ({
ImportDeclaration(node: any): void;
VariableDeclaration(node: any): void;
} & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
});
};
'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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
noMultiSpreading: string;
};
};
create(context: any): {
JSXOpeningElement(node: any): void;
};
};
'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;
};
configs: {
recommended: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: {
'react/display-name': number;
'react/jsx-key': number;
'react/jsx-no-comment-textnodes': number;
'react/jsx-no-duplicate-props': number;
'react/jsx-no-target-blank': number;
'react/jsx-no-undef': number;
'react/jsx-uses-react': number;
'react/jsx-uses-vars': number;
'react/no-children-prop': number;
'react/no-danger-with-children': number;
'react/no-deprecated': number;
'react/no-direct-mutation-state': number;
'react/no-find-dom-node': number;
'react/no-is-mounted': number;
'react/no-render-return-value': number;
'react/no-string-refs': number;
'react/no-unescaped-entities': number;
'react/no-unknown-property': number;
'react/no-unsafe': number;
'react/prop-types': number;
'react/react-in-jsx-scope': number;
'react/require-render-return': number;
};
};
all: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: Record<string, 2>;
};
'jsx-runtime': {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
jsxPragma: any;
};
rules: {
'react/react-in-jsx-scope': number;
'react/jsx-uses-react': number;
};
};
};
};
};
rules: Record<string, 2>;
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
};
};
'jsx-runtime': {
plugins: {
react: {
deprecatedRules: any;
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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
missingRefParameter: string;
addRefParameter: string;
removeForwardRef: string;
};
schema: undefined[];
type: string;
hasSuggestions: boolean;
};
create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
};
'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': {
meta: import("eslint").Rule.RuleMetaData;
create(context: any): (false & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
}) | ({
ImportDeclaration(node: any): void;
VariableDeclaration(node: any): void;
} & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
});
};
'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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
noMultiSpreading: string;
};
};
create(context: any): {
JSXOpeningElement(node: any): void;
};
};
'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;
};
configs: {
recommended: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: {
'react/display-name': number;
'react/jsx-key': number;
'react/jsx-no-comment-textnodes': number;
'react/jsx-no-duplicate-props': number;
'react/jsx-no-target-blank': number;
'react/jsx-no-undef': number;
'react/jsx-uses-react': number;
'react/jsx-uses-vars': number;
'react/no-children-prop': number;
'react/no-danger-with-children': number;
'react/no-deprecated': number;
'react/no-direct-mutation-state': number;
'react/no-find-dom-node': number;
'react/no-is-mounted': number;
'react/no-render-return-value': number;
'react/no-string-refs': number;
'react/no-unescaped-entities': number;
'react/no-unknown-property': number;
'react/no-unsafe': number;
'react/prop-types': number;
'react/react-in-jsx-scope': number;
'react/require-render-return': number;
};
};
all: {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
};
rules: Record<string, 2>;
};
'jsx-runtime': {
plugins: string[];
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
jsxPragma: any;
};
rules: {
'react/react-in-jsx-scope': number;
'react/jsx-uses-react': number;
};
};
};
};
};
rules: {
'react/react-in-jsx-scope': number;
'react/jsx-uses-react': number;
};
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: boolean;
};
jsxPragma: any;
};
};
};
};
}
//# sourceMappingURL=index.d.ts.map

1
node_modules/eslint-plugin-react/index.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""}

109
node_modules/eslint-plugin-react/index.js generated vendored Normal file
View File

@@ -0,0 +1,109 @@
'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<string, 2>}
*/
function configureAsError(rules) {
return fromEntries(Object.keys(rules).map((key) => [`react/${key}`, 2]));
}
const activeRules = filterRules(allRules, (rule) => !rule.meta.deprecated);
const activeRulesConfig = configureAsError(activeRules);
const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated);
// for legacy config system
const plugins = [
'react',
];
const plugin = {
deprecatedRules,
rules: allRules,
configs: {
recommended: {
plugins,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
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,
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': 0,
'react/jsx-uses-react': 0,
},
},
},
};
plugin.configs.flat = {
recommended: {
plugins: { react: plugin },
rules: plugin.configs.recommended.rules,
languageOptions: { parserOptions: plugin.configs.recommended.parserOptions },
},
all: {
plugins: { react: plugin },
rules: plugin.configs.all.rules,
languageOptions: { parserOptions: plugin.configs.all.parserOptions },
},
'jsx-runtime': {
plugins: { react: plugin },
rules: plugin.configs['jsx-runtime'].rules,
languageOptions: { parserOptions: plugin.configs['jsx-runtime'].parserOptions },
},
};
module.exports = plugin;

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=boolean-prop-naming.d.ts.map

View File

@@ -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"}

View File

@@ -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}}` doesnt 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();
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=button-has-type.d.ts.map

View File

@@ -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"}

View File

@@ -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);
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=checked-requires-onchange-or-readonly.d.ts.map

View File

@@ -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"}

View File

@@ -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<string>}
*/
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<string>} 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);
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=default-props-match-prop-types.d.ts.map

View File

@@ -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"}

View File

@@ -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 || {}
);
});
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=destructuring-assignment.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"destructuring-assignment.d.ts","sourceRoot":"","sources":["destructuring-assignment.js"],"names":[],"mappings":"wBA2DW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -0,0 +1,285 @@
/**
* @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,
},
});
}
}
return {
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
FunctionExpression: handleStatelessComponent,
'FunctionDeclaration:exit': handleStatelessComponentExit,
'ArrowFunctionExpression:exit': handleStatelessComponentExit,
'FunctionExpression:exit': handleStatelessComponentExit,
MemberExpression(node) {
let scope = getScope(context, node);
let SFCComponent = components.get(scope.block);
while (!SFCComponent && scope.upper && scope.upper !== scope) {
SFCComponent = components.get(scope.upper.block);
scope = scope.upper;
}
if (SFCComponent) {
handleSFCUsage(node);
}
const classComponent = utils.getParentComponent(node);
if (classComponent) {
handleClassUsage(node);
}
},
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),
];
},
});
}
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=display-name.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"display-name.d.ts","sourceRoot":"","sources":["display-name.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -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)
);
}
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-component-props.d.ts.map

View File

@@ -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"}

View File

@@ -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 (`<this.something>`), 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. `<AntdLayout.Content />`.
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,
},
});
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-dom-props.d.ts.map

View File

@@ -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"}

View File

@@ -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<string, object>} 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,
},
});
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-elements.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"forbid-elements.d.ts","sourceRoot":"","sources":["forbid-elements.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -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<string, { element: string, message?: string }>} */
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);
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-foreign-prop-types.d.ts.map

View File

@@ -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"}

View File

@@ -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,
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-prop-types.d.ts.map

View File

@@ -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"}

View File

@@ -0,0 +1,297 @@
/**
* @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
&& 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);
}
});
},
};
},
};

View File

@@ -0,0 +1,23 @@
export declare const meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
missingRefParameter: string;
addRefParameter: string;
removeForwardRef: string;
};
schema: any[];
type: string;
hasSuggestions: boolean;
};
export declare function create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
export declare function create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
//# sourceMappingURL=forward-ref-uses-ref.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"forward-ref-uses-ref.d.ts","sourceRoot":"","sources":["forward-ref-uses-ref.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAyDE;;EAwCC;AAxCD;;EAwCC"}

View File

@@ -0,0 +1,99 @@
/**
* @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',
};
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));
},
}
),
],
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=function-component-definition.d.ts.map

View File

@@ -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"}

View File

@@ -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;
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=hook-use-state.d.ts.map

View File

@@ -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"}

View File

@@ -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,
}
);
}
},
};
}),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=iframe-missing-sandbox.d.ts.map

View File

@@ -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"}

View File

@@ -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);
}
}
},
};
},
};

158
node_modules/eslint-plugin-react/lib/rules/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,158 @@
declare const _exports: {
'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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
missingRefParameter: string;
addRefParameter: string;
removeForwardRef: string;
};
schema: undefined[];
type: string;
hasSuggestions: boolean;
};
create(context: any): {
'FunctionExpression, ArrowFunctionExpression'(node: any): void;
};
};
'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': {
meta: import("eslint").Rule.RuleMetaData;
create(context: any): (false & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
}) | ({
ImportDeclaration(node: any): void;
VariableDeclaration(node: any): void;
} & {
Literal(node: any): void;
JSXAttribute(node: any): void;
JSXText(node: any): void;
TemplateLiteral(node: any): void;
});
};
'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': {
meta: {
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
messages: {
noMultiSpreading: string;
};
};
create(context: any): {
JSXOpeningElement(node: any): void;
};
};
'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;
};
export = _exports;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""}

110
node_modules/eslint-plugin-react/lib/rules/index.js generated vendored Normal file
View File

@@ -0,0 +1,110 @@
'use strict';
/* eslint global-require: 0 */
/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
module.exports = {
'boolean-prop-naming': require('./boolean-prop-naming'),
'button-has-type': require('./button-has-type'),
'checked-requires-onchange-or-readonly': require('./checked-requires-onchange-or-readonly'),
'default-props-match-prop-types': require('./default-props-match-prop-types'),
'destructuring-assignment': require('./destructuring-assignment'),
'display-name': require('./display-name'),
'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'),
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-boolean-value.d.ts.map

View File

@@ -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"}

View File

@@ -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<string>} 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<string>} 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]]);
},
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-child-element-spacing.d.ts.map

View File

@@ -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"}

View File

@@ -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,
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-closing-bracket-location.d.ts.map

View File

@@ -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"}

View File

@@ -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;
}
},
});
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-closing-tag-location.d.ts.map

View File

@@ -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"}

View File

@@ -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,
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-curly-brace-presence.d.ts.map

View File

@@ -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"}

View File

@@ -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 = '<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
// <App prop1={<CustomEl />} prop2={<CustomEl>...</CustomEl>} />
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);
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-curly-newline.d.ts.map

View File

@@ -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"}

View File

@@ -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);
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-curly-spacing.d.ts.map

View File

@@ -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"}

View File

@@ -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,
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-equals-spacing.d.ts.map

View File

@@ -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"}

View File

@@ -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, ' ');
},
});
}
}
});
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-filename-extension.d.ts.map

View File

@@ -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"}

View File

@@ -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 === '<text>') {
// 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),
},
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-first-prop-new-line.d.ts.map

View File

@@ -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"}

View File

@@ -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]], ' ');
},
});
}
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-fragments.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsx-fragments.d.ts","sourceRoot":"","sources":["jsx-fragments.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -0,0 +1,208 @@
/**
* @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 = `</${reactPragma}.${fragmentPragma}>`;
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 && 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),
});
}
}
});
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-handler-names.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsx-handler-names.d.ts","sourceRoot":"","sources":["jsx-handler-names.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -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}}\'',
};
/** @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) {
function isPrefixDisabled(prefix) {
return prefix === false;
}
function isInlineHandler(node) {
return node.value.expression.type === 'ArrowFunctionExpression';
}
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) => {
const isIgnored = minimatch(componentName, ignoredComponentNamePattern);
return isIgnored;
});
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,
},
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-indent-props.d.ts.map

View File

@@ -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"}

View File

@@ -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);
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-indent.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsx-indent.d.ts","sourceRoot":"","sources":["jsx-indent.js"],"names":[],"mappings":"wBAoDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

View File

@@ -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);
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-key.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsx-key.d.ts","sourceRoot":"","sources":["jsx-key.js"],"names":[],"mappings":"wBAmCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

Some files were not shown because too many files have changed in this diff Show More