@tuomashatakka/eslint-config 2.6.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/publish.yml +45 -0
- package/AGENTS.md +29 -0
- package/bun.lock +60 -102
- package/eslint.config.mjs +1 -0
- package/index.mjs +7 -21
- package/package.json +11 -19
- package/plugins/no-inline-types/index.mjs +11 -0
- package/plugins/no-inline-types/rules/no-inline-multiline-types.mjs +181 -0
- package/plugins/omit/index.mjs +8 -0
- package/plugins/omit/rules/omit-unnecessary-parens-brackets.mjs +329 -0
- package/plugins/omit/utils.mjs +91 -0
- package/plugins/react-strict/index.mjs +19 -0
- package/plugins/react-strict/rules/jsx-prop-layout.mjs +100 -0
- package/plugins/react-strict/rules/no-complex-jsx-map.mjs +66 -0
- package/plugins/react-strict/rules/no-jsx-value-calculations.mjs +99 -0
- package/plugins/react-strict/rules/no-nested-divs.mjs +59 -0
- package/plugins/react-strict/rules/no-style-prop.mjs +43 -0
- package/plugins/react-strict/rules/prefer-no-use-effect.mjs +26 -0
- package/plugins/whitespaced/index.mjs +15 -0
- package/plugins/whitespaced/rules/aligned-assignments.mjs +385 -0
- package/plugins/whitespaced/rules/block-padding.mjs +289 -0
- package/plugins/whitespaced/rules/class-property-grouping.mjs +370 -0
- package/plugins/whitespaced/rules/consistent-line-spacing.mjs +266 -0
- package/plugins/whitespaced/rules/multiline-format.mjs +533 -0
- package/rules.mjs +101 -95
- package/test/fixtures/basic-javascript.js +5 -4
- package/test/fixtures/complex-patterns.ts +9 -7
- package/test/fixtures/edge-cases.js +12 -7
- package/test/fixtures/jsx-formatting.jsx +5 -4
- package/test/fixtures/omit-parens.invalid.ts +12 -0
- package/test/fixtures/omit-parens.valid.ts +13 -0
- package/test/fixtures/react-component.tsx +7 -6
- package/test/fixtures/react-strict.invalid.tsx +31 -0
- package/test/fixtures/react-strict.valid.tsx +76 -0
- package/test/fixtures/whitespaced-docstring.invalid.ts +10 -0
- package/test/fixtures/whitespaced-docstring.valid.ts +16 -0
- package/test/fixtures/whitespaced-members.invalid.ts +22 -0
- package/test/fixtures/whitespaced-members.valid.ts +13 -0
- package/test/fixtures/whitespaced-multiline.invalid.ts +8 -0
- package/test/fixtures/whitespaced-multiline.valid.ts +15 -0
- package/test/fixtures/whitespaced-types.valid.ts +5 -0
- package/test/fixtures/whitespaced.valid.ts +45 -0
- package/test/format-cases.mjs +13 -14
- package/test/test-runner.mjs +128 -47
package/index.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import stylistic from '@stylistic/eslint-plugin'
|
|
2
|
-
import stylisticJsx from '@stylistic/eslint-plugin-jsx'
|
|
3
|
-
import tsplugin from '@typescript-eslint/eslint-plugin'
|
|
4
2
|
import importPlugin from 'eslint-plugin-import'
|
|
5
|
-
import noInlineMultilineTypesPlugin from '
|
|
6
|
-
import whitespacedPlugin from '
|
|
7
|
-
import omitPlugin from '
|
|
3
|
+
import noInlineMultilineTypesPlugin from './plugins/no-inline-types/index.mjs'
|
|
4
|
+
import whitespacedPlugin from './plugins/whitespaced/index.mjs'
|
|
5
|
+
import omitPlugin from './plugins/omit/index.mjs'
|
|
8
6
|
import react from 'eslint-plugin-react'
|
|
9
7
|
import reactHooks from 'eslint-plugin-react-hooks'
|
|
10
8
|
import globals from 'globals'
|
|
11
9
|
import tseslint from 'typescript-eslint'
|
|
10
|
+
import reactStrictPlugin from './plugins/react-strict/index.mjs'
|
|
12
11
|
import { rules } from './rules.mjs'
|
|
13
12
|
|
|
14
13
|
|
|
@@ -17,10 +16,11 @@ const plugins = {
|
|
|
17
16
|
'react-hooks': reactHooks,
|
|
18
17
|
'import': importPlugin,
|
|
19
18
|
'@stylistic': stylistic,
|
|
20
|
-
'@typescript-eslint':
|
|
19
|
+
'@typescript-eslint': tseslint.plugin,
|
|
21
20
|
'omit': omitPlugin,
|
|
22
21
|
'no-inline-types': noInlineMultilineTypesPlugin,
|
|
23
22
|
'whitespaced': whitespacedPlugin,
|
|
23
|
+
'react-strict': reactStrictPlugin,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export { rules }
|
|
@@ -38,17 +38,7 @@ export const baseConfig = {
|
|
|
38
38
|
'import/internal-regex': '^@/(.+)',
|
|
39
39
|
},
|
|
40
40
|
ignores: [ '**/node_modules/**' ],
|
|
41
|
-
plugins
|
|
42
|
-
...plugins,
|
|
43
|
-
// Merge stylistic JSX plugin into the main stylistic namespace
|
|
44
|
-
'@stylistic': {
|
|
45
|
-
...stylistic,
|
|
46
|
-
rules: {
|
|
47
|
-
...stylistic.rules,
|
|
48
|
-
...stylisticJsx.rules,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
41
|
+
plugins,
|
|
52
42
|
rules,
|
|
53
43
|
}
|
|
54
44
|
|
|
@@ -60,9 +50,5 @@ export const config = tseslint.config(
|
|
|
60
50
|
...tseslint.configs.recommended,
|
|
61
51
|
baseConfig)
|
|
62
52
|
|
|
63
|
-
/**
|
|
64
|
-
* @summary ESLint configuration
|
|
65
|
-
* @description Opinionated yet functional AF base config for ESLint
|
|
66
|
-
*/
|
|
67
53
|
|
|
68
54
|
export default config
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuomashatakka/eslint-config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Default eslint configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -26,28 +26,20 @@
|
|
|
26
26
|
"typescript": "*"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"eslint": "^
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"globals": "^16.3.0",
|
|
34
|
-
"typescript": "^5.8.3",
|
|
29
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
30
|
+
"@types/react": "^19.2.14",
|
|
31
|
+
"eslint": "^9.39.0",
|
|
32
|
+
"eslint-plugin-import": "^2.32.0",
|
|
35
33
|
"eslint-plugin-react": "^7.37.5",
|
|
36
34
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"eslint
|
|
40
|
-
"eslint-plugin-omit-unnecessary": "^0.0.3",
|
|
41
|
-
"eslint-plugin-whitespaced": "^1.0.2"
|
|
35
|
+
"globals": "^16.5.0",
|
|
36
|
+
"typescript": "^5.9.0",
|
|
37
|
+
"typescript-eslint": "^8.58.0"
|
|
42
38
|
},
|
|
43
39
|
"devDependencies": {
|
|
44
|
-
"@eslint/
|
|
45
|
-
"@eslint/
|
|
46
|
-
"@eslint/js": "^9.31.0",
|
|
47
|
-
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
|
40
|
+
"@eslint/eslintrc": "^3.3.5",
|
|
41
|
+
"@eslint/js": "^9.39.0",
|
|
48
42
|
"@types/eslint__eslintrc": "^2.1.2",
|
|
49
|
-
"@types/eslint__js": "^8.42.3"
|
|
50
|
-
"@typescript-eslint/parser": "^8.37.0",
|
|
51
|
-
"eslint-plugin-block-padding": "^0.0.3"
|
|
43
|
+
"@types/eslint__js": "^8.42.3"
|
|
52
44
|
}
|
|
53
45
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Disallows inline TSTypeLiteral annotations that span multiple lines.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function toPascalCase (str) {
|
|
6
|
+
if (!str)
|
|
7
|
+
return ''
|
|
8
|
+
str = str.replace(/_([a-z])/g, (match, char) => char.toUpperCase())
|
|
9
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
14
|
+
const rule = {
|
|
15
|
+
meta: {
|
|
16
|
+
type: 'suggestion',
|
|
17
|
+
docs: {
|
|
18
|
+
description: 'Disallows inline TSTypeLiteral annotations',
|
|
19
|
+
recommended: false,
|
|
20
|
+
url: null,
|
|
21
|
+
},
|
|
22
|
+
fixable: "code",
|
|
23
|
+
schema: [],
|
|
24
|
+
messages: {
|
|
25
|
+
inlineType: 'Inline type literal annotations are disallowed. Extract to a named interface or type alias.',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
create (context) {
|
|
30
|
+
const sourceCode = context.getSourceCode()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
function findInsertionPointAncestor (startNode) {
|
|
34
|
+
let currentNode = startNode
|
|
35
|
+
const targetAncestorTypes = new Set([
|
|
36
|
+
'VariableDeclaration',
|
|
37
|
+
'FunctionDeclaration',
|
|
38
|
+
'ClassDeclaration',
|
|
39
|
+
'TSInterfaceDeclaration',
|
|
40
|
+
'TSTypeAliasDeclaration',
|
|
41
|
+
])
|
|
42
|
+
const validParentContextTypes = new Set([
|
|
43
|
+
'Program',
|
|
44
|
+
'BlockStatement',
|
|
45
|
+
'ExportNamedDeclaration',
|
|
46
|
+
'ExportDefaultDeclaration',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
while (currentNode.parent) {
|
|
50
|
+
if (targetAncestorTypes.has(currentNode.type) && currentNode.parent && validParentContextTypes.has(currentNode.parent.type)) {
|
|
51
|
+
if (currentNode.parent.type === 'ExportNamedDeclaration' || currentNode.parent.type === 'ExportDefaultDeclaration')
|
|
52
|
+
return currentNode.parent
|
|
53
|
+
return currentNode
|
|
54
|
+
}
|
|
55
|
+
if ((currentNode.type === 'ExportNamedDeclaration' || currentNode.type === 'ExportDefaultDeclaration') &&
|
|
56
|
+
currentNode.declaration && targetAncestorTypes.has(currentNode.declaration.type))
|
|
57
|
+
return currentNode
|
|
58
|
+
|
|
59
|
+
currentNode = currentNode.parent
|
|
60
|
+
if (currentNode.type === 'Program') {
|
|
61
|
+
if (targetAncestorTypes.has(startNode.type))
|
|
62
|
+
return startNode
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
function deriveTypeName (typeAnnotationNode) {
|
|
71
|
+
const parent = typeAnnotationNode.parent
|
|
72
|
+
if (!parent)
|
|
73
|
+
return 'ExtractedType'
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
if (parent.type === 'Identifier' && parent.parent && parent.parent.type === 'VariableDeclarator')
|
|
77
|
+
return toPascalCase(parent.name) + 'Type'
|
|
78
|
+
|
|
79
|
+
if (parent.type === 'Identifier' && parent.parent && (
|
|
80
|
+
parent.parent.type === 'FunctionDeclaration' ||
|
|
81
|
+
parent.parent.type === 'FunctionExpression' ||
|
|
82
|
+
parent.parent.type === 'ArrowFunctionExpression' ||
|
|
83
|
+
parent.parent.type === 'TSParameterProperty'
|
|
84
|
+
)) {
|
|
85
|
+
if (parent.parent.params && parent.parent.params.includes(parent))
|
|
86
|
+
return toPascalCase(parent.name) + 'Type'
|
|
87
|
+
if (parent.parent.type === 'TSParameterProperty' && parent.parent.parameter === parent)
|
|
88
|
+
return toPascalCase(parent.name) + 'Type'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (parent.type === 'ObjectPattern' && parent.parent && (
|
|
92
|
+
parent.parent.type === 'FunctionDeclaration' ||
|
|
93
|
+
parent.parent.type === 'FunctionExpression' ||
|
|
94
|
+
parent.parent.type === 'ArrowFunctionExpression'
|
|
95
|
+
)) {
|
|
96
|
+
const funcName = parent.parent.id?.name
|
|
97
|
+
return funcName ? toPascalCase(funcName) + 'Props' : 'PropsType'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (parent.type === 'ArrayPattern' && parent.parent && (
|
|
101
|
+
parent.parent.type === 'FunctionDeclaration' ||
|
|
102
|
+
parent.parent.type === 'FunctionExpression' ||
|
|
103
|
+
parent.parent.type === 'ArrowFunctionExpression'
|
|
104
|
+
))
|
|
105
|
+
return 'ParamsType'
|
|
106
|
+
|
|
107
|
+
if (parent.type === 'PropertyDefinition' && parent.key && parent.key.type === 'Identifier')
|
|
108
|
+
return toPascalCase(parent.key.name) + 'Type'
|
|
109
|
+
|
|
110
|
+
if ((parent.type === 'FunctionDeclaration' || parent.type === 'FunctionExpression' || parent.type === 'ArrowFunctionExpression') &&
|
|
111
|
+
parent.returnType === typeAnnotationNode) {
|
|
112
|
+
const funcName = parent.id?.name
|
|
113
|
+
if (!funcName && parent.parent && parent.parent.type === 'VariableDeclarator' && parent.parent.id.type === 'Identifier')
|
|
114
|
+
return toPascalCase(parent.parent.id.name) + 'ReturnType'
|
|
115
|
+
return toPascalCase(funcName || 'Function') + 'ReturnType'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (parent.type === 'TSParameterProperty' && parent.parameter && parent.parameter.type === 'Identifier')
|
|
119
|
+
return toPascalCase(parent.parameter.name) + 'Type'
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
console.error('Error deriving type name:', e)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return 'ExtractedType'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
function checkMultilineLiteralInAnnotation (typeAnnotationNode) {
|
|
130
|
+
if (!typeAnnotationNode || typeAnnotationNode.type !== 'TSTypeAnnotation')
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
const literalNode = typeAnnotationNode.typeAnnotation
|
|
134
|
+
|
|
135
|
+
if (literalNode && literalNode.type === 'TSTypeLiteral')
|
|
136
|
+
context.report({
|
|
137
|
+
node: literalNode,
|
|
138
|
+
messageId: 'inlineType',
|
|
139
|
+
fix (fixer) {
|
|
140
|
+
const newTypeName = deriveTypeName(typeAnnotationNode)
|
|
141
|
+
const literalText = sourceCode.getText(literalNode)
|
|
142
|
+
const typeAliasString = `type ${newTypeName} = ${literalText};\n\n`
|
|
143
|
+
const insertionAncestor = findInsertionPointAncestor(typeAnnotationNode)
|
|
144
|
+
|
|
145
|
+
if (!insertionAncestor)
|
|
146
|
+
return null
|
|
147
|
+
|
|
148
|
+
return [
|
|
149
|
+
fixer.insertTextBefore(insertionAncestor, typeAliasString),
|
|
150
|
+
fixer.replaceText(literalNode, newTypeName),
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
VariableDeclarator (node) {
|
|
159
|
+
if (node.id && node.id.typeAnnotation)
|
|
160
|
+
checkMultilineLiteralInAnnotation(node.id.typeAnnotation)
|
|
161
|
+
},
|
|
162
|
+
PropertyDefinition (node) {
|
|
163
|
+
if (node.typeAnnotation)
|
|
164
|
+
checkMultilineLiteralInAnnotation(node.typeAnnotation)
|
|
165
|
+
},
|
|
166
|
+
':function' (node) {
|
|
167
|
+
if (node.returnType)
|
|
168
|
+
checkMultilineLiteralInAnnotation(node.returnType)
|
|
169
|
+
if (node.params)
|
|
170
|
+
for (const param of node.params)
|
|
171
|
+
if (param && param.typeAnnotation)
|
|
172
|
+
checkMultilineLiteralInAnnotation(param.typeAnnotation)
|
|
173
|
+
else if (param.type === 'TSParameterProperty' && param.parameter && param.parameter.typeAnnotation)
|
|
174
|
+
checkMultilineLiteralInAnnotation(param.parameter.typeAnnotation)
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
export default rule
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to omit unnecessary parentheses, brackets, and braces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isValidDotNotationIdentifier, isDeclaration, isParenthesized } from '../utils.mjs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Omit unnecessary parentheses, brackets, and braces',
|
|
13
|
+
category: "Stylistic Issues",
|
|
14
|
+
recommended: false,
|
|
15
|
+
url: null,
|
|
16
|
+
},
|
|
17
|
+
fixable: "code",
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
unnecessaryParens: "Unnecessary parentheses",
|
|
21
|
+
useDotNotation: "Use dot notation instead of bracket notation",
|
|
22
|
+
unnecessaryBraces: "Unnecessary curly braces",
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
create (context) {
|
|
26
|
+
const sourceCode = context.getSourceCode()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
function checkDotNotation (node) {
|
|
30
|
+
if (node.computed &&
|
|
31
|
+
node.property.type === 'Literal' &&
|
|
32
|
+
typeof node.property.value === 'string' &&
|
|
33
|
+
isValidDotNotationIdentifier(node.property.value) &&
|
|
34
|
+
!node.optional
|
|
35
|
+
) context.report({
|
|
36
|
+
node: node.property,
|
|
37
|
+
messageId: "useDotNotation",
|
|
38
|
+
fix(fixer) {
|
|
39
|
+
const propText = node.property.value
|
|
40
|
+
const openingBracket = sourceCode.getTokenBefore(node.property)
|
|
41
|
+
const closingBracket = sourceCode.getTokenAfter(node.property)
|
|
42
|
+
|
|
43
|
+
if (!openingBracket || openingBracket.value !== '[' || !closingBracket || closingBracket.value !== ']')
|
|
44
|
+
return null
|
|
45
|
+
|
|
46
|
+
const textBetweenObjectAndBracket = sourceCode.text.slice(node.object.range[1], openingBracket.range[0])
|
|
47
|
+
const textBetweenPropertyAndBracket = sourceCode.text.slice(node.property.range[1], closingBracket.range[0])
|
|
48
|
+
|
|
49
|
+
if (textBetweenObjectAndBracket.trim() !== '' || textBetweenPropertyAndBracket.trim() !== '')
|
|
50
|
+
return null
|
|
51
|
+
|
|
52
|
+
return fixer.replaceTextRange(
|
|
53
|
+
[openingBracket.range[0], closingBracket.range[1]],
|
|
54
|
+
`.${propText}`
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function checkUnnecessaryParens (node) {
|
|
62
|
+
if (!node || !isParenthesized(node, sourceCode))
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
const parent = node.parent
|
|
66
|
+
const tokenBefore = sourceCode.getTokenBefore(node)
|
|
67
|
+
const tokenAfter = sourceCode.getTokenAfter(node)
|
|
68
|
+
|
|
69
|
+
if (!tokenBefore || !tokenAfter)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
const reportAndFixUnnecessaryParens = reportedNode => {
|
|
73
|
+
context.report({
|
|
74
|
+
node: reportedNode,
|
|
75
|
+
loc: { start: tokenBefore.loc.start, end: tokenAfter.loc.end },
|
|
76
|
+
messageId: 'unnecessaryParens',
|
|
77
|
+
fix: (fixer) => [ fixer.remove(tokenBefore), fixer.remove(tokenAfter) ],
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (parent && isParenthesized(parent, sourceCode)) {
|
|
82
|
+
const parentTokenBefore = sourceCode.getTokenBefore(parent)
|
|
83
|
+
const parentTokenAfter = sourceCode.getTokenAfter(parent)
|
|
84
|
+
if (parentTokenBefore && parentTokenAfter &&
|
|
85
|
+
parentTokenBefore.range[0] === tokenBefore.range[0] - 1 &&
|
|
86
|
+
parentTokenAfter.range[1] === tokenAfter.range[1] + 1) {
|
|
87
|
+
reportAndFixUnnecessaryParens(node)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (node.type === 'Literal' || node.type === 'Identifier') {
|
|
93
|
+
if (parent && (
|
|
94
|
+
parent.type === 'VariableDeclarator' && parent.init === node ||
|
|
95
|
+
parent.type === 'ReturnStatement' && parent.argument === node ||
|
|
96
|
+
parent.type === 'ExpressionStatement' && parent.expression === node ||
|
|
97
|
+
parent.type === 'ArrayExpression' && parent.elements.includes(node) ||
|
|
98
|
+
parent.type === 'Property' && parent.value === node && !parent.method && !parent.shorthand
|
|
99
|
+
)) {
|
|
100
|
+
reportAndFixUnnecessaryParens(node)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (node.type === 'AwaitExpression' || node.type === 'YieldExpression') {
|
|
106
|
+
if (parent && (
|
|
107
|
+
parent.type === 'ExpressionStatement' ||
|
|
108
|
+
parent.type === 'VariableDeclarator' ||
|
|
109
|
+
parent.type === 'ReturnStatement' ||
|
|
110
|
+
parent.type === 'AssignmentExpression' && parent.right === node
|
|
111
|
+
)) {
|
|
112
|
+
reportAndFixUnnecessaryParens(node)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (parent && parent.type === 'ArrowFunctionExpression' && parent.body === node) {
|
|
118
|
+
if (node.type !== 'ObjectExpression') {
|
|
119
|
+
reportAndFixUnnecessaryParens(node)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (parent && parent.type === 'ReturnStatement' && parent.argument === node &&
|
|
125
|
+
(node.type === 'JSXElement' || node.type === 'JSXFragment')) {
|
|
126
|
+
context.report({
|
|
127
|
+
node,
|
|
128
|
+
loc: { start: tokenBefore.loc.start, end: tokenAfter.loc.end },
|
|
129
|
+
messageId: 'unnecessaryParens',
|
|
130
|
+
fix: (fixer) => {
|
|
131
|
+
const returnToken = sourceCode.getFirstToken(parent)
|
|
132
|
+
return [
|
|
133
|
+
fixer.replaceTextRange([ returnToken.range[1], node.range[0] ], ' '),
|
|
134
|
+
fixer.removeRange([ node.range[1], tokenAfter.range[1] ]),
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
function checkUnnecessaryBraces (blockStatement, controllingNode) {
|
|
144
|
+
if (!blockStatement || blockStatement.type !== 'BlockStatement')
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if (blockStatement.body.length !== 1)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
const singleStatement = blockStatement.body[0]
|
|
151
|
+
|
|
152
|
+
if (isDeclaration(singleStatement))
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
if (controllingNode && controllingNode.type === 'IfStatement' &&
|
|
156
|
+
!controllingNode.alternate &&
|
|
157
|
+
singleStatement.type === 'IfStatement')
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
if (singleStatement.type === 'BlockStatement')
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
context.report({
|
|
164
|
+
node: blockStatement,
|
|
165
|
+
messageId: 'unnecessaryBraces',
|
|
166
|
+
fix (fixer) {
|
|
167
|
+
const firstToken = sourceCode.getFirstToken(blockStatement)
|
|
168
|
+
const lastToken = sourceCode.getLastToken(blockStatement)
|
|
169
|
+
const innerText = sourceCode.getText(singleStatement)
|
|
170
|
+
|
|
171
|
+
if (!firstToken || firstToken.value !== '{' || !lastToken || lastToken.value !== '}')
|
|
172
|
+
return null
|
|
173
|
+
|
|
174
|
+
const firstStatementToken = sourceCode.getFirstToken(singleStatement)
|
|
175
|
+
const lastStatementToken = sourceCode.getLastToken(singleStatement)
|
|
176
|
+
|
|
177
|
+
if (firstStatementToken && firstToken.range[1] < firstStatementToken.range[0]) {
|
|
178
|
+
const tokensBefore = sourceCode.getTokensBetween(firstToken, firstStatementToken, {
|
|
179
|
+
includeComments: false,
|
|
180
|
+
})
|
|
181
|
+
if (tokensBefore.length > 0)
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (lastStatementToken && lastToken.range[1] < lastStatementToken.range[0]) {
|
|
186
|
+
const tokensAfter = sourceCode.getTokensBetween(lastStatementToken, lastToken, {
|
|
187
|
+
includeComments: false,
|
|
188
|
+
})
|
|
189
|
+
if (tokensAfter.length > 0)
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return fixer.replaceTextRange(blockStatement.range, innerText)
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
function checkArrowFunctionBraces (node) {
|
|
200
|
+
if (!node.body || node.body.type !== 'BlockStatement')
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
const blockBody = node.body
|
|
204
|
+
if (blockBody.body.length !== 1)
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
const singleStatement = blockBody.body[0]
|
|
208
|
+
if (singleStatement.type !== 'ReturnStatement')
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
if (!singleStatement.argument)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
const returnedExpression = singleStatement.argument
|
|
215
|
+
const needsParens = returnedExpression.type === 'ObjectExpression'
|
|
216
|
+
|
|
217
|
+
context.report({
|
|
218
|
+
node: blockBody,
|
|
219
|
+
messageId: 'unnecessaryBraces',
|
|
220
|
+
fix (fixer) {
|
|
221
|
+
const arrowToken = sourceCode.getTokenBefore(blockBody)
|
|
222
|
+
if (!arrowToken || arrowToken.value !== '=>')
|
|
223
|
+
return null
|
|
224
|
+
|
|
225
|
+
const firstBlockToken = sourceCode.getFirstToken(blockBody)
|
|
226
|
+
const lastBlockToken = sourceCode.getLastToken(blockBody)
|
|
227
|
+
if (!firstBlockToken || firstBlockToken.value !== '{' || !lastBlockToken || lastBlockToken.value !== '}')
|
|
228
|
+
return null
|
|
229
|
+
|
|
230
|
+
let expressionText = sourceCode.getText(returnedExpression)
|
|
231
|
+
|
|
232
|
+
const textBeforeBlock = sourceCode.text.slice(arrowToken.range[1], firstBlockToken.range[0])
|
|
233
|
+
if (textBeforeBlock.trim())
|
|
234
|
+
return null
|
|
235
|
+
|
|
236
|
+
const returnToken = sourceCode.getFirstToken(singleStatement)
|
|
237
|
+
const textBeforeReturn = sourceCode.text.slice(firstBlockToken.range[1], returnToken.range[0])
|
|
238
|
+
const textAfterReturn = sourceCode.text.slice(returnToken.range[1], returnedExpression.range[0])
|
|
239
|
+
const textAfterExpr = sourceCode.text.slice(returnedExpression.range[1], lastBlockToken.range[0])
|
|
240
|
+
|
|
241
|
+
if (textBeforeReturn.trim() || textAfterReturn.trim() || textAfterExpr.trim())
|
|
242
|
+
return null
|
|
243
|
+
|
|
244
|
+
if (needsParens && !isParenthesized(returnedExpression, sourceCode))
|
|
245
|
+
expressionText = `(${expressionText})`
|
|
246
|
+
|
|
247
|
+
let replacementText = (needsParens || expressionText.startsWith('(') ? '' : ' ') + expressionText
|
|
248
|
+
replacementText = replacementText.replace(/\s+/g, ' ')
|
|
249
|
+
return fixer.replaceTextRange(blockBody.range, replacementText.trim())
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
MemberExpression: checkDotNotation,
|
|
257
|
+
|
|
258
|
+
VariableDeclarator (node) {
|
|
259
|
+
checkUnnecessaryParens(node.init)
|
|
260
|
+
},
|
|
261
|
+
ExpressionStatement (node) {
|
|
262
|
+
checkUnnecessaryParens(node.expression)
|
|
263
|
+
},
|
|
264
|
+
ReturnStatement (node) {
|
|
265
|
+
checkUnnecessaryParens(node.argument)
|
|
266
|
+
},
|
|
267
|
+
BinaryExpression (node) {
|
|
268
|
+
checkUnnecessaryParens(node.left); checkUnnecessaryParens(node.right)
|
|
269
|
+
},
|
|
270
|
+
LogicalExpression (node) {
|
|
271
|
+
checkUnnecessaryParens(node.left); checkUnnecessaryParens(node.right)
|
|
272
|
+
},
|
|
273
|
+
UnaryExpression (node) {
|
|
274
|
+
checkUnnecessaryParens(node.argument)
|
|
275
|
+
},
|
|
276
|
+
AwaitExpression (node) {
|
|
277
|
+
checkUnnecessaryParens(node.argument)
|
|
278
|
+
},
|
|
279
|
+
YieldExpression (node) {
|
|
280
|
+
checkUnnecessaryParens(node.argument)
|
|
281
|
+
},
|
|
282
|
+
ConditionalExpression (node) {
|
|
283
|
+
checkUnnecessaryParens(node.test)
|
|
284
|
+
checkUnnecessaryParens(node.consequent)
|
|
285
|
+
checkUnnecessaryParens(node.alternate)
|
|
286
|
+
},
|
|
287
|
+
CallExpression (node) {
|
|
288
|
+
node.arguments.forEach(arg => checkUnnecessaryParens(arg))
|
|
289
|
+
checkUnnecessaryParens(node.callee)
|
|
290
|
+
},
|
|
291
|
+
NewExpression (node) {
|
|
292
|
+
node.arguments.forEach(arg => checkUnnecessaryParens(arg))
|
|
293
|
+
checkUnnecessaryParens(node.callee)
|
|
294
|
+
},
|
|
295
|
+
ArrayExpression (node) {
|
|
296
|
+
node.elements.forEach(arg => checkUnnecessaryParens(arg))
|
|
297
|
+
},
|
|
298
|
+
Property (node) {
|
|
299
|
+
checkUnnecessaryParens(node.value)
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
IfStatement (node) {
|
|
303
|
+
checkUnnecessaryBraces(node.consequent, node)
|
|
304
|
+
if (node.alternate && node.alternate.type !== 'IfStatement')
|
|
305
|
+
checkUnnecessaryBraces(node.alternate, node)
|
|
306
|
+
},
|
|
307
|
+
ForStatement (node) {
|
|
308
|
+
checkUnnecessaryBraces(node.body, node)
|
|
309
|
+
},
|
|
310
|
+
ForInStatement (node) {
|
|
311
|
+
checkUnnecessaryBraces(node.body, node)
|
|
312
|
+
},
|
|
313
|
+
ForOfStatement (node) {
|
|
314
|
+
checkUnnecessaryBraces(node.body, node)
|
|
315
|
+
},
|
|
316
|
+
WhileStatement (node) {
|
|
317
|
+
checkUnnecessaryBraces(node.body, node)
|
|
318
|
+
},
|
|
319
|
+
DoWhileStatement (node) {
|
|
320
|
+
checkUnnecessaryBraces(node.body, node)
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
ArrowFunctionExpression (node) {
|
|
324
|
+
checkUnnecessaryParens(node.body)
|
|
325
|
+
checkArrowFunctionBraces(node)
|
|
326
|
+
},
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
}
|