mirror of
https://github.com/elyby/eslint-plugin.git
synced 2024-11-30 02:32:13 +05:30
Init commit
This commit is contained in:
commit
4f58034415
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/lib
|
||||||
|
/node_modules
|
||||||
|
.idea
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 Ely.by <team@ely.by>
|
||||||
|
|
||||||
|
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.
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Ely.by ESLint rules
|
||||||
|
|
||||||
|
Set of ESLint rules used in development of Ely.by JS projects. Contains rules for pure JS, JSX, ReactJS and TS.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First of all install Ely.by ESLint plugin and `eslint` peer dependency via preferred package manager:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# NPM users:
|
||||||
|
npm install @elyby/eslint-plugin eslint --save-dev
|
||||||
|
# Yarn users:
|
||||||
|
yarn add -D @elyby/eslint-plugin eslint
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add the following configuration to your `.eslintrc.js` file:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'plugin:@elyby/config',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's it!
|
||||||
|
|
||||||
|
You may still wish to override some of our rules, as well as the rest of our eslint configuration settings.
|
||||||
|
For example, you can specify the preferred `env` for eslint:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
// ...rest of the configuration
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using our custom fixers
|
||||||
|
|
||||||
|
First of all, you must install Ely.by's ESLint plugin as described in the [installation chapter](#installation).
|
||||||
|
After that you can enable our custom rules with defining our plugin in `plugins` section:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
// ...rest of the configuration
|
||||||
|
plugins: [
|
||||||
|
'@elyby',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
After that all custom rules will be available for use.
|
||||||
|
|
||||||
|
### List of supported rules
|
||||||
|
|
||||||
|
* [@elyby/jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md):
|
||||||
|
Validate closing bracket location in JSX
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Ely.by ESLint plugin is licensed under the [MIT License](LICENSE.md).
|
10
babel.config.js
Normal file
10
babel.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = (api) => ({
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-env', {
|
||||||
|
targets: {
|
||||||
|
node: api.env('test') ? 'current' : 8,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
['@babel/preset-typescript'],
|
||||||
|
],
|
||||||
|
});
|
41
docs/rules/jsx-closing-bracket-location.md
Normal file
41
docs/rules/jsx-closing-bracket-location.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Validate closing bracket location in JSX (@elyby/jsx-closing-bracket-location)
|
||||||
|
|
||||||
|
Enforce the closing bracket location for JSX multiline elements.
|
||||||
|
|
||||||
|
## Rule Details
|
||||||
|
|
||||||
|
This rule checks all JSX multiline elements and verifies the location of the closing bracket according to the Ely.by's
|
||||||
|
team preferences.
|
||||||
|
|
||||||
|
The rule is simple: if the first property is written on the same line as the component, it's considered to be inline
|
||||||
|
and the closing bracket should be placed immediately after the last property:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<Component param={123} />
|
||||||
|
<Component param1={123} param2={{
|
||||||
|
key: 'value',
|
||||||
|
}} />
|
||||||
|
```
|
||||||
|
|
||||||
|
And vice versa: if the first property is written with a new line, the component is considered to be multi-line
|
||||||
|
and the closing bracket must be placed on the next line after the last property.
|
||||||
|
|
||||||
|
```js
|
||||||
|
<Component
|
||||||
|
param={123}
|
||||||
|
/>
|
||||||
|
<Comment
|
||||||
|
param1={123}
|
||||||
|
param2={{
|
||||||
|
key: 'value',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## When Not To Use It
|
||||||
|
|
||||||
|
If you follow the same formatting rules for component properties as our team.
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
* [Inspired by the original rule from `eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md).
|
8
jest.config.js
Normal file
8
jest.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
clearMocks: true,
|
||||||
|
coverageDirectory: 'reports/coverage',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: [
|
||||||
|
'**/tests/**/*.[jt]s?(x)',
|
||||||
|
],
|
||||||
|
};
|
44
package.json
Normal file
44
package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "@elyby/eslint-plugin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Shareable ESLint config for the Ely.by's projects",
|
||||||
|
"author": {
|
||||||
|
"name": "erickskrauch",
|
||||||
|
"email": "erickskrauch@ely.by",
|
||||||
|
"url": "https://github.com/erickskrauch"
|
||||||
|
},
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"repository": "https://github.com/elyby/eslint-config.git",
|
||||||
|
"keywords": [
|
||||||
|
"eslint",
|
||||||
|
"eslintconfig",
|
||||||
|
"lint",
|
||||||
|
"ely",
|
||||||
|
"elyby"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "babel src -d lib --extensions .ts,.js",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^1.9.0",
|
||||||
|
"@typescript-eslint/parser": "^1.9.0",
|
||||||
|
"eslint-plugin-react": "^7.13.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.4.4",
|
||||||
|
"@babel/core": "^7.4.5",
|
||||||
|
"@babel/preset-env": "^7.4.4",
|
||||||
|
"@babel/preset-typescript": "^7.3.3",
|
||||||
|
"@types/eslint": "^4.16.6",
|
||||||
|
"@types/estree": "^0.0.39",
|
||||||
|
"@types/node": "^12.0.2",
|
||||||
|
"eslint": "^5.16.0",
|
||||||
|
"jest": "^24.8.0",
|
||||||
|
"typescript": "^3.4.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=5.16.0"
|
||||||
|
}
|
||||||
|
}
|
278
src/configs/all.ts
Normal file
278
src/configs/all.ts
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
import { Linter } from 'eslint';
|
||||||
|
|
||||||
|
const config: Linter.Config = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'@elyby',
|
||||||
|
],
|
||||||
|
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// possible errors (including eslint:recommended)
|
||||||
|
'valid-jsdoc': ['warn', {
|
||||||
|
requireParamDescription: false,
|
||||||
|
requireReturn: false,
|
||||||
|
requireReturnDescription: false,
|
||||||
|
prefer: {
|
||||||
|
returns: 'return',
|
||||||
|
},
|
||||||
|
preferType: {
|
||||||
|
String: 'string',
|
||||||
|
Object: 'object',
|
||||||
|
Number: 'number',
|
||||||
|
Function: 'function',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
|
||||||
|
// best practice
|
||||||
|
'block-scoped-var': 'error',
|
||||||
|
'curly': 'error',
|
||||||
|
'default-case': 'error',
|
||||||
|
'dot-location': ['error', 'property'],
|
||||||
|
'dot-notation': 'error',
|
||||||
|
'eqeqeq': ['error', 'smart'],
|
||||||
|
'no-alert': 'error',
|
||||||
|
'no-caller': 'error',
|
||||||
|
'no-case-declarations': 'error',
|
||||||
|
'no-div-regex': 'error',
|
||||||
|
'no-else-return': 'error',
|
||||||
|
'no-empty-pattern': 'error',
|
||||||
|
'no-eq-null': 'error',
|
||||||
|
'no-eval': 'error',
|
||||||
|
'no-extend-native': 'error',
|
||||||
|
'no-extra-bind': 'warn',
|
||||||
|
'no-fallthrough': 'error',
|
||||||
|
'no-floating-decimal': 'warn',
|
||||||
|
'no-implied-eval': 'error',
|
||||||
|
'no-invalid-this': 'off',
|
||||||
|
'no-labels': 'error',
|
||||||
|
'no-lone-blocks': 'warn',
|
||||||
|
'no-loop-func': 'error',
|
||||||
|
'no-multi-spaces': 'error',
|
||||||
|
'no-multi-str': 'error',
|
||||||
|
'no-native-reassign': 'error',
|
||||||
|
'no-new-wrappers': 'warn',
|
||||||
|
'no-new': 'warn',
|
||||||
|
'no-octal-escape': 'warn',
|
||||||
|
'no-octal': 'error',
|
||||||
|
'no-proto': 'error',
|
||||||
|
'no-redeclare': 'warn',
|
||||||
|
'no-script-url': 'error',
|
||||||
|
'no-self-compare': 'error',
|
||||||
|
'no-sequences': 'error',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
'no-unused-expressions': ['warn', {
|
||||||
|
allowShortCircuit: true,
|
||||||
|
allowTernary: true,
|
||||||
|
}],
|
||||||
|
'no-useless-call': 'warn',
|
||||||
|
'no-useless-concat': 'warn',
|
||||||
|
'no-void': 'error',
|
||||||
|
'no-with': 'error',
|
||||||
|
'radix': 'error',
|
||||||
|
'wrap-iife': 'error',
|
||||||
|
'yoda': 'warn',
|
||||||
|
'no-constant-condition': 'error',
|
||||||
|
|
||||||
|
// strict mode
|
||||||
|
'strict': ['warn', 'never'], // allow babel to do it for us
|
||||||
|
|
||||||
|
// variables
|
||||||
|
'no-catch-shadow': 'off',
|
||||||
|
'no-delete-var': 'error',
|
||||||
|
'no-label-var': 'error',
|
||||||
|
'no-shadow-restricted-names': 'error',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'no-undef-init': 'error',
|
||||||
|
'no-undef': 'error',
|
||||||
|
'no-use-before-define': ['warn', 'nofunc'],
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
'no-mixed-requires': 'warn',
|
||||||
|
'no-path-concat': 'warn',
|
||||||
|
|
||||||
|
// stylistic
|
||||||
|
'array-bracket-spacing': 'off', // disable because we want spaces on destructured arrays
|
||||||
|
'block-spacing': ['error', 'never'],
|
||||||
|
'brace-style': ['error', '1tbs', {
|
||||||
|
allowSingleLine: true,
|
||||||
|
}],
|
||||||
|
'comma-spacing': 'error',
|
||||||
|
'comma-style': 'error',
|
||||||
|
'comma-dangle': ['warn', 'always-multiline'],
|
||||||
|
'computed-property-spacing': 'error',
|
||||||
|
'consistent-this': ['error', 'that'],
|
||||||
|
'camelcase': 'warn',
|
||||||
|
'eol-last': 'warn',
|
||||||
|
'id-length': ['error', {
|
||||||
|
min: 2,
|
||||||
|
exceptions: ['x', 'y', 'i', '$'],
|
||||||
|
}],
|
||||||
|
'indent': ['error', 4, {
|
||||||
|
SwitchCase: 1,
|
||||||
|
}],
|
||||||
|
'jsx-quotes': 'error',
|
||||||
|
'key-spacing': ['error', {
|
||||||
|
mode: 'minimum',
|
||||||
|
}],
|
||||||
|
'linebreak-style': 'error',
|
||||||
|
'max-depth': 'error',
|
||||||
|
'new-cap': 'error',
|
||||||
|
'new-parens': 'error',
|
||||||
|
'no-array-constructor': 'warn',
|
||||||
|
'no-bitwise': 'warn',
|
||||||
|
'no-lonely-if': 'error',
|
||||||
|
'no-negated-condition': 'warn',
|
||||||
|
'no-nested-ternary': 'error',
|
||||||
|
'no-new-object': 'error',
|
||||||
|
'no-spaced-func': 'error',
|
||||||
|
'no-trailing-spaces': 'warn',
|
||||||
|
'no-unneeded-ternary': 'warn',
|
||||||
|
'one-var': ['error', 'never'],
|
||||||
|
'operator-assignment': ['warn', 'always'],
|
||||||
|
'operator-linebreak': ['error', 'before'],
|
||||||
|
'padded-blocks': ['warn', 'never'],
|
||||||
|
'quote-props': ['warn', 'as-needed'],
|
||||||
|
'quotes': ['warn', 'single'],
|
||||||
|
'semi': 'error',
|
||||||
|
'semi-spacing': 'error',
|
||||||
|
'keyword-spacing': 'warn',
|
||||||
|
'space-before-blocks': 'error',
|
||||||
|
'space-before-function-paren': ['error', {
|
||||||
|
anonymous: 'never',
|
||||||
|
named: 'never',
|
||||||
|
asyncArrow: 'always',
|
||||||
|
}],
|
||||||
|
'space-in-parens': 'warn',
|
||||||
|
'space-infix-ops': 'error',
|
||||||
|
'space-unary-ops': 'error',
|
||||||
|
'spaced-comment': 'warn',
|
||||||
|
|
||||||
|
// es6
|
||||||
|
'arrow-body-style': 'warn',
|
||||||
|
'arrow-parens': 'error',
|
||||||
|
'arrow-spacing': 'error',
|
||||||
|
'constructor-super': 'error',
|
||||||
|
'generator-star-spacing': 'warn',
|
||||||
|
'no-class-assign': 'error',
|
||||||
|
'no-const-assign': 'error',
|
||||||
|
'no-dupe-class-members': 'error',
|
||||||
|
'no-this-before-super': 'error',
|
||||||
|
'no-var': 'warn',
|
||||||
|
'object-shorthand': 'warn',
|
||||||
|
'prefer-arrow-callback': 'warn',
|
||||||
|
'prefer-const': 'warn',
|
||||||
|
'prefer-reflect': 'warn',
|
||||||
|
'prefer-spread': 'warn',
|
||||||
|
'prefer-template': 'warn',
|
||||||
|
'require-yield': 'error',
|
||||||
|
|
||||||
|
// react
|
||||||
|
'react/display-name': 'warn',
|
||||||
|
'react/forbid-prop-types': 'warn',
|
||||||
|
'react/jsx-boolean-value': 'warn',
|
||||||
|
'react/jsx-closing-bracket-location': 'off',
|
||||||
|
'react/jsx-curly-spacing': 'warn',
|
||||||
|
'react/jsx-handler-names': ['warn', {
|
||||||
|
eventHandlerPrefix: 'on',
|
||||||
|
eventHandlerPropPrefix: 'on',
|
||||||
|
}],
|
||||||
|
'react/jsx-indent-props': 'warn',
|
||||||
|
'react/jsx-key': 'warn',
|
||||||
|
'react/jsx-max-props-per-line': 'off',
|
||||||
|
'react/jsx-no-bind': ['error', {
|
||||||
|
allowArrowFunctions: true,
|
||||||
|
}],
|
||||||
|
'react/jsx-no-duplicate-props': 'warn',
|
||||||
|
'react/jsx-no-literals': 'off',
|
||||||
|
'react/jsx-no-undef': 'warn',
|
||||||
|
'react/jsx-pascal-case': 'warn',
|
||||||
|
'react/jsx-uses-react': 'warn',
|
||||||
|
'react/jsx-uses-vars': 'warn',
|
||||||
|
'react/jsx-no-comment-textnodes': 'warn',
|
||||||
|
'react/jsx-tag-spacing': ['warn', {
|
||||||
|
beforeClosing: 'never',
|
||||||
|
}],
|
||||||
|
'react/jsx-wrap-multilines': 'warn',
|
||||||
|
'react/no-deprecated': 'warn',
|
||||||
|
'react/no-did-mount-set-state': 'warn',
|
||||||
|
'react/no-did-update-set-state': 'warn',
|
||||||
|
'react/no-direct-mutation-state': 'warn',
|
||||||
|
'react/require-render-return': 'warn',
|
||||||
|
'react/no-is-mounted': 'warn',
|
||||||
|
'react/no-multi-comp': 'warn',
|
||||||
|
'react/no-string-refs': 'warn',
|
||||||
|
'react/no-unknown-property': 'warn',
|
||||||
|
'react/prefer-es6-class': 'warn',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/self-closing-comp': 'warn',
|
||||||
|
'react/sort-comp': ['warn', {
|
||||||
|
order: ['lifecycle', 'render', 'everything-else'],
|
||||||
|
}],
|
||||||
|
|
||||||
|
// Ely.by's custom rules
|
||||||
|
'@elyby/jsx-closing-bracket-location': 'warn',
|
||||||
|
},
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'camelcase': 'off',
|
||||||
|
'indent': 'off',
|
||||||
|
'no-array-constructor': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
|
||||||
|
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||||
|
'@typescript-eslint/array-type': ['error', 'generic'],
|
||||||
|
'@typescript-eslint/ban-types': 'error',
|
||||||
|
'@typescript-eslint/camelcase': 'error',
|
||||||
|
'@typescript-eslint/class-name-casing': 'error',
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': 'error',
|
||||||
|
'@typescript-eslint/indent': 'error',
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'error',
|
||||||
|
'@typescript-eslint/member-delimiter-style': 'error',
|
||||||
|
'@typescript-eslint/no-angle-bracket-type-assertion': 'error',
|
||||||
|
'@typescript-eslint/no-array-constructor': 'error',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'error',
|
||||||
|
'@typescript-eslint/no-misused-new': 'error',
|
||||||
|
'@typescript-eslint/no-namespace': 'error',
|
||||||
|
'@typescript-eslint/no-object-literal-type-assertion': 'error',
|
||||||
|
'@typescript-eslint/no-parameter-properties': 'error',
|
||||||
|
'@typescript-eslint/no-triple-slash-reference': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
'@typescript-eslint/no-use-before-define': 'error',
|
||||||
|
'@typescript-eslint/no-var-requires': 'error',
|
||||||
|
'@typescript-eslint/prefer-for-of': 'warn',
|
||||||
|
'@typescript-eslint/prefer-interface': 'error',
|
||||||
|
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||||
|
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
8
src/index.ts
Normal file
8
src/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import allRules from './rules';
|
||||||
|
import config from './configs/all';
|
||||||
|
|
||||||
|
export const rules = allRules;
|
||||||
|
|
||||||
|
export const configs = {
|
||||||
|
config,
|
||||||
|
};
|
9
src/rules/index.ts
Normal file
9
src/rules/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Rule } from 'eslint';
|
||||||
|
|
||||||
|
import { default as reactPropsBrackets } from './jsx-closing-bracket-location';
|
||||||
|
|
||||||
|
const rules: { [key: string]: Rule.RuleModule } = {
|
||||||
|
'jsx-closing-bracket-location': reactPropsBrackets,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rules;
|
54
src/rules/jsx-closing-bracket-location.ts
Normal file
54
src/rules/jsx-closing-bracket-location.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Node } from 'estree';
|
||||||
|
import { Rule } from 'eslint';
|
||||||
|
import { JSXOpeningElement } from '@babel/types';
|
||||||
|
|
||||||
|
const RIGHT_AFTER_PROP_TEXT = 'right after the last prop';
|
||||||
|
const ON_THE_NEXT_LINE_TEXT = 'on the next line';
|
||||||
|
|
||||||
|
const rule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: 'Validate closing bracket location in JSX',
|
||||||
|
category: 'Stylistic Issues',
|
||||||
|
recommended: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
create: (context) => ({
|
||||||
|
'JSXOpeningElement:exit': (node: Node|JSXOpeningElement) => {
|
||||||
|
const { start: tagStart, end: componentEnd } = node.loc!;
|
||||||
|
|
||||||
|
let expectedClosingBracketLine: number;
|
||||||
|
const isSelfClosing = (node as JSXOpeningElement).selfClosing;
|
||||||
|
const attributes = (node as JSXOpeningElement).attributes;
|
||||||
|
if (attributes.length === 0) {
|
||||||
|
expectedClosingBracketLine = tagStart.line;
|
||||||
|
} else {
|
||||||
|
const { start: firstPropStart } = attributes[0].loc!;
|
||||||
|
const { end: lastPropEnd } = attributes[attributes.length - 1].loc!;
|
||||||
|
|
||||||
|
expectedClosingBracketLine = lastPropEnd.line;
|
||||||
|
if (firstPropStart.line !== tagStart.line) {
|
||||||
|
expectedClosingBracketLine++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (componentEnd.line === expectedClosingBracketLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.report({
|
||||||
|
loc: {
|
||||||
|
start: context.getSourceCode().getLastToken(node as Node, isSelfClosing ? 2 : 1)!.loc.end,
|
||||||
|
end: componentEnd,
|
||||||
|
},
|
||||||
|
message: 'The closing bracket must be placed {{ at }}',
|
||||||
|
data: {
|
||||||
|
at: expectedClosingBracketLine > componentEnd.line ? ON_THE_NEXT_LINE_TEXT : RIGHT_AFTER_PROP_TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rule;
|
180
tests/lib/rules/jsx-closing-bracket-location.ts
Normal file
180
tests/lib/rules/jsx-closing-bracket-location.ts
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Requirements
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import { RuleTester } from 'eslint';
|
||||||
|
|
||||||
|
import rule from './../../../src/rules/jsx-closing-bracket-location';
|
||||||
|
|
||||||
|
const parserOptions = {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MESSAGE_ON_THE_NEXT_LINE = 'The closing bracket must be placed on the next line';
|
||||||
|
const MESSAGE_RIGHT_AFTER_PROP = 'The closing bracket must be placed right after the last prop';
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const ruleTester = new RuleTester({parserOptions});
|
||||||
|
ruleTester.run('react-props-brackets', rule, {
|
||||||
|
valid: [
|
||||||
|
'<Component></Component>',
|
||||||
|
'<Component />',
|
||||||
|
'<Component prop={"value"}></Component>',
|
||||||
|
'<Component {...props1}></Component>',
|
||||||
|
'<Component prop={"value"}/>',
|
||||||
|
'<Component {...props1}/>',
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
'}}/>',
|
||||||
|
].join('\n'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
' prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
' }}',
|
||||||
|
'/>',
|
||||||
|
].join('\n'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
'}}></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
' prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
' }}',
|
||||||
|
'></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
'></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 1,
|
||||||
|
column: 11,
|
||||||
|
endLine: 2,
|
||||||
|
endColumn: 2,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
'/>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 1,
|
||||||
|
column: 11,
|
||||||
|
endLine: 2,
|
||||||
|
endColumn: 3,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={"value"}',
|
||||||
|
'></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 1,
|
||||||
|
column: 26,
|
||||||
|
endLine: 2,
|
||||||
|
endColumn: 2,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={"value"}',
|
||||||
|
'/>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 1,
|
||||||
|
column: 26,
|
||||||
|
endLine: 2,
|
||||||
|
endColumn: 3,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
'}}',
|
||||||
|
'/>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 3,
|
||||||
|
column: 3,
|
||||||
|
endLine: 4,
|
||||||
|
endColumn: 3,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
' prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
' }}/>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_ON_THE_NEXT_LINE,
|
||||||
|
line: 4,
|
||||||
|
column: 7,
|
||||||
|
endLine: 4,
|
||||||
|
endColumn: 9,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
'}}',
|
||||||
|
'></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_RIGHT_AFTER_PROP,
|
||||||
|
line: 3,
|
||||||
|
column: 3,
|
||||||
|
endLine: 4,
|
||||||
|
endColumn: 2,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
'<Component',
|
||||||
|
' prop={{',
|
||||||
|
' key: "value",',
|
||||||
|
' }}></Component>',
|
||||||
|
].join('\n'),
|
||||||
|
errors: [{
|
||||||
|
message: MESSAGE_ON_THE_NEXT_LINE,
|
||||||
|
line: 4,
|
||||||
|
column: 7,
|
||||||
|
endLine: 4,
|
||||||
|
endColumn: 8,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"es7",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
|
],
|
||||||
|
"jsx": "preserve",
|
||||||
|
"declaration": false,
|
||||||
|
"skipLibCheck": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"baseUrl": "src",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user