@typescript-eslint/eslint-plugin 8.52.1-alpha.1 → 8.52.1-alpha.11
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/dist/rules/no-unsafe-call.d.ts +1 -1
- package/dist/rules/no-unsafe-call.js +19 -14
- package/dist/rules/no-unsafe-member-access.d.ts +1 -1
- package/dist/rules/no-unsafe-member-access.js +24 -13
- package/dist/rules/no-unused-vars.d.ts +4 -1
- package/dist/rules/no-unused-vars.js +373 -40
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type MessageIds = 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' | 'unsafeTemplateTag';
|
|
1
|
+
export type MessageIds = 'errorCall' | 'errorCallThis' | 'errorNew' | 'errorTemplateTag' | 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' | 'unsafeTemplateTag';
|
|
2
2
|
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
3
3
|
name: string;
|
|
4
4
|
};
|
|
@@ -45,13 +45,17 @@ exports.default = (0, util_1.createRule)({
|
|
|
45
45
|
requiresTypeChecking: true,
|
|
46
46
|
},
|
|
47
47
|
messages: {
|
|
48
|
-
|
|
48
|
+
errorCall: 'Unsafe call of a type that could not be resolved.',
|
|
49
|
+
errorCallThis: 'Unsafe call of a `this` type that could not be resolved.',
|
|
50
|
+
errorNew: 'Unsafe construction of a type that could not be resolved.',
|
|
51
|
+
errorTemplateTag: 'Unsafe use of a template tag whose type could not be resolved.',
|
|
52
|
+
unsafeCall: 'Unsafe call of {{type}} typed value.',
|
|
49
53
|
unsafeCallThis: [
|
|
50
|
-
'Unsafe call of
|
|
54
|
+
'Unsafe call of {{type}} typed value. `this` is typed as {{type}}.',
|
|
51
55
|
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
|
|
52
56
|
].join('\n'),
|
|
53
|
-
unsafeNew: 'Unsafe construction of
|
|
54
|
-
unsafeTemplateTag: 'Unsafe use of
|
|
57
|
+
unsafeNew: 'Unsafe construction of {{type}} typed value.',
|
|
58
|
+
unsafeTemplateTag: 'Unsafe use of {{type}} typed template tag.',
|
|
55
59
|
},
|
|
56
60
|
schema: [],
|
|
57
61
|
},
|
|
@@ -60,7 +64,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
60
64
|
const services = (0, util_1.getParserServices)(context);
|
|
61
65
|
const compilerOptions = services.program.getCompilerOptions();
|
|
62
66
|
const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(compilerOptions, 'noImplicitThis');
|
|
63
|
-
function checkCall(node, reportingNode,
|
|
67
|
+
function checkCall(node, reportingNode, unsafeMessageId, errorMessageId) {
|
|
64
68
|
const type = (0, util_1.getConstrainedTypeAtLocation)(services, node);
|
|
65
69
|
if ((0, util_1.isTypeAnyType)(type)) {
|
|
66
70
|
if (!isNoImplicitThis) {
|
|
@@ -68,15 +72,16 @@ exports.default = (0, util_1.createRule)({
|
|
|
68
72
|
const thisExpression = (0, util_1.getThisExpression)(node);
|
|
69
73
|
if (thisExpression &&
|
|
70
74
|
(0, util_1.isTypeAnyType)((0, util_1.getConstrainedTypeAtLocation)(services, thisExpression))) {
|
|
71
|
-
|
|
75
|
+
unsafeMessageId = 'unsafeCallThis';
|
|
76
|
+
errorMessageId = 'errorCallThis';
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
const isErrorType = tsutils.isIntrinsicErrorType(type);
|
|
75
80
|
context.report({
|
|
76
81
|
node: reportingNode,
|
|
77
|
-
messageId,
|
|
82
|
+
messageId: isErrorType ? errorMessageId : unsafeMessageId,
|
|
78
83
|
data: {
|
|
79
|
-
type:
|
|
84
|
+
type: 'an `any`',
|
|
80
85
|
},
|
|
81
86
|
});
|
|
82
87
|
return;
|
|
@@ -98,7 +103,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
98
103
|
return;
|
|
99
104
|
}
|
|
100
105
|
const callSignatures = type.getCallSignatures();
|
|
101
|
-
if (
|
|
106
|
+
if (unsafeMessageId === 'unsafeNew') {
|
|
102
107
|
if (callSignatures.some(signature => !tsutils.isIntrinsicVoidType(signature.getReturnType()))) {
|
|
103
108
|
return;
|
|
104
109
|
}
|
|
@@ -108,9 +113,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
108
113
|
}
|
|
109
114
|
context.report({
|
|
110
115
|
node: reportingNode,
|
|
111
|
-
messageId,
|
|
116
|
+
messageId: unsafeMessageId,
|
|
112
117
|
data: {
|
|
113
|
-
type: '`Function`',
|
|
118
|
+
type: 'a `Function`',
|
|
114
119
|
},
|
|
115
120
|
});
|
|
116
121
|
return;
|
|
@@ -118,13 +123,13 @@ exports.default = (0, util_1.createRule)({
|
|
|
118
123
|
}
|
|
119
124
|
return {
|
|
120
125
|
'CallExpression > *.callee'(node) {
|
|
121
|
-
checkCall(node, node, 'unsafeCall');
|
|
126
|
+
checkCall(node, node, 'unsafeCall', 'errorCall');
|
|
122
127
|
},
|
|
123
128
|
NewExpression(node) {
|
|
124
|
-
checkCall(node.callee, node, 'unsafeNew');
|
|
129
|
+
checkCall(node.callee, node, 'unsafeNew', 'errorNew');
|
|
125
130
|
},
|
|
126
131
|
'TaggedTemplateExpression > *.tag'(node) {
|
|
127
|
-
checkCall(node, node, 'unsafeTemplateTag');
|
|
132
|
+
checkCall(node, node, 'unsafeTemplateTag', 'errorTemplateTag');
|
|
128
133
|
},
|
|
129
134
|
};
|
|
130
135
|
},
|
|
@@ -3,7 +3,7 @@ export type Options = [
|
|
|
3
3
|
allowOptionalChaining?: boolean;
|
|
4
4
|
}
|
|
5
5
|
];
|
|
6
|
-
export type MessageIds = 'unsafeComputedMemberAccess' | 'unsafeMemberExpression' | 'unsafeThisMemberExpression';
|
|
6
|
+
export type MessageIds = 'errorComputedMemberAccess' | 'errorMemberExpression' | 'errorThisMemberExpression' | 'unsafeComputedMemberAccess' | 'unsafeMemberExpression' | 'unsafeThisMemberExpression';
|
|
7
7
|
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
8
8
|
name: string;
|
|
9
9
|
};
|
|
@@ -42,10 +42,6 @@ var State;
|
|
|
42
42
|
State[State["Safe"] = 2] = "Safe";
|
|
43
43
|
State[State["Chained"] = 3] = "Chained";
|
|
44
44
|
})(State || (State = {}));
|
|
45
|
-
function createDataType(type) {
|
|
46
|
-
const isErrorType = tsutils.isIntrinsicErrorType(type);
|
|
47
|
-
return isErrorType ? '`error` typed' : '`any`';
|
|
48
|
-
}
|
|
49
45
|
exports.default = (0, util_1.createRule)({
|
|
50
46
|
name: 'no-unsafe-member-access',
|
|
51
47
|
meta: {
|
|
@@ -56,8 +52,14 @@ exports.default = (0, util_1.createRule)({
|
|
|
56
52
|
requiresTypeChecking: true,
|
|
57
53
|
},
|
|
58
54
|
messages: {
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
errorComputedMemberAccess: 'The type of computed name {{property}} cannot be resolved.',
|
|
56
|
+
errorMemberExpression: 'Unsafe member access {{property}} on a type that cannot be resolved.',
|
|
57
|
+
errorThisMemberExpression: [
|
|
58
|
+
'Unsafe member access {{property}}. The type of `this` cannot be resolved.',
|
|
59
|
+
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
|
|
60
|
+
].join('\n'),
|
|
61
|
+
unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an `any` value.',
|
|
62
|
+
unsafeMemberExpression: 'Unsafe member access {{property}} on an `any` value.',
|
|
61
63
|
unsafeThisMemberExpression: [
|
|
62
64
|
'Unsafe member access {{property}} on an `any` value. `this` is typed as `any`.',
|
|
63
65
|
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
|
|
@@ -116,20 +118,28 @@ exports.default = (0, util_1.createRule)({
|
|
|
116
118
|
stateCache.set(node, state);
|
|
117
119
|
if (state === State.Unsafe) {
|
|
118
120
|
const propertyName = context.sourceCode.getText(node.property);
|
|
119
|
-
let messageId
|
|
121
|
+
let messageId;
|
|
120
122
|
if (!isNoImplicitThis) {
|
|
121
123
|
// `this.foo` or `this.foo[bar]`
|
|
122
124
|
const thisExpression = (0, util_1.getThisExpression)(node);
|
|
123
|
-
if (thisExpression
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if (thisExpression) {
|
|
126
|
+
const thisType = (0, util_1.getConstrainedTypeAtLocation)(services, thisExpression);
|
|
127
|
+
if ((0, util_1.isTypeAnyType)(thisType)) {
|
|
128
|
+
messageId = tsutils.isIntrinsicErrorType(thisType)
|
|
129
|
+
? 'errorThisMemberExpression'
|
|
130
|
+
: 'unsafeThisMemberExpression';
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
}
|
|
134
|
+
if (!messageId) {
|
|
135
|
+
messageId = tsutils.isIntrinsicErrorType(type)
|
|
136
|
+
? 'errorMemberExpression'
|
|
137
|
+
: 'unsafeMemberExpression';
|
|
138
|
+
}
|
|
128
139
|
context.report({
|
|
129
140
|
node: node.property,
|
|
130
141
|
messageId,
|
|
131
142
|
data: {
|
|
132
|
-
type: createDataType(type),
|
|
133
143
|
property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
|
|
134
144
|
},
|
|
135
145
|
});
|
|
@@ -159,9 +169,10 @@ exports.default = (0, util_1.createRule)({
|
|
|
159
169
|
const propertyName = context.sourceCode.getText(node);
|
|
160
170
|
context.report({
|
|
161
171
|
node,
|
|
162
|
-
messageId:
|
|
172
|
+
messageId: tsutils.isIntrinsicErrorType(type)
|
|
173
|
+
? 'errorComputedMemberAccess'
|
|
174
|
+
: 'unsafeComputedMemberAccess',
|
|
163
175
|
data: {
|
|
164
|
-
type: createDataType(type),
|
|
165
176
|
property: `[${propertyName}]`,
|
|
166
177
|
},
|
|
167
178
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
export type MessageIds = 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType';
|
|
2
|
+
export type MessageIds = 'removeUnusedImportDeclaration' | 'removeUnusedVar' | 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType';
|
|
3
3
|
export type Options = [
|
|
4
4
|
'all' | 'local' | {
|
|
5
5
|
args?: 'after-used' | 'all' | 'none';
|
|
@@ -7,6 +7,9 @@ export type Options = [
|
|
|
7
7
|
caughtErrors?: 'all' | 'none';
|
|
8
8
|
caughtErrorsIgnorePattern?: string;
|
|
9
9
|
destructuredArrayIgnorePattern?: string;
|
|
10
|
+
enableAutofixRemoval?: {
|
|
11
|
+
imports?: boolean;
|
|
12
|
+
};
|
|
10
13
|
ignoreClassWithStaticInitBlock?: boolean;
|
|
11
14
|
ignoreRestSiblings?: boolean;
|
|
12
15
|
ignoreUsingDeclarations?: boolean;
|
|
@@ -4,6 +4,45 @@ const scope_manager_1 = require("@typescript-eslint/scope-manager");
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const util_1 = require("../util");
|
|
6
6
|
const referenceContainsTypeQuery_1 = require("../util/referenceContainsTypeQuery");
|
|
7
|
+
// this is a superset of DefinitionType which defines sub-types for better granularity
|
|
8
|
+
var VariableType;
|
|
9
|
+
(function (VariableType) {
|
|
10
|
+
// New sub-types
|
|
11
|
+
VariableType[VariableType["ArrayDestructure"] = 0] = "ArrayDestructure";
|
|
12
|
+
// DefinitionType
|
|
13
|
+
VariableType[VariableType["CatchClause"] = 1] = "CatchClause";
|
|
14
|
+
VariableType[VariableType["ClassName"] = 2] = "ClassName";
|
|
15
|
+
VariableType[VariableType["FunctionName"] = 3] = "FunctionName";
|
|
16
|
+
VariableType[VariableType["ImportBinding"] = 4] = "ImportBinding";
|
|
17
|
+
VariableType[VariableType["ImplicitGlobalVariable"] = 5] = "ImplicitGlobalVariable";
|
|
18
|
+
VariableType[VariableType["Parameter"] = 6] = "Parameter";
|
|
19
|
+
VariableType[VariableType["TSEnumMember"] = 7] = "TSEnumMember";
|
|
20
|
+
VariableType[VariableType["TSEnumName"] = 8] = "TSEnumName";
|
|
21
|
+
VariableType[VariableType["TSModuleName"] = 9] = "TSModuleName";
|
|
22
|
+
VariableType[VariableType["Type"] = 10] = "Type";
|
|
23
|
+
VariableType[VariableType["Variable"] = 11] = "Variable";
|
|
24
|
+
})(VariableType || (VariableType = {}));
|
|
25
|
+
const isCommaToken = {
|
|
26
|
+
predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === ',',
|
|
27
|
+
tokenChar: ',',
|
|
28
|
+
};
|
|
29
|
+
const isLeftCurlyToken = {
|
|
30
|
+
predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '{',
|
|
31
|
+
tokenChar: '{',
|
|
32
|
+
};
|
|
33
|
+
const isRightCurlyToken = {
|
|
34
|
+
predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '}',
|
|
35
|
+
tokenChar: '}',
|
|
36
|
+
};
|
|
37
|
+
function assertToken({ predicate, tokenChar }, token) {
|
|
38
|
+
if (token == null) {
|
|
39
|
+
throw new Error(`Expected a valid "${tokenChar}" token, but found no token`);
|
|
40
|
+
}
|
|
41
|
+
if (!predicate(token)) {
|
|
42
|
+
throw new Error(`Expected a valid "${tokenChar}" token, but got "${token.value}" instead`);
|
|
43
|
+
}
|
|
44
|
+
return token;
|
|
45
|
+
}
|
|
7
46
|
exports.default = (0, util_1.createRule)({
|
|
8
47
|
name: 'no-unused-vars',
|
|
9
48
|
meta: {
|
|
@@ -13,7 +52,11 @@ exports.default = (0, util_1.createRule)({
|
|
|
13
52
|
extendsBaseRule: true,
|
|
14
53
|
recommended: 'recommended',
|
|
15
54
|
},
|
|
55
|
+
fixable: 'code',
|
|
56
|
+
hasSuggestions: true,
|
|
16
57
|
messages: {
|
|
58
|
+
removeUnusedImportDeclaration: 'Remove unused import declaration.',
|
|
59
|
+
removeUnusedVar: 'Remove unused variable "{{varName}}".',
|
|
17
60
|
unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
|
|
18
61
|
usedIgnoredVar: "'{{varName}}' is marked as ignored but is used{{additional}}.",
|
|
19
62
|
usedOnlyAsType: "'{{varName}}' is {{action}} but only used as a type{{additional}}.",
|
|
@@ -52,6 +95,17 @@ exports.default = (0, util_1.createRule)({
|
|
|
52
95
|
type: 'string',
|
|
53
96
|
description: 'Regular expressions of destructured array variable names to not check for usage.',
|
|
54
97
|
},
|
|
98
|
+
enableAutofixRemoval: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
description: 'Configurable automatic fixes for different types of unused variables.',
|
|
102
|
+
properties: {
|
|
103
|
+
imports: {
|
|
104
|
+
type: 'boolean',
|
|
105
|
+
description: 'Whether to enable automatic removal of unused imports.',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
55
109
|
ignoreClassWithStaticInitBlock: {
|
|
56
110
|
type: 'boolean',
|
|
57
111
|
description: 'Whether to ignore classes with at least one static initialization block.',
|
|
@@ -86,10 +140,130 @@ exports.default = (0, util_1.createRule)({
|
|
|
86
140
|
defaultOptions: [{}],
|
|
87
141
|
create(context, [firstOption]) {
|
|
88
142
|
const MODULE_DECL_CACHE = new Map();
|
|
143
|
+
const reportedUnusedVariables = new Set();
|
|
144
|
+
function areAllSpecifiersUnused(decl) {
|
|
145
|
+
return context.sourceCode.getDeclaredVariables(decl).every(variable => {
|
|
146
|
+
return reportedUnusedVariables.has(variable);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const report = (unusedVar, opts) => {
|
|
150
|
+
reportedUnusedVariables.add(unusedVar);
|
|
151
|
+
const writeReferences = unusedVar.references.filter(ref => ref.isWrite() &&
|
|
152
|
+
ref.from.variableScope === unusedVar.scope.variableScope);
|
|
153
|
+
const id = writeReferences.length
|
|
154
|
+
? writeReferences[writeReferences.length - 1].identifier
|
|
155
|
+
: unusedVar.identifiers[0];
|
|
156
|
+
const { start } = id.loc;
|
|
157
|
+
const idLength = id.name.length;
|
|
158
|
+
const loc = {
|
|
159
|
+
start,
|
|
160
|
+
end: {
|
|
161
|
+
column: start.column + idLength,
|
|
162
|
+
line: start.line,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const fixer = (() => {
|
|
166
|
+
const { messageId, fix, useAutofix } = (() => {
|
|
167
|
+
if (unusedVar.defs.length !== 1) {
|
|
168
|
+
// If there's multiple definitions then we'd have to clean them all
|
|
169
|
+
// up! That's complicated and messy so for now let's just ignore it.
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
const { type, def } = defToVariableType(unusedVar.defs[0]);
|
|
173
|
+
switch (type) {
|
|
174
|
+
case VariableType.ArrayDestructure:
|
|
175
|
+
// TODO(bradzacher) -- this would be really easy to implement and
|
|
176
|
+
// is side-effect free!
|
|
177
|
+
return {};
|
|
178
|
+
case VariableType.CatchClause:
|
|
179
|
+
// TODO(bradzacher) -- this would be really easy to implement and
|
|
180
|
+
// is side-effect free!
|
|
181
|
+
return {};
|
|
182
|
+
case VariableType.ClassName:
|
|
183
|
+
// This would be easy to implement -- but classes can have
|
|
184
|
+
// side-effects in static initializers / static blocks. So it's
|
|
185
|
+
// dangerous to ever auto-fix remove them.
|
|
186
|
+
//
|
|
187
|
+
// Perhaps as an always-suggestion fixer...?
|
|
188
|
+
return {};
|
|
189
|
+
case VariableType.FunctionName:
|
|
190
|
+
// TODO(bradzacher) -- this would be really easy to implement and
|
|
191
|
+
// is side-effect free!
|
|
192
|
+
return {};
|
|
193
|
+
case VariableType.ImportBinding:
|
|
194
|
+
return {
|
|
195
|
+
...getImportFixer(def),
|
|
196
|
+
useAutofix: options.enableAutofixRemoval.imports,
|
|
197
|
+
};
|
|
198
|
+
case VariableType.ImplicitGlobalVariable:
|
|
199
|
+
// We don't report these via this code path, so no fixer is possible
|
|
200
|
+
return {};
|
|
201
|
+
case VariableType.Parameter:
|
|
202
|
+
// This is easy to implement -- but we cannot implement it cos it
|
|
203
|
+
// changes the signature of the function which in turn might
|
|
204
|
+
// introduce type errors in consumers.
|
|
205
|
+
//
|
|
206
|
+
// Also parameters can have default values which might have
|
|
207
|
+
// side-effects.
|
|
208
|
+
//
|
|
209
|
+
// Perhaps as an always-suggestion fixer...?
|
|
210
|
+
return {};
|
|
211
|
+
case VariableType.TSEnumMember:
|
|
212
|
+
// We don't report unused enum members so no fixer is ever possible
|
|
213
|
+
return {};
|
|
214
|
+
case VariableType.TSEnumName:
|
|
215
|
+
// TODO(bradzacher) -- this would be really easy to implement and
|
|
216
|
+
// is side-effect free!
|
|
217
|
+
return {};
|
|
218
|
+
case VariableType.TSModuleName:
|
|
219
|
+
// This is easy to implement -- but TS namespaces are eagerly
|
|
220
|
+
// initialized -- meaning that they might have side-effects in
|
|
221
|
+
// the body. So it's dangerous to ever auto-fix remove them.
|
|
222
|
+
//
|
|
223
|
+
// Perhaps as an always-suggestion fixer...?
|
|
224
|
+
return {};
|
|
225
|
+
case VariableType.Type:
|
|
226
|
+
// TODO(bradzacher) -- this would be really easy to implement and
|
|
227
|
+
// is side-effect free!
|
|
228
|
+
return {};
|
|
229
|
+
case VariableType.Variable:
|
|
230
|
+
// TODO(bradzacher) -- this would be really easy to implement
|
|
231
|
+
return {};
|
|
232
|
+
}
|
|
233
|
+
})();
|
|
234
|
+
if (!fix) {
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
if (useAutofix) {
|
|
238
|
+
return { fix };
|
|
239
|
+
}
|
|
240
|
+
const data = {
|
|
241
|
+
varName: unusedVar.name,
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
suggest: [
|
|
245
|
+
{
|
|
246
|
+
messageId: messageId ?? 'removeUnusedVar',
|
|
247
|
+
data,
|
|
248
|
+
fix,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
})();
|
|
253
|
+
context.report({
|
|
254
|
+
...opts,
|
|
255
|
+
...fixer,
|
|
256
|
+
loc,
|
|
257
|
+
node: id,
|
|
258
|
+
});
|
|
259
|
+
};
|
|
89
260
|
const options = (() => {
|
|
90
261
|
const options = {
|
|
91
262
|
args: 'after-used',
|
|
92
263
|
caughtErrors: 'all',
|
|
264
|
+
enableAutofixRemoval: {
|
|
265
|
+
imports: false,
|
|
266
|
+
},
|
|
93
267
|
ignoreClassWithStaticInitBlock: false,
|
|
94
268
|
ignoreRestSiblings: false,
|
|
95
269
|
ignoreUsingDeclarations: false,
|
|
@@ -126,9 +300,171 @@ exports.default = (0, util_1.createRule)({
|
|
|
126
300
|
if (firstOption.destructuredArrayIgnorePattern) {
|
|
127
301
|
options.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, 'u');
|
|
128
302
|
}
|
|
303
|
+
if (firstOption.enableAutofixRemoval) {
|
|
304
|
+
// eslint-disable-next-line unicorn/no-lonely-if -- will add more cases later
|
|
305
|
+
if (firstOption.enableAutofixRemoval.imports != null) {
|
|
306
|
+
options.enableAutofixRemoval.imports =
|
|
307
|
+
firstOption.enableAutofixRemoval.imports;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
129
310
|
}
|
|
130
311
|
return options;
|
|
131
312
|
})();
|
|
313
|
+
function getImportFixer(def) {
|
|
314
|
+
switch (def.node.type) {
|
|
315
|
+
case utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration:
|
|
316
|
+
// import equals declarations can only have one binding -- so we can
|
|
317
|
+
// just remove entire import declaration
|
|
318
|
+
return {
|
|
319
|
+
messageId: 'removeUnusedImportDeclaration',
|
|
320
|
+
fix: fixer => fixer.remove(def.node),
|
|
321
|
+
};
|
|
322
|
+
case utils_1.AST_NODE_TYPES.ImportDefaultSpecifier: {
|
|
323
|
+
const importDecl = def.node.parent;
|
|
324
|
+
if (importDecl.specifiers.length === 1 ||
|
|
325
|
+
areAllSpecifiersUnused(importDecl)) {
|
|
326
|
+
// all specifiers are unused -- so we can just remove entire import
|
|
327
|
+
// declaration
|
|
328
|
+
return {
|
|
329
|
+
messageId: 'removeUnusedImportDeclaration',
|
|
330
|
+
fix: fixer => fixer.remove(importDecl),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
// in this branch we know the following things:
|
|
334
|
+
// 1) there is at least one specifier that is used
|
|
335
|
+
// 2) the default specifier is unused
|
|
336
|
+
//
|
|
337
|
+
// by process of elimination we can deduce that there is at least one
|
|
338
|
+
// named specifier that is used
|
|
339
|
+
//
|
|
340
|
+
// i.e. the code must be import Unused, { Used, ... } from 'module';
|
|
341
|
+
//
|
|
342
|
+
// there's one or more unused named specifiers, so we must remove the
|
|
343
|
+
// default specifier in isolation including the trailing comma.
|
|
344
|
+
//
|
|
345
|
+
// import Unused, { Used, ... } from 'module';
|
|
346
|
+
// ^^^^^^^ remove this
|
|
347
|
+
//
|
|
348
|
+
// NOTE: we could also remove the spaces between the comma and the
|
|
349
|
+
// opening curly brace -- but this does risk removing comments. To be
|
|
350
|
+
// safe we'll be conservative for now
|
|
351
|
+
//
|
|
352
|
+
// TODO(bradzacher) -- consider removing the extra space whilst also
|
|
353
|
+
// preserving comments.
|
|
354
|
+
return {
|
|
355
|
+
messageId: 'removeUnusedVar',
|
|
356
|
+
fix: fixer => {
|
|
357
|
+
const comma = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(def.node), util_1.NullThrowsReasons.MissingToken(',', 'import specifier'));
|
|
358
|
+
assertToken(isCommaToken, comma);
|
|
359
|
+
return fixer.removeRange([
|
|
360
|
+
Math.min(def.node.range[0], comma.range[0]),
|
|
361
|
+
Math.max(def.node.range[1], comma.range[1]),
|
|
362
|
+
]);
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
case utils_1.AST_NODE_TYPES.ImportSpecifier: {
|
|
367
|
+
// guaranteed to NOT be in an export statement as we're inspecting an
|
|
368
|
+
// import
|
|
369
|
+
const importDecl = def.node.parent;
|
|
370
|
+
if (importDecl.specifiers.length === 1 ||
|
|
371
|
+
areAllSpecifiersUnused(importDecl)) {
|
|
372
|
+
// all specifiers are unused -- so we can just remove entire import
|
|
373
|
+
// declaration
|
|
374
|
+
return {
|
|
375
|
+
messageId: 'removeUnusedImportDeclaration',
|
|
376
|
+
fix: fixer => fixer.remove(importDecl),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
messageId: 'removeUnusedVar',
|
|
381
|
+
fix: fixer => {
|
|
382
|
+
const usedNamedSpecifiers = context.sourceCode
|
|
383
|
+
.getDeclaredVariables(importDecl)
|
|
384
|
+
.map(variable => {
|
|
385
|
+
if (reportedUnusedVariables.has(variable)) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const specifier = variable.defs[0].node;
|
|
389
|
+
if (specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
return specifier;
|
|
393
|
+
})
|
|
394
|
+
.filter(v => v != null);
|
|
395
|
+
if (usedNamedSpecifiers.length === 0) {
|
|
396
|
+
// in this branch we know the following things:
|
|
397
|
+
// 1) there is at least one specifier that is used
|
|
398
|
+
// 2) all named specifiers are unused
|
|
399
|
+
//
|
|
400
|
+
// by process of elimination we can deduce that there is a
|
|
401
|
+
// default specifier and it is the only used specifier
|
|
402
|
+
//
|
|
403
|
+
// i.e. the code must be import Used, { Unused, ... } from
|
|
404
|
+
// 'module';
|
|
405
|
+
//
|
|
406
|
+
// So we can just remove the entire curly content and the comma
|
|
407
|
+
// before, eg import Used, { Unused, ... } from 'module';
|
|
408
|
+
// ^^^^^^^^^^^^^^^^^ remove this
|
|
409
|
+
const leftCurly = assertToken(isLeftCurlyToken, context.sourceCode.getFirstToken(importDecl, isLeftCurlyToken.predicate));
|
|
410
|
+
const leftToken = assertToken(isCommaToken, context.sourceCode.getTokenBefore(leftCurly));
|
|
411
|
+
const rightToken = assertToken(isRightCurlyToken, context.sourceCode.getFirstToken(importDecl, isRightCurlyToken.predicate));
|
|
412
|
+
return fixer.removeRange([
|
|
413
|
+
leftToken.range[0],
|
|
414
|
+
rightToken.range[1],
|
|
415
|
+
]);
|
|
416
|
+
}
|
|
417
|
+
// in this branch we know there is at least one used named
|
|
418
|
+
// specifier which means we have to remove each unused specifier
|
|
419
|
+
// in isolation.
|
|
420
|
+
//
|
|
421
|
+
// there's 3 possible cases to care about: import { Unused,
|
|
422
|
+
// Used... } from 'module'; import { ...Used, Unused } from
|
|
423
|
+
// 'module'; import { ...Used, Unused, } from 'module';
|
|
424
|
+
//
|
|
425
|
+
// Note that because of the above usedNamedSpecifiers check we
|
|
426
|
+
// know that we don't have one of these cases: import { Unused }
|
|
427
|
+
// from 'module'; import { Unused, Unused... } from 'module';
|
|
428
|
+
// import { ...Unused, Unused, } from 'module';
|
|
429
|
+
//
|
|
430
|
+
// The result is that we know that there _must_ be a comma that
|
|
431
|
+
// needs cleaning up
|
|
432
|
+
//
|
|
433
|
+
// try to remove the leading comma first as it leads to a nicer
|
|
434
|
+
// fix output in most cases
|
|
435
|
+
//
|
|
436
|
+
// leading preferred: import { Used, Unused, Used } from 'module';
|
|
437
|
+
// ^^^^^^^^ remove import { Used, Used } from 'module';
|
|
438
|
+
//
|
|
439
|
+
// trailing preferred: import { Used, Unused, Used } from
|
|
440
|
+
// 'module'; ^^^^^^^ remove import { Used, Used } from
|
|
441
|
+
// 'module'; ^^ ugly double space
|
|
442
|
+
//
|
|
443
|
+
// But we need to still fallback to the trailing comma for cases
|
|
444
|
+
// where the unused specifier is the first in the import eg:
|
|
445
|
+
// import { Unused, Used } from 'module';
|
|
446
|
+
const maybeComma = context.sourceCode.getTokenBefore(def.node);
|
|
447
|
+
const comma = maybeComma && isCommaToken.predicate(maybeComma)
|
|
448
|
+
? maybeComma
|
|
449
|
+
: assertToken(isCommaToken, context.sourceCode.getTokenAfter(def.node));
|
|
450
|
+
return fixer.removeRange([
|
|
451
|
+
Math.min(def.node.range[0], comma.range[0]),
|
|
452
|
+
Math.max(def.node.range[1], comma.range[1]),
|
|
453
|
+
]);
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
case utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier: {
|
|
458
|
+
// namespace specifiers cannot be used with any other specifier -- so
|
|
459
|
+
// we can just remove entire import declaration
|
|
460
|
+
const importDecl = def.node.parent;
|
|
461
|
+
return {
|
|
462
|
+
messageId: 'removeUnusedImportDeclaration',
|
|
463
|
+
fix: fixer => fixer.remove(importDecl),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
132
468
|
/**
|
|
133
469
|
* Determines what variable type a def is.
|
|
134
470
|
* @param def the declaration to check
|
|
@@ -146,15 +482,31 @@ exports.default = (0, util_1.createRule)({
|
|
|
146
482
|
*/
|
|
147
483
|
if (options.destructuredArrayIgnorePattern &&
|
|
148
484
|
def.name.parent.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
149
|
-
return
|
|
485
|
+
return { type: VariableType.ArrayDestructure, def };
|
|
150
486
|
}
|
|
151
487
|
switch (def.type) {
|
|
152
488
|
case scope_manager_1.DefinitionType.CatchClause:
|
|
153
|
-
return
|
|
489
|
+
return { type: VariableType.CatchClause, def };
|
|
490
|
+
case scope_manager_1.DefinitionType.ClassName:
|
|
491
|
+
return { type: VariableType.ClassName, def };
|
|
492
|
+
case scope_manager_1.DefinitionType.FunctionName:
|
|
493
|
+
return { type: VariableType.FunctionName, def };
|
|
494
|
+
case scope_manager_1.DefinitionType.ImplicitGlobalVariable:
|
|
495
|
+
return { type: VariableType.ImplicitGlobalVariable, def };
|
|
496
|
+
case scope_manager_1.DefinitionType.ImportBinding:
|
|
497
|
+
return { type: VariableType.ImportBinding, def };
|
|
154
498
|
case scope_manager_1.DefinitionType.Parameter:
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
return
|
|
499
|
+
return { type: VariableType.Parameter, def };
|
|
500
|
+
case scope_manager_1.DefinitionType.TSEnumName:
|
|
501
|
+
return { type: VariableType.TSEnumName, def };
|
|
502
|
+
case scope_manager_1.DefinitionType.TSEnumMember:
|
|
503
|
+
return { type: VariableType.TSEnumMember, def };
|
|
504
|
+
case scope_manager_1.DefinitionType.TSModuleName:
|
|
505
|
+
return { type: VariableType.TSModuleName, def };
|
|
506
|
+
case scope_manager_1.DefinitionType.Type:
|
|
507
|
+
return { type: VariableType.Type, def };
|
|
508
|
+
case scope_manager_1.DefinitionType.Variable:
|
|
509
|
+
return { type: VariableType.Variable, def };
|
|
158
510
|
}
|
|
159
511
|
}
|
|
160
512
|
/**
|
|
@@ -166,22 +518,22 @@ exports.default = (0, util_1.createRule)({
|
|
|
166
518
|
*/
|
|
167
519
|
function getVariableDescription(variableType) {
|
|
168
520
|
switch (variableType) {
|
|
169
|
-
case
|
|
521
|
+
case VariableType.ArrayDestructure:
|
|
170
522
|
return {
|
|
171
523
|
pattern: options.destructuredArrayIgnorePattern?.toString(),
|
|
172
524
|
variableDescription: 'elements of array destructuring',
|
|
173
525
|
};
|
|
174
|
-
case
|
|
526
|
+
case VariableType.CatchClause:
|
|
175
527
|
return {
|
|
176
528
|
pattern: options.caughtErrorsIgnorePattern?.toString(),
|
|
177
529
|
variableDescription: 'caught errors',
|
|
178
530
|
};
|
|
179
|
-
case
|
|
531
|
+
case VariableType.Parameter:
|
|
180
532
|
return {
|
|
181
533
|
pattern: options.argsIgnorePattern?.toString(),
|
|
182
534
|
variableDescription: 'args',
|
|
183
535
|
};
|
|
184
|
-
|
|
536
|
+
default:
|
|
185
537
|
return {
|
|
186
538
|
pattern: options.varsIgnorePattern?.toString(),
|
|
187
539
|
variableDescription: 'vars',
|
|
@@ -198,7 +550,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
198
550
|
const def = unusedVar.defs.at(0);
|
|
199
551
|
let additionalMessageData = '';
|
|
200
552
|
if (def) {
|
|
201
|
-
const { pattern, variableDescription } = getVariableDescription(defToVariableType(def));
|
|
553
|
+
const { pattern, variableDescription } = getVariableDescription(defToVariableType(def).type);
|
|
202
554
|
if (pattern && variableDescription) {
|
|
203
555
|
additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
|
|
204
556
|
}
|
|
@@ -219,7 +571,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
219
571
|
const def = unusedVar.defs.at(0);
|
|
220
572
|
let additionalMessageData = '';
|
|
221
573
|
if (def) {
|
|
222
|
-
const { pattern, variableDescription } = getVariableDescription(defToVariableType(def));
|
|
574
|
+
const { pattern, variableDescription } = getVariableDescription(defToVariableType(def).type);
|
|
223
575
|
if (pattern && variableDescription) {
|
|
224
576
|
additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
|
|
225
577
|
}
|
|
@@ -318,10 +670,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
318
670
|
def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
319
671
|
options.destructuredArrayIgnorePattern?.test(def.name.name)) {
|
|
320
672
|
if (options.reportUsedIgnorePattern && used) {
|
|
321
|
-
|
|
322
|
-
node: def.name,
|
|
673
|
+
report(variable, {
|
|
323
674
|
messageId: 'usedIgnoredVar',
|
|
324
|
-
data: getUsedIgnoredMessageData(variable,
|
|
675
|
+
data: getUsedIgnoredMessageData(variable, VariableType.ArrayDestructure),
|
|
325
676
|
});
|
|
326
677
|
}
|
|
327
678
|
continue;
|
|
@@ -341,10 +692,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
341
692
|
if (def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
342
693
|
options.caughtErrorsIgnorePattern?.test(def.name.name)) {
|
|
343
694
|
if (options.reportUsedIgnorePattern && used) {
|
|
344
|
-
|
|
345
|
-
node: def.name,
|
|
695
|
+
report(variable, {
|
|
346
696
|
messageId: 'usedIgnoredVar',
|
|
347
|
-
data: getUsedIgnoredMessageData(variable,
|
|
697
|
+
data: getUsedIgnoredMessageData(variable, VariableType.CatchClause),
|
|
348
698
|
});
|
|
349
699
|
}
|
|
350
700
|
continue;
|
|
@@ -359,10 +709,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
359
709
|
if (def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
360
710
|
options.argsIgnorePattern?.test(def.name.name)) {
|
|
361
711
|
if (options.reportUsedIgnorePattern && used) {
|
|
362
|
-
|
|
363
|
-
node: def.name,
|
|
712
|
+
report(variable, {
|
|
364
713
|
messageId: 'usedIgnoredVar',
|
|
365
|
-
data: getUsedIgnoredMessageData(variable,
|
|
714
|
+
data: getUsedIgnoredMessageData(variable, VariableType.Parameter),
|
|
366
715
|
});
|
|
367
716
|
}
|
|
368
717
|
continue;
|
|
@@ -382,10 +731,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
382
731
|
/* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
|
|
383
732
|
unused. either way, don't complain about their naming. */
|
|
384
733
|
def.type !== utils_1.TSESLint.Scope.DefinitionType.TSEnumMember) {
|
|
385
|
-
|
|
386
|
-
node: def.name,
|
|
734
|
+
report(variable, {
|
|
387
735
|
messageId: 'usedIgnoredVar',
|
|
388
|
-
data: getUsedIgnoredMessageData(variable,
|
|
736
|
+
data: getUsedIgnoredMessageData(variable, VariableType.Variable),
|
|
389
737
|
});
|
|
390
738
|
}
|
|
391
739
|
continue;
|
|
@@ -455,28 +803,13 @@ exports.default = (0, util_1.createRule)({
|
|
|
455
803
|
// Report the first declaration.
|
|
456
804
|
if (unusedVar.defs.length > 0) {
|
|
457
805
|
const usedOnlyAsType = unusedVar.references.some(ref => (0, referenceContainsTypeQuery_1.referenceContainsTypeQuery)(ref.identifier));
|
|
806
|
+
const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar';
|
|
458
807
|
const isImportUsedOnlyAsType = usedOnlyAsType &&
|
|
459
808
|
unusedVar.defs.some(def => def.type === scope_manager_1.DefinitionType.ImportBinding);
|
|
460
809
|
if (isImportUsedOnlyAsType) {
|
|
461
810
|
continue;
|
|
462
811
|
}
|
|
463
|
-
|
|
464
|
-
ref.from.variableScope === unusedVar.scope.variableScope);
|
|
465
|
-
const id = writeReferences.length
|
|
466
|
-
? writeReferences[writeReferences.length - 1].identifier
|
|
467
|
-
: unusedVar.identifiers[0];
|
|
468
|
-
const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar';
|
|
469
|
-
const { start } = id.loc;
|
|
470
|
-
const idLength = id.name.length;
|
|
471
|
-
const loc = {
|
|
472
|
-
start,
|
|
473
|
-
end: {
|
|
474
|
-
column: start.column + idLength,
|
|
475
|
-
line: start.line,
|
|
476
|
-
},
|
|
477
|
-
};
|
|
478
|
-
context.report({
|
|
479
|
-
loc,
|
|
812
|
+
report(unusedVar, {
|
|
480
813
|
messageId,
|
|
481
814
|
data: unusedVar.references.some(ref => ref.isWrite())
|
|
482
815
|
? getAssignedMessageData(unusedVar)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typescript-eslint/eslint-plugin",
|
|
3
|
-
"version": "8.52.1-alpha.
|
|
3
|
+
"version": "8.52.1-alpha.11",
|
|
4
4
|
"description": "TypeScript plugin for ESLint",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@eslint-community/regexpp": "^4.12.2",
|
|
62
|
-
"@typescript-eslint/scope-manager": "8.52.1-alpha.
|
|
63
|
-
"@typescript-eslint/type-utils": "8.52.1-alpha.
|
|
64
|
-
"@typescript-eslint/utils": "8.52.1-alpha.
|
|
65
|
-
"@typescript-eslint/visitor-keys": "8.52.1-alpha.
|
|
62
|
+
"@typescript-eslint/scope-manager": "8.52.1-alpha.11",
|
|
63
|
+
"@typescript-eslint/type-utils": "8.52.1-alpha.11",
|
|
64
|
+
"@typescript-eslint/utils": "8.52.1-alpha.11",
|
|
65
|
+
"@typescript-eslint/visitor-keys": "8.52.1-alpha.11",
|
|
66
66
|
"ignore": "^7.0.5",
|
|
67
67
|
"natural-compare": "^1.4.0",
|
|
68
68
|
"ts-api-utils": "^2.4.0"
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/mdast": "^4.0.4",
|
|
72
72
|
"@types/natural-compare": "*",
|
|
73
|
-
"@typescript-eslint/rule-schema-to-typescript-types": "8.52.1-alpha.
|
|
74
|
-
"@typescript-eslint/rule-tester": "8.52.1-alpha.
|
|
73
|
+
"@typescript-eslint/rule-schema-to-typescript-types": "8.52.1-alpha.11",
|
|
74
|
+
"@typescript-eslint/rule-tester": "8.52.1-alpha.11",
|
|
75
75
|
"@vitest/coverage-v8": "^3.2.4",
|
|
76
76
|
"ajv": "^6.12.6",
|
|
77
77
|
"eslint": "*",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"vitest": "^3.2.4"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"@typescript-eslint/parser": "^8.52.1-alpha.
|
|
93
|
+
"@typescript-eslint/parser": "^8.52.1-alpha.11",
|
|
94
94
|
"eslint": "^8.57.0 || ^9.0.0",
|
|
95
95
|
"typescript": ">=4.8.4 <6.0.0"
|
|
96
96
|
},
|