| "use strict"; |
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); |
| }) : (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| o[k2] = m[k]; |
| })); |
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { |
| Object.defineProperty(o, "default", { enumerable: true, value: v }); |
| }) : function(o, v) { |
| o["default"] = v; |
| }); |
| var __importStar = (this && this.__importStar) || function (mod) { |
| if (mod && mod.__esModule) return mod; |
| var result = {}; |
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); |
| __setModuleDefault(result, mod); |
| return result; |
| }; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| const utils_1 = require("@typescript-eslint/utils"); |
| const scope_manager_1 = require("@typescript-eslint/scope-manager"); |
| const util = __importStar(require("../util")); |
| exports.default = util.createRule({ |
| name: 'no-unused-vars', |
| meta: { |
| type: 'problem', |
| docs: { |
| description: 'Disallow unused variables', |
| recommended: 'warn', |
| extendsBaseRule: true, |
| }, |
| schema: [ |
| { |
| oneOf: [ |
| { |
| enum: ['all', 'local'], |
| }, |
| { |
| type: 'object', |
| properties: { |
| vars: { |
| enum: ['all', 'local'], |
| }, |
| varsIgnorePattern: { |
| type: 'string', |
| }, |
| args: { |
| enum: ['all', 'after-used', 'none'], |
| }, |
| ignoreRestSiblings: { |
| type: 'boolean', |
| }, |
| argsIgnorePattern: { |
| type: 'string', |
| }, |
| caughtErrors: { |
| enum: ['all', 'none'], |
| }, |
| caughtErrorsIgnorePattern: { |
| type: 'string', |
| }, |
| }, |
| additionalProperties: false, |
| }, |
| ], |
| }, |
| ], |
| messages: { |
| unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.", |
| }, |
| }, |
| defaultOptions: [{}], |
| create(context) { |
| const filename = context.getFilename(); |
| const sourceCode = context.getSourceCode(); |
| const MODULE_DECL_CACHE = new Map(); |
| const options = (() => { |
| var _a, _b, _c, _d; |
| const options = { |
| vars: 'all', |
| args: 'after-used', |
| ignoreRestSiblings: false, |
| caughtErrors: 'none', |
| }; |
| const [firstOption] = context.options; |
| if (firstOption) { |
| if (typeof firstOption === 'string') { |
| options.vars = firstOption; |
| } |
| else { |
| options.vars = (_a = firstOption.vars) !== null && _a !== void 0 ? _a : options.vars; |
| options.args = (_b = firstOption.args) !== null && _b !== void 0 ? _b : options.args; |
| options.ignoreRestSiblings = |
| (_c = firstOption.ignoreRestSiblings) !== null && _c !== void 0 ? _c : options.ignoreRestSiblings; |
| options.caughtErrors = |
| (_d = firstOption.caughtErrors) !== null && _d !== void 0 ? _d : options.caughtErrors; |
| if (firstOption.varsIgnorePattern) { |
| options.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, 'u'); |
| } |
| if (firstOption.argsIgnorePattern) { |
| options.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, 'u'); |
| } |
| if (firstOption.caughtErrorsIgnorePattern) { |
| options.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, 'u'); |
| } |
| } |
| } |
| return options; |
| })(); |
| function collectUnusedVariables() { |
| var _a, _b, _c; |
| /** |
| * Determines if a variable has a sibling rest property |
| * @param variable eslint-scope variable object. |
| * @returns True if the variable is exported, false if not. |
| */ |
| function hasRestSpreadSibling(variable) { |
| if (options.ignoreRestSiblings) { |
| return variable.defs.some(def => { |
| const propertyNode = def.name.parent; |
| const patternNode = propertyNode.parent; |
| return (propertyNode.type === utils_1.AST_NODE_TYPES.Property && |
| patternNode.type === utils_1.AST_NODE_TYPES.ObjectPattern && |
| patternNode.properties[patternNode.properties.length - 1].type === |
| utils_1.AST_NODE_TYPES.RestElement); |
| }); |
| } |
| return false; |
| } |
| /** |
| * Checks whether the given variable is after the last used parameter. |
| * @param variable The variable to check. |
| * @returns `true` if the variable is defined after the last used parameter. |
| */ |
| function isAfterLastUsedArg(variable) { |
| const def = variable.defs[0]; |
| const params = context.getDeclaredVariables(def.node); |
| const posteriorParams = params.slice(params.indexOf(variable) + 1); |
| // If any used parameters occur after this parameter, do not report. |
| return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed); |
| } |
| const unusedVariablesOriginal = util.collectUnusedVariables(context); |
| const unusedVariablesReturn = []; |
| for (const variable of unusedVariablesOriginal) { |
| // explicit global variables don't have definitions. |
| if (variable.defs.length === 0) { |
| unusedVariablesReturn.push(variable); |
| continue; |
| } |
| const def = variable.defs[0]; |
| if (variable.scope.type === utils_1.TSESLint.Scope.ScopeType.global && |
| options.vars === 'local') { |
| // skip variables in the global scope if configured to |
| continue; |
| } |
| // skip catch variables |
| if (def.type === utils_1.TSESLint.Scope.DefinitionType.CatchClause) { |
| if (options.caughtErrors === 'none') { |
| continue; |
| } |
| // skip ignored parameters |
| if ('name' in def.name && |
| ((_a = options.caughtErrorsIgnorePattern) === null || _a === void 0 ? void 0 : _a.test(def.name.name))) { |
| continue; |
| } |
| } |
| if (def.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) { |
| // if "args" option is "none", skip any parameter |
| if (options.args === 'none') { |
| continue; |
| } |
| // skip ignored parameters |
| if ('name' in def.name && |
| ((_b = options.argsIgnorePattern) === null || _b === void 0 ? void 0 : _b.test(def.name.name))) { |
| continue; |
| } |
| // if "args" option is "after-used", skip used variables |
| if (options.args === 'after-used' && |
| util.isFunction(def.name.parent) && |
| !isAfterLastUsedArg(variable)) { |
| continue; |
| } |
| } |
| else { |
| // skip ignored variables |
| if ('name' in def.name && |
| ((_c = options.varsIgnorePattern) === null || _c === void 0 ? void 0 : _c.test(def.name.name))) { |
| continue; |
| } |
| } |
| if (hasRestSpreadSibling(variable)) { |
| continue; |
| } |
| // in case another rule has run and used the collectUnusedVariables, |
| // we want to ensure our selectors that marked variables as used are respected |
| if (variable.eslintUsed) { |
| continue; |
| } |
| unusedVariablesReturn.push(variable); |
| } |
| return unusedVariablesReturn; |
| } |
| return { |
| // declaration file handling |
| [ambientDeclarationSelector(utils_1.AST_NODE_TYPES.Program, true)](node) { |
| if (!util.isDefinitionFile(filename)) { |
| return; |
| } |
| markDeclarationChildAsUsed(node); |
| }, |
| // module declaration in module declaration should not report unused vars error |
| // this is workaround as this change should be done in better way |
| 'TSModuleDeclaration > TSModuleDeclaration'(node) { |
| if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) { |
| let scope = context.getScope(); |
| if (scope.upper) { |
| scope = scope.upper; |
| } |
| const superVar = scope.set.get(node.id.name); |
| if (superVar) { |
| superVar.eslintUsed = true; |
| } |
| } |
| }, |
| // children of a namespace that is a child of a declared namespace are auto-exported |
| [ambientDeclarationSelector('TSModuleDeclaration[declare = true] > TSModuleBlock TSModuleDeclaration > TSModuleBlock', false)](node) { |
| markDeclarationChildAsUsed(node); |
| }, |
| // declared namespace handling |
| [ambientDeclarationSelector('TSModuleDeclaration[declare = true] > TSModuleBlock', false)](node) { |
| var _a; |
| const moduleDecl = util.nullThrows((_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent, util.NullThrowsReasons.MissingParent); |
| // declared ambient modules with an `export =` statement will only export that one thing |
| // all other statements are not automatically exported in this case |
| if (moduleDecl.id.type === utils_1.AST_NODE_TYPES.Literal && |
| checkModuleDeclForExportEquals(moduleDecl)) { |
| return; |
| } |
| markDeclarationChildAsUsed(node); |
| }, |
| // collect |
| 'Program:exit'(programNode) { |
| /** |
| * Generates the message data about the variable being defined and unused, |
| * including the ignore pattern if configured. |
| * @param unusedVar eslint-scope variable object. |
| * @returns The message data to be used with this unused variable. |
| */ |
| function getDefinedMessageData(unusedVar) { |
| var _a; |
| const defType = (_a = unusedVar === null || unusedVar === void 0 ? void 0 : unusedVar.defs[0]) === null || _a === void 0 ? void 0 : _a.type; |
| let type; |
| let pattern; |
| if (defType === utils_1.TSESLint.Scope.DefinitionType.CatchClause && |
| options.caughtErrorsIgnorePattern) { |
| type = 'args'; |
| pattern = options.caughtErrorsIgnorePattern.toString(); |
| } |
| else if (defType === utils_1.TSESLint.Scope.DefinitionType.Parameter && |
| options.argsIgnorePattern) { |
| type = 'args'; |
| pattern = options.argsIgnorePattern.toString(); |
| } |
| else if (defType !== utils_1.TSESLint.Scope.DefinitionType.Parameter && |
| options.varsIgnorePattern) { |
| type = 'vars'; |
| pattern = options.varsIgnorePattern.toString(); |
| } |
| const additional = type |
| ? `. Allowed unused ${type} must match ${pattern}` |
| : ''; |
| return { |
| varName: unusedVar.name, |
| action: 'defined', |
| additional, |
| }; |
| } |
| /** |
| * Generate the warning message about the variable being |
| * assigned and unused, including the ignore pattern if configured. |
| * @param unusedVar eslint-scope variable object. |
| * @returns The message data to be used with this unused variable. |
| */ |
| function getAssignedMessageData(unusedVar) { |
| const additional = options.varsIgnorePattern |
| ? `. Allowed unused vars must match ${options.varsIgnorePattern.toString()}` |
| : ''; |
| return { |
| varName: unusedVar.name, |
| action: 'assigned a value', |
| additional, |
| }; |
| } |
| const unusedVars = collectUnusedVariables(); |
| for (let i = 0, l = unusedVars.length; i < l; ++i) { |
| const unusedVar = unusedVars[i]; |
| // Report the first declaration. |
| if (unusedVar.defs.length > 0) { |
| context.report({ |
| node: unusedVar.references.length |
| ? unusedVar.references[unusedVar.references.length - 1] |
| .identifier |
| : unusedVar.identifiers[0], |
| messageId: 'unusedVar', |
| data: unusedVar.references.some(ref => ref.isWrite()) |
| ? getAssignedMessageData(unusedVar) |
| : getDefinedMessageData(unusedVar), |
| }); |
| // If there are no regular declaration, report the first `/*globals*/` comment directive. |
| } |
| else if ('eslintExplicitGlobalComments' in unusedVar && |
| unusedVar.eslintExplicitGlobalComments) { |
| const directiveComment = unusedVar.eslintExplicitGlobalComments[0]; |
| context.report({ |
| node: programNode, |
| loc: util.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name), |
| messageId: 'unusedVar', |
| data: getDefinedMessageData(unusedVar), |
| }); |
| } |
| } |
| }, |
| }; |
| function checkModuleDeclForExportEquals(node) { |
| const cached = MODULE_DECL_CACHE.get(node); |
| if (cached != null) { |
| return cached; |
| } |
| if (node.body && node.body.type === utils_1.AST_NODE_TYPES.TSModuleBlock) { |
| for (const statement of node.body.body) { |
| if (statement.type === utils_1.AST_NODE_TYPES.TSExportAssignment) { |
| MODULE_DECL_CACHE.set(node, true); |
| return true; |
| } |
| } |
| } |
| MODULE_DECL_CACHE.set(node, false); |
| return false; |
| } |
| function ambientDeclarationSelector(parent, childDeclare) { |
| return [ |
| // Types are ambiently exported |
| `${parent} > :matches(${[ |
| utils_1.AST_NODE_TYPES.TSInterfaceDeclaration, |
| utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration, |
| ].join(', ')})`, |
| // Value things are ambiently exported if they are "declare"d |
| `${parent} > :matches(${[ |
| utils_1.AST_NODE_TYPES.ClassDeclaration, |
| utils_1.AST_NODE_TYPES.TSDeclareFunction, |
| utils_1.AST_NODE_TYPES.TSEnumDeclaration, |
| utils_1.AST_NODE_TYPES.TSModuleDeclaration, |
| utils_1.AST_NODE_TYPES.VariableDeclaration, |
| ].join(', ')})${childDeclare ? '[declare = true]' : ''}`, |
| ].join(', '); |
| } |
| function markDeclarationChildAsUsed(node) { |
| var _a; |
| const identifiers = []; |
| switch (node.type) { |
| case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration: |
| case utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration: |
| case utils_1.AST_NODE_TYPES.ClassDeclaration: |
| case utils_1.AST_NODE_TYPES.FunctionDeclaration: |
| case utils_1.AST_NODE_TYPES.TSDeclareFunction: |
| case utils_1.AST_NODE_TYPES.TSEnumDeclaration: |
| case utils_1.AST_NODE_TYPES.TSModuleDeclaration: |
| if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.Identifier) { |
| identifiers.push(node.id); |
| } |
| break; |
| case utils_1.AST_NODE_TYPES.VariableDeclaration: |
| for (const declaration of node.declarations) { |
| visitPattern(declaration, pattern => { |
| identifiers.push(pattern); |
| }); |
| } |
| break; |
| } |
| let scope = context.getScope(); |
| const shouldUseUpperScope = [ |
| utils_1.AST_NODE_TYPES.TSModuleDeclaration, |
| utils_1.AST_NODE_TYPES.TSDeclareFunction, |
| ].includes(node.type); |
| if (scope.variableScope !== scope) { |
| scope = scope.variableScope; |
| } |
| else if (shouldUseUpperScope && scope.upper) { |
| scope = scope.upper; |
| } |
| for (const id of identifiers) { |
| const superVar = scope.set.get(id.name); |
| if (superVar) { |
| superVar.eslintUsed = true; |
| } |
| } |
| } |
| function visitPattern(node, cb) { |
| const visitor = new scope_manager_1.PatternVisitor({}, node, cb); |
| visitor.visit(node); |
| } |
| }, |
| }); |
| /* |
| |
| ###### TODO ###### |
| |
| Edge cases that aren't currently handled due to laziness and them being super edgy edge cases |
| |
| |
| --- function params referenced in typeof type refs in the function declaration --- |
| --- NOTE - TS gets these cases wrong |
| |
| function _foo( |
| arg: number // arg should be unused |
| ): typeof arg { |
| return 1 as any; |
| } |
| |
| function _bar( |
| arg: number, // arg should be unused |
| _arg2: typeof arg, |
| ) {} |
| |
| |
| --- function names referenced in typeof type refs in the function declaration --- |
| --- NOTE - TS gets these cases right |
| |
| function foo( // foo should be unused |
| ): typeof foo { |
| return 1 as any; |
| } |
| |
| function bar( // bar should be unused |
| _arg: typeof bar |
| ) {} |
| |
| |
| --- if an interface is merged into a namespace --- |
| --- NOTE - TS gets these cases wrong |
| |
| namespace Test { |
| interface Foo { // Foo should be unused here |
| a: string; |
| } |
| export namespace Foo { |
| export type T = 'b'; |
| } |
| } |
| type T = Test.Foo; // Error: Namespace 'Test' has no exported member 'Foo'. |
| |
| |
| namespace Test { |
| export interface Foo { |
| a: string; |
| } |
| namespace Foo { // Foo should be unused here |
| export type T = 'b'; |
| } |
| } |
| type T = Test.Foo.T; // Error: Namespace 'Test' has no exported member 'Foo'. |
| |
| */ |
| /* |
| |
| ###### TODO ###### |
| |
| We currently extend base `no-unused-vars` implementation because it's easier and lighter-weight. |
| |
| Because of this, there are a few false-negatives which won't get caught. |
| We could fix these if we fork the base rule; but that's a lot of code (~650 lines) to add in. |
| I didn't want to do that just yet without some real-world issues, considering these are pretty rare edge-cases. |
| |
| These cases are mishandled because the base rule assumes that each variable has one def, but type-value shadowing |
| creates a variable with two defs |
| |
| --- type-only or value-only references to type/value shadowed variables --- |
| --- NOTE - TS gets these cases wrong |
| |
| type T = 1; |
| const T = 2; // this T should be unused |
| |
| type U = T; // this U should be unused |
| const U = 3; |
| |
| const _V = U; |
| |
| |
| --- partially exported type/value shadowed variables --- |
| --- NOTE - TS gets these cases wrong |
| |
| export interface Foo {} |
| const Foo = 1; // this Foo should be unused |
| |
| interface Bar {} // this Bar should be unused |
| export const Bar = 1; |
| |
| */ |
| //# sourceMappingURL=no-unused-vars.js.map |