UNPKG

21.6 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const scope_manager_1 = require("@typescript-eslint/scope-manager");
24const util = __importStar(require("../util"));
25exports.default = util.createRule({
26 name: 'no-unused-vars',
27 meta: {
28 type: 'problem',
29 docs: {
30 description: 'Disallow unused variables',
31 recommended: 'warn',
32 extendsBaseRule: true,
33 },
34 schema: [
35 {
36 oneOf: [
37 {
38 enum: ['all', 'local'],
39 },
40 {
41 type: 'object',
42 properties: {
43 vars: {
44 enum: ['all', 'local'],
45 },
46 varsIgnorePattern: {
47 type: 'string',
48 },
49 args: {
50 enum: ['all', 'after-used', 'none'],
51 },
52 ignoreRestSiblings: {
53 type: 'boolean',
54 },
55 argsIgnorePattern: {
56 type: 'string',
57 },
58 caughtErrors: {
59 enum: ['all', 'none'],
60 },
61 caughtErrorsIgnorePattern: {
62 type: 'string',
63 },
64 },
65 additionalProperties: false,
66 },
67 ],
68 },
69 ],
70 messages: {
71 unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
72 },
73 },
74 defaultOptions: [{}],
75 create(context) {
76 const filename = context.getFilename();
77 const sourceCode = context.getSourceCode();
78 const MODULE_DECL_CACHE = new Map();
79 const options = (() => {
80 var _a, _b, _c, _d;
81 const options = {
82 vars: 'all',
83 args: 'after-used',
84 ignoreRestSiblings: false,
85 caughtErrors: 'none',
86 };
87 const [firstOption] = context.options;
88 if (firstOption) {
89 if (typeof firstOption === 'string') {
90 options.vars = firstOption;
91 }
92 else {
93 options.vars = (_a = firstOption.vars) !== null && _a !== void 0 ? _a : options.vars;
94 options.args = (_b = firstOption.args) !== null && _b !== void 0 ? _b : options.args;
95 options.ignoreRestSiblings =
96 (_c = firstOption.ignoreRestSiblings) !== null && _c !== void 0 ? _c : options.ignoreRestSiblings;
97 options.caughtErrors =
98 (_d = firstOption.caughtErrors) !== null && _d !== void 0 ? _d : options.caughtErrors;
99 if (firstOption.varsIgnorePattern) {
100 options.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, 'u');
101 }
102 if (firstOption.argsIgnorePattern) {
103 options.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, 'u');
104 }
105 if (firstOption.caughtErrorsIgnorePattern) {
106 options.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, 'u');
107 }
108 }
109 }
110 return options;
111 })();
112 function collectUnusedVariables() {
113 var _a, _b, _c;
114 /**
115 * Determines if a variable has a sibling rest property
116 * @param variable eslint-scope variable object.
117 * @returns True if the variable is exported, false if not.
118 */
119 function hasRestSpreadSibling(variable) {
120 if (options.ignoreRestSiblings) {
121 return variable.defs.some(def => {
122 const propertyNode = def.name.parent;
123 const patternNode = propertyNode.parent;
124 return (propertyNode.type === utils_1.AST_NODE_TYPES.Property &&
125 patternNode.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
126 patternNode.properties[patternNode.properties.length - 1].type ===
127 utils_1.AST_NODE_TYPES.RestElement);
128 });
129 }
130 return false;
131 }
132 /**
133 * Checks whether the given variable is after the last used parameter.
134 * @param variable The variable to check.
135 * @returns `true` if the variable is defined after the last used parameter.
136 */
137 function isAfterLastUsedArg(variable) {
138 const def = variable.defs[0];
139 const params = context.getDeclaredVariables(def.node);
140 const posteriorParams = params.slice(params.indexOf(variable) + 1);
141 // If any used parameters occur after this parameter, do not report.
142 return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
143 }
144 const unusedVariablesOriginal = util.collectUnusedVariables(context);
145 const unusedVariablesReturn = [];
146 for (const variable of unusedVariablesOriginal) {
147 // explicit global variables don't have definitions.
148 if (variable.defs.length === 0) {
149 unusedVariablesReturn.push(variable);
150 continue;
151 }
152 const def = variable.defs[0];
153 if (variable.scope.type === utils_1.TSESLint.Scope.ScopeType.global &&
154 options.vars === 'local') {
155 // skip variables in the global scope if configured to
156 continue;
157 }
158 // skip catch variables
159 if (def.type === utils_1.TSESLint.Scope.DefinitionType.CatchClause) {
160 if (options.caughtErrors === 'none') {
161 continue;
162 }
163 // skip ignored parameters
164 if ('name' in def.name &&
165 ((_a = options.caughtErrorsIgnorePattern) === null || _a === void 0 ? void 0 : _a.test(def.name.name))) {
166 continue;
167 }
168 }
169 if (def.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) {
170 // if "args" option is "none", skip any parameter
171 if (options.args === 'none') {
172 continue;
173 }
174 // skip ignored parameters
175 if ('name' in def.name &&
176 ((_b = options.argsIgnorePattern) === null || _b === void 0 ? void 0 : _b.test(def.name.name))) {
177 continue;
178 }
179 // if "args" option is "after-used", skip used variables
180 if (options.args === 'after-used' &&
181 util.isFunction(def.name.parent) &&
182 !isAfterLastUsedArg(variable)) {
183 continue;
184 }
185 }
186 else {
187 // skip ignored variables
188 if ('name' in def.name &&
189 ((_c = options.varsIgnorePattern) === null || _c === void 0 ? void 0 : _c.test(def.name.name))) {
190 continue;
191 }
192 }
193 if (hasRestSpreadSibling(variable)) {
194 continue;
195 }
196 // in case another rule has run and used the collectUnusedVariables,
197 // we want to ensure our selectors that marked variables as used are respected
198 if (variable.eslintUsed) {
199 continue;
200 }
201 unusedVariablesReturn.push(variable);
202 }
203 return unusedVariablesReturn;
204 }
205 return {
206 // declaration file handling
207 [ambientDeclarationSelector(utils_1.AST_NODE_TYPES.Program, true)](node) {
208 if (!util.isDefinitionFile(filename)) {
209 return;
210 }
211 markDeclarationChildAsUsed(node);
212 },
213 // module declaration in module declaration should not report unused vars error
214 // this is workaround as this change should be done in better way
215 'TSModuleDeclaration > TSModuleDeclaration'(node) {
216 if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
217 let scope = context.getScope();
218 if (scope.upper) {
219 scope = scope.upper;
220 }
221 const superVar = scope.set.get(node.id.name);
222 if (superVar) {
223 superVar.eslintUsed = true;
224 }
225 }
226 },
227 // children of a namespace that is a child of a declared namespace are auto-exported
228 [ambientDeclarationSelector('TSModuleDeclaration[declare = true] > TSModuleBlock TSModuleDeclaration > TSModuleBlock', false)](node) {
229 markDeclarationChildAsUsed(node);
230 },
231 // declared namespace handling
232 [ambientDeclarationSelector('TSModuleDeclaration[declare = true] > TSModuleBlock', false)](node) {
233 var _a;
234 const moduleDecl = util.nullThrows((_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent, util.NullThrowsReasons.MissingParent);
235 // declared ambient modules with an `export =` statement will only export that one thing
236 // all other statements are not automatically exported in this case
237 if (moduleDecl.id.type === utils_1.AST_NODE_TYPES.Literal &&
238 checkModuleDeclForExportEquals(moduleDecl)) {
239 return;
240 }
241 markDeclarationChildAsUsed(node);
242 },
243 // collect
244 'Program:exit'(programNode) {
245 /**
246 * Generates the message data about the variable being defined and unused,
247 * including the ignore pattern if configured.
248 * @param unusedVar eslint-scope variable object.
249 * @returns The message data to be used with this unused variable.
250 */
251 function getDefinedMessageData(unusedVar) {
252 var _a;
253 const defType = (_a = unusedVar === null || unusedVar === void 0 ? void 0 : unusedVar.defs[0]) === null || _a === void 0 ? void 0 : _a.type;
254 let type;
255 let pattern;
256 if (defType === utils_1.TSESLint.Scope.DefinitionType.CatchClause &&
257 options.caughtErrorsIgnorePattern) {
258 type = 'args';
259 pattern = options.caughtErrorsIgnorePattern.toString();
260 }
261 else if (defType === utils_1.TSESLint.Scope.DefinitionType.Parameter &&
262 options.argsIgnorePattern) {
263 type = 'args';
264 pattern = options.argsIgnorePattern.toString();
265 }
266 else if (defType !== utils_1.TSESLint.Scope.DefinitionType.Parameter &&
267 options.varsIgnorePattern) {
268 type = 'vars';
269 pattern = options.varsIgnorePattern.toString();
270 }
271 const additional = type
272 ? `. Allowed unused ${type} must match ${pattern}`
273 : '';
274 return {
275 varName: unusedVar.name,
276 action: 'defined',
277 additional,
278 };
279 }
280 /**
281 * Generate the warning message about the variable being
282 * assigned and unused, including the ignore pattern if configured.
283 * @param unusedVar eslint-scope variable object.
284 * @returns The message data to be used with this unused variable.
285 */
286 function getAssignedMessageData(unusedVar) {
287 const additional = options.varsIgnorePattern
288 ? `. Allowed unused vars must match ${options.varsIgnorePattern.toString()}`
289 : '';
290 return {
291 varName: unusedVar.name,
292 action: 'assigned a value',
293 additional,
294 };
295 }
296 const unusedVars = collectUnusedVariables();
297 for (let i = 0, l = unusedVars.length; i < l; ++i) {
298 const unusedVar = unusedVars[i];
299 // Report the first declaration.
300 if (unusedVar.defs.length > 0) {
301 context.report({
302 node: unusedVar.references.length
303 ? unusedVar.references[unusedVar.references.length - 1]
304 .identifier
305 : unusedVar.identifiers[0],
306 messageId: 'unusedVar',
307 data: unusedVar.references.some(ref => ref.isWrite())
308 ? getAssignedMessageData(unusedVar)
309 : getDefinedMessageData(unusedVar),
310 });
311 // If there are no regular declaration, report the first `/*globals*/` comment directive.
312 }
313 else if ('eslintExplicitGlobalComments' in unusedVar &&
314 unusedVar.eslintExplicitGlobalComments) {
315 const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
316 context.report({
317 node: programNode,
318 loc: util.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
319 messageId: 'unusedVar',
320 data: getDefinedMessageData(unusedVar),
321 });
322 }
323 }
324 },
325 };
326 function checkModuleDeclForExportEquals(node) {
327 const cached = MODULE_DECL_CACHE.get(node);
328 if (cached != null) {
329 return cached;
330 }
331 if (node.body && node.body.type === utils_1.AST_NODE_TYPES.TSModuleBlock) {
332 for (const statement of node.body.body) {
333 if (statement.type === utils_1.AST_NODE_TYPES.TSExportAssignment) {
334 MODULE_DECL_CACHE.set(node, true);
335 return true;
336 }
337 }
338 }
339 MODULE_DECL_CACHE.set(node, false);
340 return false;
341 }
342 function ambientDeclarationSelector(parent, childDeclare) {
343 return [
344 // Types are ambiently exported
345 `${parent} > :matches(${[
346 utils_1.AST_NODE_TYPES.TSInterfaceDeclaration,
347 utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration,
348 ].join(', ')})`,
349 // Value things are ambiently exported if they are "declare"d
350 `${parent} > :matches(${[
351 utils_1.AST_NODE_TYPES.ClassDeclaration,
352 utils_1.AST_NODE_TYPES.TSDeclareFunction,
353 utils_1.AST_NODE_TYPES.TSEnumDeclaration,
354 utils_1.AST_NODE_TYPES.TSModuleDeclaration,
355 utils_1.AST_NODE_TYPES.VariableDeclaration,
356 ].join(', ')})${childDeclare ? '[declare = true]' : ''}`,
357 ].join(', ');
358 }
359 function markDeclarationChildAsUsed(node) {
360 var _a;
361 const identifiers = [];
362 switch (node.type) {
363 case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration:
364 case utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration:
365 case utils_1.AST_NODE_TYPES.ClassDeclaration:
366 case utils_1.AST_NODE_TYPES.FunctionDeclaration:
367 case utils_1.AST_NODE_TYPES.TSDeclareFunction:
368 case utils_1.AST_NODE_TYPES.TSEnumDeclaration:
369 case utils_1.AST_NODE_TYPES.TSModuleDeclaration:
370 if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.Identifier) {
371 identifiers.push(node.id);
372 }
373 break;
374 case utils_1.AST_NODE_TYPES.VariableDeclaration:
375 for (const declaration of node.declarations) {
376 visitPattern(declaration, pattern => {
377 identifiers.push(pattern);
378 });
379 }
380 break;
381 }
382 let scope = context.getScope();
383 const shouldUseUpperScope = [
384 utils_1.AST_NODE_TYPES.TSModuleDeclaration,
385 utils_1.AST_NODE_TYPES.TSDeclareFunction,
386 ].includes(node.type);
387 if (scope.variableScope !== scope) {
388 scope = scope.variableScope;
389 }
390 else if (shouldUseUpperScope && scope.upper) {
391 scope = scope.upper;
392 }
393 for (const id of identifiers) {
394 const superVar = scope.set.get(id.name);
395 if (superVar) {
396 superVar.eslintUsed = true;
397 }
398 }
399 }
400 function visitPattern(node, cb) {
401 const visitor = new scope_manager_1.PatternVisitor({}, node, cb);
402 visitor.visit(node);
403 }
404 },
405});
406/*
407
408###### TODO ######
409
410Edge cases that aren't currently handled due to laziness and them being super edgy edge cases
411
412
413--- function params referenced in typeof type refs in the function declaration ---
414--- NOTE - TS gets these cases wrong
415
416function _foo(
417 arg: number // arg should be unused
418): typeof arg {
419 return 1 as any;
420}
421
422function _bar(
423 arg: number, // arg should be unused
424 _arg2: typeof arg,
425) {}
426
427
428--- function names referenced in typeof type refs in the function declaration ---
429--- NOTE - TS gets these cases right
430
431function foo( // foo should be unused
432): typeof foo {
433 return 1 as any;
434}
435
436function bar( // bar should be unused
437 _arg: typeof bar
438) {}
439
440
441--- if an interface is merged into a namespace ---
442--- NOTE - TS gets these cases wrong
443
444namespace Test {
445 interface Foo { // Foo should be unused here
446 a: string;
447 }
448 export namespace Foo {
449 export type T = 'b';
450 }
451}
452type T = Test.Foo; // Error: Namespace 'Test' has no exported member 'Foo'.
453
454
455namespace Test {
456 export interface Foo {
457 a: string;
458 }
459 namespace Foo { // Foo should be unused here
460 export type T = 'b';
461 }
462}
463type T = Test.Foo.T; // Error: Namespace 'Test' has no exported member 'Foo'.
464
465*/
466/*
467
468###### TODO ######
469
470We currently extend base `no-unused-vars` implementation because it's easier and lighter-weight.
471
472Because of this, there are a few false-negatives which won't get caught.
473We could fix these if we fork the base rule; but that's a lot of code (~650 lines) to add in.
474I didn't want to do that just yet without some real-world issues, considering these are pretty rare edge-cases.
475
476These cases are mishandled because the base rule assumes that each variable has one def, but type-value shadowing
477creates a variable with two defs
478
479--- type-only or value-only references to type/value shadowed variables ---
480--- NOTE - TS gets these cases wrong
481
482type T = 1;
483const T = 2; // this T should be unused
484
485type U = T; // this U should be unused
486const U = 3;
487
488const _V = U;
489
490
491--- partially exported type/value shadowed variables ---
492--- NOTE - TS gets these cases wrong
493
494export interface Foo {}
495const Foo = 1; // this Foo should be unused
496
497interface Bar {} // this Bar should be unused
498export const Bar = 1;
499
500*/
501//# sourceMappingURL=no-unused-vars.js.map
\No newline at end of file