eslint-plugin-unicorn-ts 0.0.1-security → 50.0.1
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.
Potentially problematic release.
This version of eslint-plugin-unicorn-ts might be problematic. Click here for more details.
- package/configs/all.js +6 -0
- package/configs/flat-config-base.js +10 -0
- package/configs/legacy-config-base.js +10 -0
- package/configs/recommended.js +117 -0
- package/index.js +91 -0
- package/license +9 -0
- package/package.json +186 -4
- package/readme.md +356 -0
- package/rules/ast/call-or-new-expression.js +127 -0
- package/rules/ast/function-types.js +5 -0
- package/rules/ast/index.js +39 -0
- package/rules/ast/is-arrow-function-body.js +7 -0
- package/rules/ast/is-empty-node.js +20 -0
- package/rules/ast/is-expression-statement.js +11 -0
- package/rules/ast/is-function.js +8 -0
- package/rules/ast/is-member-expression.js +101 -0
- package/rules/ast/is-method-call.js +65 -0
- package/rules/ast/is-reference-identifier.js +156 -0
- package/rules/ast/is-static-require.js +14 -0
- package/rules/ast/is-undefined.js +7 -0
- package/rules/ast/literal.js +29 -0
- package/rules/better-regex.js +144 -0
- package/rules/catch-error-name.js +136 -0
- package/rules/consistent-destructuring.js +168 -0
- package/rules/consistent-function-scoping.js +223 -0
- package/rules/custom-error-definition.js +215 -0
- package/rules/empty-brace-spaces.js +72 -0
- package/rules/error-message.js +104 -0
- package/rules/escape-case.js +63 -0
- package/rules/expiring-todo-comments.js +580 -0
- package/rules/explicit-length-check.js +229 -0
- package/rules/filename-case.js +258 -0
- package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
- package/rules/fix/append-argument.js +20 -0
- package/rules/fix/extend-fix-range.js +15 -0
- package/rules/fix/fix-space-around-keywords.js +35 -0
- package/rules/fix/index.js +23 -0
- package/rules/fix/remove-argument.js +32 -0
- package/rules/fix/remove-member-expression-property.js +11 -0
- package/rules/fix/remove-method-call.js +20 -0
- package/rules/fix/remove-parentheses.js +11 -0
- package/rules/fix/remove-spaces-after.js +14 -0
- package/rules/fix/rename-variable.js +9 -0
- package/rules/fix/replace-argument.js +8 -0
- package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
- package/rules/fix/replace-reference-identifier.js +35 -0
- package/rules/fix/replace-string-literal.js +11 -0
- package/rules/fix/replace-string-raw.js +14 -0
- package/rules/fix/replace-template-element.js +11 -0
- package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
- package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
- package/rules/import-style.js +364 -0
- package/rules/new-for-builtins.js +85 -0
- package/rules/no-abusive-eslint-disable.js +48 -0
- package/rules/no-array-callback-reference.js +256 -0
- package/rules/no-array-for-each.js +473 -0
- package/rules/no-array-method-this-argument.js +188 -0
- package/rules/no-array-push-push.js +144 -0
- package/rules/no-array-reduce.js +126 -0
- package/rules/no-await-expression-member.js +90 -0
- package/rules/no-console-spaces.js +86 -0
- package/rules/no-document-cookie.js +25 -0
- package/rules/no-empty-file.js +57 -0
- package/rules/no-for-loop.js +427 -0
- package/rules/no-hex-escape.js +46 -0
- package/rules/no-instanceof-array.js +65 -0
- package/rules/no-invalid-remove-event-listener.js +60 -0
- package/rules/no-keyword-prefix.js +199 -0
- package/rules/no-lonely-if.js +151 -0
- package/rules/no-negated-condition.js +144 -0
- package/rules/no-nested-ternary.js +58 -0
- package/rules/no-new-array.js +104 -0
- package/rules/no-new-buffer.js +98 -0
- package/rules/no-null.js +153 -0
- package/rules/no-object-as-default-parameter.js +50 -0
- package/rules/no-process-exit.js +104 -0
- package/rules/no-static-only-class.js +224 -0
- package/rules/no-thenable.js +198 -0
- package/rules/no-this-assignment.js +38 -0
- package/rules/no-typeof-undefined.js +143 -0
- package/rules/no-unnecessary-await.js +107 -0
- package/rules/no-unnecessary-polyfills.js +176 -0
- package/rules/no-unreadable-array-destructuring.js +83 -0
- package/rules/no-unreadable-iife.js +45 -0
- package/rules/no-unused-properties.js +238 -0
- package/rules/no-useless-fallback-in-spread.js +68 -0
- package/rules/no-useless-length-check.js +152 -0
- package/rules/no-useless-promise-resolve-reject.js +212 -0
- package/rules/no-useless-spread.js +381 -0
- package/rules/no-useless-switch-case.js +71 -0
- package/rules/no-useless-undefined.js +301 -0
- package/rules/no-zero-fractions.js +79 -0
- package/rules/number-literal-case.js +52 -0
- package/rules/numeric-separators-style.js +181 -0
- package/rules/prefer-add-event-listener.js +188 -0
- package/rules/prefer-array-find.js +423 -0
- package/rules/prefer-array-flat-map.js +82 -0
- package/rules/prefer-array-flat.js +279 -0
- package/rules/prefer-array-index-of.js +32 -0
- package/rules/prefer-array-some.js +157 -0
- package/rules/prefer-at.js +374 -0
- package/rules/prefer-blob-reading-methods.js +45 -0
- package/rules/prefer-code-point.js +67 -0
- package/rules/prefer-date-now.js +135 -0
- package/rules/prefer-default-parameters.js +219 -0
- package/rules/prefer-dom-node-append.js +48 -0
- package/rules/prefer-dom-node-dataset.js +120 -0
- package/rules/prefer-dom-node-remove.js +122 -0
- package/rules/prefer-dom-node-text-content.js +75 -0
- package/rules/prefer-event-target.js +117 -0
- package/rules/prefer-export-from.js +413 -0
- package/rules/prefer-includes.js +98 -0
- package/rules/prefer-json-parse-buffer.js +159 -0
- package/rules/prefer-keyboard-event-key.js +186 -0
- package/rules/prefer-logical-operator-over-ternary.js +159 -0
- package/rules/prefer-math-trunc.js +109 -0
- package/rules/prefer-modern-dom-apis.js +141 -0
- package/rules/prefer-modern-math-apis.js +212 -0
- package/rules/prefer-module.js +349 -0
- package/rules/prefer-native-coercion-functions.js +185 -0
- package/rules/prefer-negative-index.js +213 -0
- package/rules/prefer-node-protocol.js +61 -0
- package/rules/prefer-number-properties.js +126 -0
- package/rules/prefer-object-from-entries.js +252 -0
- package/rules/prefer-optional-catch-binding.js +75 -0
- package/rules/prefer-prototype-methods.js +88 -0
- package/rules/prefer-query-selector.js +135 -0
- package/rules/prefer-reflect-apply.js +97 -0
- package/rules/prefer-regexp-test.js +156 -0
- package/rules/prefer-set-has.js +186 -0
- package/rules/prefer-set-size.js +103 -0
- package/rules/prefer-spread.js +529 -0
- package/rules/prefer-string-replace-all.js +145 -0
- package/rules/prefer-string-slice.js +182 -0
- package/rules/prefer-string-starts-ends-with.js +199 -0
- package/rules/prefer-string-trim-start-end.js +44 -0
- package/rules/prefer-switch.js +344 -0
- package/rules/prefer-ternary.js +282 -0
- package/rules/prefer-top-level-await.js +152 -0
- package/rules/prefer-type-error.js +151 -0
- package/rules/prevent-abbreviations.js +645 -0
- package/rules/relative-url-style.js +168 -0
- package/rules/require-array-join-separator.js +63 -0
- package/rules/require-number-to-fixed-digits-argument.js +54 -0
- package/rules/require-post-message-target-origin.js +71 -0
- package/rules/shared/abbreviations.js +262 -0
- package/rules/shared/dom-events.js +275 -0
- package/rules/shared/event-keys.js +52 -0
- package/rules/shared/negative-index.js +46 -0
- package/rules/shared/simple-array-search-rule.js +128 -0
- package/rules/shared/typed-array.js +16 -0
- package/rules/string-content.js +187 -0
- package/rules/switch-case-braces.js +109 -0
- package/rules/template-indent.js +219 -0
- package/rules/text-encoding-identifier-case.js +108 -0
- package/rules/throw-new-error.js +53 -0
- package/rules/utils/array-or-object-prototype-property.js +63 -0
- package/rules/utils/assert-token.js +32 -0
- package/rules/utils/avoid-capture.js +146 -0
- package/rules/utils/boolean.js +92 -0
- package/rules/utils/builtins.js +36 -0
- package/rules/utils/cartesian-product-samples.js +24 -0
- package/rules/utils/create-deprecated-rules.js +25 -0
- package/rules/utils/escape-string.js +26 -0
- package/rules/utils/escape-template-element-raw.js +6 -0
- package/rules/utils/get-ancestor.js +20 -0
- package/rules/utils/get-builtin-rule.js +7 -0
- package/rules/utils/get-call-expression-arguments-text.js +21 -0
- package/rules/utils/get-class-head-location.js +22 -0
- package/rules/utils/get-documentation-url.js +10 -0
- package/rules/utils/get-indent-string.js +11 -0
- package/rules/utils/get-previous-node.js +24 -0
- package/rules/utils/get-references.js +9 -0
- package/rules/utils/get-scopes.js +14 -0
- package/rules/utils/get-switch-case-head-location.js +21 -0
- package/rules/utils/get-variable-identifiers.js +7 -0
- package/rules/utils/global-reference-tracker.js +72 -0
- package/rules/utils/has-optional-chain-element.js +21 -0
- package/rules/utils/has-same-range.js +7 -0
- package/rules/utils/index.js +53 -0
- package/rules/utils/is-function-self-used-inside.js +43 -0
- package/rules/utils/is-left-hand-side.js +22 -0
- package/rules/utils/is-logical-expression.js +16 -0
- package/rules/utils/is-method-named.js +9 -0
- package/rules/utils/is-new-expression-with-parentheses.js +26 -0
- package/rules/utils/is-node-matches.js +53 -0
- package/rules/utils/is-node-value-not-dom-node.js +21 -0
- package/rules/utils/is-node-value-not-function.js +42 -0
- package/rules/utils/is-number.js +224 -0
- package/rules/utils/is-object-method.js +11 -0
- package/rules/utils/is-on-same-line.js +7 -0
- package/rules/utils/is-same-identifier.js +8 -0
- package/rules/utils/is-same-reference.js +173 -0
- package/rules/utils/is-shadowed.js +33 -0
- package/rules/utils/is-shorthand-export-local.js +9 -0
- package/rules/utils/is-shorthand-import-local.js +9 -0
- package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
- package/rules/utils/is-shorthand-property-value.js +8 -0
- package/rules/utils/is-value-not-usable.js +5 -0
- package/rules/utils/lodash.js +1589 -0
- package/rules/utils/needs-semicolon.js +114 -0
- package/rules/utils/numeric.js +53 -0
- package/rules/utils/parentheses.js +73 -0
- package/rules/utils/resolve-variable-name.js +20 -0
- package/rules/utils/rule.js +190 -0
- package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
- package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
- package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
- package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
- package/rules/utils/singular.js +18 -0
- package/rules/utils/to-location.js +21 -0
- package/README.md +0 -5
@@ -0,0 +1,168 @@
|
|
1
|
+
'use strict';
|
2
|
+
const avoidCapture = require('./utils/avoid-capture.js');
|
3
|
+
const isLeftHandSide = require('./utils/is-left-hand-side.js');
|
4
|
+
const {isCallOrNewExpression} = require('./ast/index.js');
|
5
|
+
|
6
|
+
const MESSAGE_ID = 'consistentDestructuring';
|
7
|
+
const MESSAGE_ID_SUGGEST = 'consistentDestructuringSuggest';
|
8
|
+
|
9
|
+
const isSimpleExpression = expression => {
|
10
|
+
while (expression) {
|
11
|
+
if (expression.computed) {
|
12
|
+
return false;
|
13
|
+
}
|
14
|
+
|
15
|
+
if (expression.type !== 'MemberExpression') {
|
16
|
+
break;
|
17
|
+
}
|
18
|
+
|
19
|
+
expression = expression.object;
|
20
|
+
}
|
21
|
+
|
22
|
+
return expression.type === 'Identifier'
|
23
|
+
|| expression.type === 'ThisExpression';
|
24
|
+
};
|
25
|
+
|
26
|
+
const isChildInParentScope = (child, parent) => {
|
27
|
+
while (child) {
|
28
|
+
if (child === parent) {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
|
32
|
+
child = child.upper;
|
33
|
+
}
|
34
|
+
|
35
|
+
return false;
|
36
|
+
};
|
37
|
+
|
38
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
39
|
+
const create = context => {
|
40
|
+
const {sourceCode} = context;
|
41
|
+
const declarations = new Map();
|
42
|
+
|
43
|
+
return {
|
44
|
+
VariableDeclarator(node) {
|
45
|
+
if (!(
|
46
|
+
node.id.type === 'ObjectPattern'
|
47
|
+
&& node.init
|
48
|
+
&& node.init.type !== 'Literal'
|
49
|
+
// Ignore any complex expressions (e.g. arrays, functions)
|
50
|
+
&& isSimpleExpression(node.init)
|
51
|
+
)) {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
|
55
|
+
declarations.set(sourceCode.getText(node.init), {
|
56
|
+
scope: sourceCode.getScope(node),
|
57
|
+
variables: sourceCode.getDeclaredVariables(node),
|
58
|
+
objectPattern: node.id,
|
59
|
+
});
|
60
|
+
},
|
61
|
+
MemberExpression(node) {
|
62
|
+
if (
|
63
|
+
node.computed
|
64
|
+
|| (
|
65
|
+
isCallOrNewExpression(node.parent)
|
66
|
+
&& node.parent.callee === node
|
67
|
+
)
|
68
|
+
|| isLeftHandSide(node)
|
69
|
+
) {
|
70
|
+
return;
|
71
|
+
}
|
72
|
+
|
73
|
+
const declaration = declarations.get(sourceCode.getText(node.object));
|
74
|
+
|
75
|
+
if (!declaration) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
const {scope, objectPattern} = declaration;
|
80
|
+
const memberScope = sourceCode.getScope(node);
|
81
|
+
|
82
|
+
// Property is destructured outside the current scope
|
83
|
+
if (!isChildInParentScope(memberScope, scope)) {
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
|
87
|
+
const destructurings = objectPattern.properties.filter(property =>
|
88
|
+
property.type === 'Property'
|
89
|
+
&& property.key.type === 'Identifier'
|
90
|
+
&& property.value.type === 'Identifier',
|
91
|
+
);
|
92
|
+
const lastProperty = objectPattern.properties.at(-1);
|
93
|
+
|
94
|
+
const hasRest = lastProperty && lastProperty.type === 'RestElement';
|
95
|
+
|
96
|
+
const expression = sourceCode.getText(node);
|
97
|
+
const member = sourceCode.getText(node.property);
|
98
|
+
|
99
|
+
// Member might already be destructured
|
100
|
+
const destructuredMember = destructurings.find(property =>
|
101
|
+
property.key.name === member,
|
102
|
+
);
|
103
|
+
|
104
|
+
if (!destructuredMember) {
|
105
|
+
// Don't destructure additional members when rest is used
|
106
|
+
if (hasRest) {
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
|
110
|
+
// Destructured member collides with an existing identifier
|
111
|
+
if (avoidCapture(member, [memberScope]) !== member) {
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
// Don't try to fix nested member expressions
|
117
|
+
if (node.parent.type === 'MemberExpression') {
|
118
|
+
return {
|
119
|
+
node,
|
120
|
+
messageId: MESSAGE_ID,
|
121
|
+
};
|
122
|
+
}
|
123
|
+
|
124
|
+
const newMember = destructuredMember ? destructuredMember.value.name : member;
|
125
|
+
|
126
|
+
return {
|
127
|
+
node,
|
128
|
+
messageId: MESSAGE_ID,
|
129
|
+
suggest: [{
|
130
|
+
messageId: MESSAGE_ID_SUGGEST,
|
131
|
+
data: {
|
132
|
+
expression,
|
133
|
+
property: newMember,
|
134
|
+
},
|
135
|
+
* fix(fixer) {
|
136
|
+
const {properties} = objectPattern;
|
137
|
+
const lastProperty = properties.at(-1);
|
138
|
+
|
139
|
+
yield fixer.replaceText(node, newMember);
|
140
|
+
|
141
|
+
if (!destructuredMember) {
|
142
|
+
yield lastProperty
|
143
|
+
? fixer.insertTextAfter(lastProperty, `, ${newMember}`)
|
144
|
+
: fixer.replaceText(objectPattern, `{${newMember}}`);
|
145
|
+
}
|
146
|
+
},
|
147
|
+
}],
|
148
|
+
};
|
149
|
+
},
|
150
|
+
};
|
151
|
+
};
|
152
|
+
|
153
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
154
|
+
module.exports = {
|
155
|
+
create,
|
156
|
+
meta: {
|
157
|
+
type: 'suggestion',
|
158
|
+
docs: {
|
159
|
+
description: 'Use destructured variables over properties.',
|
160
|
+
},
|
161
|
+
fixable: 'code',
|
162
|
+
hasSuggestions: true,
|
163
|
+
messages: {
|
164
|
+
[MESSAGE_ID]: 'Use destructured variables over properties.',
|
165
|
+
[MESSAGE_ID_SUGGEST]: 'Replace `{{expression}}` with destructured property `{{property}}`.',
|
166
|
+
},
|
167
|
+
},
|
168
|
+
};
|
@@ -0,0 +1,223 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('@eslint-community/eslint-utils');
|
3
|
+
const {
|
4
|
+
getReferences,
|
5
|
+
isNodeMatches,
|
6
|
+
} = require('./utils/index.js');
|
7
|
+
const {
|
8
|
+
functionTypes,
|
9
|
+
} = require('./ast/index.js');
|
10
|
+
|
11
|
+
const MESSAGE_ID = 'consistent-function-scoping';
|
12
|
+
const messages = {
|
13
|
+
[MESSAGE_ID]: 'Move {{functionNameWithKind}} to the outer scope.',
|
14
|
+
};
|
15
|
+
|
16
|
+
const isSameScope = (scope1, scope2) =>
|
17
|
+
scope1 && scope2 && (scope1 === scope2 || scope1.block === scope2.block);
|
18
|
+
|
19
|
+
function checkReferences(scope, parent, scopeManager) {
|
20
|
+
const hitReference = references => references.some(reference => {
|
21
|
+
if (isSameScope(parent, reference.from)) {
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
|
25
|
+
const {resolved} = reference;
|
26
|
+
const [definition] = resolved.defs;
|
27
|
+
|
28
|
+
// Skip recursive function name
|
29
|
+
if (definition?.type === 'FunctionName' && resolved.name === definition.name.name) {
|
30
|
+
return false;
|
31
|
+
}
|
32
|
+
|
33
|
+
return isSameScope(parent, resolved.scope);
|
34
|
+
});
|
35
|
+
|
36
|
+
const hitDefinitions = definitions => definitions.some(definition => {
|
37
|
+
const scope = scopeManager.acquire(definition.node);
|
38
|
+
return isSameScope(parent, scope);
|
39
|
+
});
|
40
|
+
|
41
|
+
// This check looks for neighboring function definitions
|
42
|
+
const hitIdentifier = identifiers => identifiers.some(identifier => {
|
43
|
+
// Only look at identifiers that live in a FunctionDeclaration
|
44
|
+
if (
|
45
|
+
!identifier.parent
|
46
|
+
|| identifier.parent.type !== 'FunctionDeclaration'
|
47
|
+
) {
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
|
51
|
+
const identifierScope = scopeManager.acquire(identifier);
|
52
|
+
|
53
|
+
// If we have a scope, the earlier checks should have worked so ignore them here
|
54
|
+
/* c8 ignore next 3 */
|
55
|
+
if (identifierScope) {
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
|
59
|
+
const identifierParentScope = scopeManager.acquire(identifier.parent);
|
60
|
+
/* c8 ignore next 3 */
|
61
|
+
if (!identifierParentScope) {
|
62
|
+
return false;
|
63
|
+
}
|
64
|
+
|
65
|
+
// Ignore identifiers from our own scope
|
66
|
+
if (isSameScope(scope, identifierParentScope)) {
|
67
|
+
return false;
|
68
|
+
}
|
69
|
+
|
70
|
+
// Look at the scope above the function definition to see if lives
|
71
|
+
// next to the reference being checked
|
72
|
+
return isSameScope(parent, identifierParentScope.upper);
|
73
|
+
});
|
74
|
+
|
75
|
+
return getReferences(scope)
|
76
|
+
.map(({resolved}) => resolved)
|
77
|
+
.filter(Boolean)
|
78
|
+
.some(variable =>
|
79
|
+
hitReference(variable.references)
|
80
|
+
|| hitDefinitions(variable.defs)
|
81
|
+
|| hitIdentifier(variable.identifiers),
|
82
|
+
);
|
83
|
+
}
|
84
|
+
|
85
|
+
// https://reactjs.org/docs/hooks-reference.html
|
86
|
+
const reactHooks = [
|
87
|
+
'useState',
|
88
|
+
'useEffect',
|
89
|
+
'useContext',
|
90
|
+
'useReducer',
|
91
|
+
'useCallback',
|
92
|
+
'useMemo',
|
93
|
+
'useRef',
|
94
|
+
'useImperativeHandle',
|
95
|
+
'useLayoutEffect',
|
96
|
+
'useDebugValue',
|
97
|
+
].flatMap(hookName => [hookName, `React.${hookName}`]);
|
98
|
+
|
99
|
+
const isReactHook = scope =>
|
100
|
+
scope.block?.parent?.callee
|
101
|
+
&& isNodeMatches(scope.block.parent.callee, reactHooks);
|
102
|
+
|
103
|
+
const isArrowFunctionWithThis = scope =>
|
104
|
+
scope.type === 'function'
|
105
|
+
&& scope.block?.type === 'ArrowFunctionExpression'
|
106
|
+
&& (scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
|
107
|
+
|
108
|
+
const iifeFunctionTypes = new Set([
|
109
|
+
'FunctionExpression',
|
110
|
+
'ArrowFunctionExpression',
|
111
|
+
]);
|
112
|
+
const isIife = node =>
|
113
|
+
iifeFunctionTypes.has(node.type)
|
114
|
+
&& node.parent.type === 'CallExpression'
|
115
|
+
&& node.parent.callee === node;
|
116
|
+
|
117
|
+
function checkNode(node, scopeManager) {
|
118
|
+
const scope = scopeManager.acquire(node);
|
119
|
+
|
120
|
+
if (!scope || isArrowFunctionWithThis(scope)) {
|
121
|
+
return true;
|
122
|
+
}
|
123
|
+
|
124
|
+
let parentNode = node.parent;
|
125
|
+
|
126
|
+
// Skip over junk like the block statement inside of a function declaration
|
127
|
+
// or the various pieces of an arrow function.
|
128
|
+
|
129
|
+
if (parentNode.type === 'VariableDeclarator') {
|
130
|
+
parentNode = parentNode.parent;
|
131
|
+
}
|
132
|
+
|
133
|
+
if (parentNode.type === 'VariableDeclaration') {
|
134
|
+
parentNode = parentNode.parent;
|
135
|
+
}
|
136
|
+
|
137
|
+
if (parentNode.type === 'BlockStatement') {
|
138
|
+
parentNode = parentNode.parent;
|
139
|
+
}
|
140
|
+
|
141
|
+
const parentScope = scopeManager.acquire(parentNode);
|
142
|
+
if (
|
143
|
+
!parentScope
|
144
|
+
|| parentScope.type === 'global'
|
145
|
+
|| isReactHook(parentScope)
|
146
|
+
|| isIife(parentNode)
|
147
|
+
) {
|
148
|
+
return true;
|
149
|
+
}
|
150
|
+
|
151
|
+
return checkReferences(scope, parentScope, scopeManager);
|
152
|
+
}
|
153
|
+
|
154
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
155
|
+
const create = context => {
|
156
|
+
const {checkArrowFunctions} = {checkArrowFunctions: true, ...context.options[0]};
|
157
|
+
const {sourceCode} = context;
|
158
|
+
const {scopeManager} = sourceCode;
|
159
|
+
|
160
|
+
const functions = [];
|
161
|
+
|
162
|
+
context.on(functionTypes, () => {
|
163
|
+
functions.push(false);
|
164
|
+
});
|
165
|
+
|
166
|
+
context.on('JSXElement', () => {
|
167
|
+
// Turn off this rule if we see a JSX element because scope
|
168
|
+
// references does not include JSXElement nodes.
|
169
|
+
if (functions.length > 0) {
|
170
|
+
functions[functions.length - 1] = true;
|
171
|
+
}
|
172
|
+
});
|
173
|
+
|
174
|
+
context.onExit(functionTypes, node => {
|
175
|
+
const currentFunctionHasJsx = functions.pop();
|
176
|
+
if (currentFunctionHasJsx) {
|
177
|
+
return;
|
178
|
+
}
|
179
|
+
|
180
|
+
if (node.type === 'ArrowFunctionExpression' && !checkArrowFunctions) {
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
|
184
|
+
if (checkNode(node, scopeManager)) {
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
return {
|
189
|
+
node,
|
190
|
+
loc: getFunctionHeadLocation(node, sourceCode),
|
191
|
+
messageId: MESSAGE_ID,
|
192
|
+
data: {
|
193
|
+
functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
|
194
|
+
},
|
195
|
+
};
|
196
|
+
});
|
197
|
+
};
|
198
|
+
|
199
|
+
const schema = [
|
200
|
+
{
|
201
|
+
type: 'object',
|
202
|
+
additionalProperties: false,
|
203
|
+
properties: {
|
204
|
+
checkArrowFunctions: {
|
205
|
+
type: 'boolean',
|
206
|
+
default: true,
|
207
|
+
},
|
208
|
+
},
|
209
|
+
},
|
210
|
+
];
|
211
|
+
|
212
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
213
|
+
module.exports = {
|
214
|
+
create,
|
215
|
+
meta: {
|
216
|
+
type: 'suggestion',
|
217
|
+
docs: {
|
218
|
+
description: 'Move function definitions to the highest possible scope.',
|
219
|
+
},
|
220
|
+
schema,
|
221
|
+
messages,
|
222
|
+
},
|
223
|
+
};
|
@@ -0,0 +1,215 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {upperFirst} = require('./utils/lodash.js');
|
3
|
+
|
4
|
+
const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
|
5
|
+
const messages = {
|
6
|
+
[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class',
|
7
|
+
};
|
8
|
+
|
9
|
+
const nameRegexp = /^(?:[A-Z][\da-z]*)*Error$/;
|
10
|
+
|
11
|
+
const getClassName = name => upperFirst(name).replace(/(?:error|)$/i, 'Error');
|
12
|
+
|
13
|
+
const getConstructorMethod = className => `
|
14
|
+
constructor() {
|
15
|
+
super();
|
16
|
+
this.name = '${className}';
|
17
|
+
}
|
18
|
+
`;
|
19
|
+
|
20
|
+
const hasValidSuperClass = node => {
|
21
|
+
if (!node.superClass) {
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
|
25
|
+
let {name, type, property} = node.superClass;
|
26
|
+
|
27
|
+
if (type === 'MemberExpression') {
|
28
|
+
({name} = property);
|
29
|
+
}
|
30
|
+
|
31
|
+
return nameRegexp.test(name);
|
32
|
+
};
|
33
|
+
|
34
|
+
const isSuperExpression = node =>
|
35
|
+
node.type === 'ExpressionStatement'
|
36
|
+
&& node.expression.type === 'CallExpression'
|
37
|
+
&& node.expression.callee.type === 'Super';
|
38
|
+
|
39
|
+
const isAssignmentExpression = (node, name) => {
|
40
|
+
if (
|
41
|
+
node.type !== 'ExpressionStatement'
|
42
|
+
|| node.expression.type !== 'AssignmentExpression'
|
43
|
+
) {
|
44
|
+
return false;
|
45
|
+
}
|
46
|
+
|
47
|
+
const lhs = node.expression.left;
|
48
|
+
|
49
|
+
if (!lhs.object || lhs.object.type !== 'ThisExpression') {
|
50
|
+
return false;
|
51
|
+
}
|
52
|
+
|
53
|
+
return lhs.property.name === name;
|
54
|
+
};
|
55
|
+
|
56
|
+
const isPropertyDefinition = (node, name) =>
|
57
|
+
node.type === 'PropertyDefinition'
|
58
|
+
&& !node.computed
|
59
|
+
&& node.key.type === 'Identifier'
|
60
|
+
&& node.key.name === name;
|
61
|
+
|
62
|
+
function * customErrorDefinition(context, node) {
|
63
|
+
if (!hasValidSuperClass(node)) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
if (node.id === null) {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
const {name} = node.id;
|
72
|
+
const className = getClassName(name);
|
73
|
+
|
74
|
+
if (name !== className) {
|
75
|
+
yield {
|
76
|
+
node: node.id,
|
77
|
+
message: `Invalid class name, use \`${className}\`.`,
|
78
|
+
};
|
79
|
+
}
|
80
|
+
|
81
|
+
const {body, range} = node.body;
|
82
|
+
const constructor = body.find(x => x.kind === 'constructor');
|
83
|
+
|
84
|
+
if (!constructor) {
|
85
|
+
yield {
|
86
|
+
node,
|
87
|
+
message: 'Add a constructor to your error.',
|
88
|
+
fix: fixer => fixer.insertTextAfterRange([
|
89
|
+
range[0],
|
90
|
+
range[0] + 1,
|
91
|
+
], getConstructorMethod(name)),
|
92
|
+
};
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
|
96
|
+
const constructorBodyNode = constructor.value.body;
|
97
|
+
|
98
|
+
// Verify the constructor has a body (TypeScript)
|
99
|
+
if (!constructorBodyNode) {
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
|
103
|
+
const constructorBody = constructorBodyNode.body;
|
104
|
+
|
105
|
+
const superExpression = constructorBody.find(body => isSuperExpression(body));
|
106
|
+
const messageExpressionIndex = constructorBody.findIndex(x => isAssignmentExpression(x, 'message'));
|
107
|
+
|
108
|
+
if (!superExpression) {
|
109
|
+
yield {
|
110
|
+
node: constructorBodyNode,
|
111
|
+
message: 'Missing call to `super()` in constructor.',
|
112
|
+
};
|
113
|
+
} else if (messageExpressionIndex !== -1) {
|
114
|
+
const expression = constructorBody[messageExpressionIndex];
|
115
|
+
|
116
|
+
yield {
|
117
|
+
node: superExpression,
|
118
|
+
message: 'Pass the error message to `super()` instead of setting `this.message`.',
|
119
|
+
* fix(fixer) {
|
120
|
+
if (superExpression.expression.arguments.length === 0) {
|
121
|
+
const rhs = expression.expression.right;
|
122
|
+
yield fixer.insertTextAfterRange([
|
123
|
+
superExpression.range[0],
|
124
|
+
superExpression.range[0] + 6,
|
125
|
+
], rhs.raw || rhs.name);
|
126
|
+
}
|
127
|
+
|
128
|
+
yield fixer.removeRange([
|
129
|
+
messageExpressionIndex === 0 ? constructorBodyNode.range[0] : constructorBody[messageExpressionIndex - 1].range[1],
|
130
|
+
expression.range[1],
|
131
|
+
]);
|
132
|
+
},
|
133
|
+
};
|
134
|
+
}
|
135
|
+
|
136
|
+
const nameExpression = constructorBody.find(x => isAssignmentExpression(x, 'name'));
|
137
|
+
if (!nameExpression) {
|
138
|
+
const nameProperty = body.find(node => isPropertyDefinition(node, 'name'));
|
139
|
+
|
140
|
+
if (!nameProperty?.value || nameProperty.value.value !== name) {
|
141
|
+
yield {
|
142
|
+
node: nameProperty?.value ?? constructorBodyNode,
|
143
|
+
message: `The \`name\` property should be set to \`${name}\`.`,
|
144
|
+
};
|
145
|
+
}
|
146
|
+
} else if (nameExpression.expression.right.value !== name) {
|
147
|
+
yield {
|
148
|
+
node: nameExpression?.expression.right ?? constructorBodyNode,
|
149
|
+
message: `The \`name\` property should be set to \`${name}\`.`,
|
150
|
+
};
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
const customErrorExport = (context, node) => {
|
155
|
+
const exportsName = node.left.property.name;
|
156
|
+
|
157
|
+
const maybeError = node.right;
|
158
|
+
|
159
|
+
if (maybeError.type !== 'ClassExpression') {
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
|
163
|
+
if (!hasValidSuperClass(maybeError)) {
|
164
|
+
return;
|
165
|
+
}
|
166
|
+
|
167
|
+
if (!maybeError.id) {
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
|
171
|
+
// Assume rule has already fixed the error name
|
172
|
+
const errorName = maybeError.id.name;
|
173
|
+
|
174
|
+
if (exportsName === errorName) {
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
|
178
|
+
return {
|
179
|
+
node: node.left.property,
|
180
|
+
messageId: MESSAGE_ID_INVALID_EXPORT,
|
181
|
+
fix: fixer => fixer.replaceText(node.left.property, errorName),
|
182
|
+
};
|
183
|
+
};
|
184
|
+
|
185
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
186
|
+
const create = context => {
|
187
|
+
context.on('ClassDeclaration', node => customErrorDefinition(context, node));
|
188
|
+
context.on('AssignmentExpression', node => {
|
189
|
+
if (node.right.type === 'ClassExpression') {
|
190
|
+
return customErrorDefinition(context, node.right);
|
191
|
+
}
|
192
|
+
});
|
193
|
+
context.on('AssignmentExpression', node => {
|
194
|
+
if (
|
195
|
+
node.left.type === 'MemberExpression'
|
196
|
+
&& node.left.object.type === 'Identifier'
|
197
|
+
&& node.left.object.name === 'exports'
|
198
|
+
) {
|
199
|
+
return customErrorExport(context, node);
|
200
|
+
}
|
201
|
+
});
|
202
|
+
};
|
203
|
+
|
204
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
205
|
+
module.exports = {
|
206
|
+
create,
|
207
|
+
meta: {
|
208
|
+
type: 'problem',
|
209
|
+
docs: {
|
210
|
+
description: 'Enforce correct `Error` subclassing.',
|
211
|
+
},
|
212
|
+
fixable: 'code',
|
213
|
+
messages,
|
214
|
+
},
|
215
|
+
};
|
@@ -0,0 +1,72 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isOpeningBraceToken} = require('@eslint-community/eslint-utils');
|
3
|
+
|
4
|
+
const MESSAGE_ID = 'empty-brace-spaces';
|
5
|
+
const messages = {
|
6
|
+
[MESSAGE_ID]: 'Do not add spaces between braces.',
|
7
|
+
};
|
8
|
+
|
9
|
+
const getProblem = (node, context) => {
|
10
|
+
const {sourceCode} = context;
|
11
|
+
const filter = node.type === 'RecordExpression'
|
12
|
+
? token => token.type === 'Punctuator' && (token.value === '#{' || token.value === '{|')
|
13
|
+
: isOpeningBraceToken;
|
14
|
+
const openingBrace = sourceCode.getFirstToken(node, {filter});
|
15
|
+
const closingBrace = sourceCode.getLastToken(node);
|
16
|
+
const [, start] = openingBrace.range;
|
17
|
+
const [end] = closingBrace.range;
|
18
|
+
const textBetween = sourceCode.text.slice(start, end);
|
19
|
+
|
20
|
+
if (!/^\s+$/.test(textBetween)) {
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
|
24
|
+
return {
|
25
|
+
loc: {
|
26
|
+
start: openingBrace.loc.end,
|
27
|
+
end: closingBrace.loc.start,
|
28
|
+
},
|
29
|
+
messageId: MESSAGE_ID,
|
30
|
+
fix: fixer => fixer.removeRange([start, end]),
|
31
|
+
};
|
32
|
+
};
|
33
|
+
|
34
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
35
|
+
const create = context => {
|
36
|
+
context.on([
|
37
|
+
'BlockStatement',
|
38
|
+
'ClassBody',
|
39
|
+
'StaticBlock',
|
40
|
+
], node => {
|
41
|
+
if (node.body.length > 0) {
|
42
|
+
return;
|
43
|
+
}
|
44
|
+
|
45
|
+
return getProblem(node, context);
|
46
|
+
});
|
47
|
+
|
48
|
+
context.on([
|
49
|
+
'ObjectExpression',
|
50
|
+
// Experimental https://github.com/tc39/proposal-record-tuple
|
51
|
+
'RecordExpression',
|
52
|
+
], node => {
|
53
|
+
if (node.properties.length > 0) {
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
|
57
|
+
return getProblem(node, context);
|
58
|
+
});
|
59
|
+
};
|
60
|
+
|
61
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
62
|
+
module.exports = {
|
63
|
+
create,
|
64
|
+
meta: {
|
65
|
+
type: 'layout',
|
66
|
+
docs: {
|
67
|
+
description: 'Enforce no spaces between braces.',
|
68
|
+
},
|
69
|
+
fixable: 'whitespace',
|
70
|
+
messages,
|
71
|
+
},
|
72
|
+
};
|