blob: fd057221568d28323a308351f973029960a1ada9 [file] [log] [blame]
"use strict";
/* eslint-disable no-fallthrough */
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 });
exports.DEFAULT_IGNORED_REGEX_STRING = void 0;
const ts = __importStar(require("typescript"));
const util = __importStar(require("../util"));
exports.DEFAULT_IGNORED_REGEX_STRING = '^_';
exports.default = util.createRule({
name: 'no-unused-vars-experimental',
meta: {
type: 'problem',
docs: {
description: 'Disallow unused variables and arguments',
category: 'Best Practices',
recommended: false,
},
deprecated: true,
replacedBy: ['no-unused-vars'],
schema: [
{
type: 'object',
properties: {
ignoredNamesRegex: {
oneOf: [
{
type: 'string',
},
{
type: 'boolean',
enum: [false],
},
],
},
ignoreArgsIfArgsAfterAreUsed: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
messages: {
unused: "{{type}} '{{name}}' is declared but its value is never read.",
unusedWithIgnorePattern: "{{type}} '{{name}}' is declared but its value is never read. Allowed unused names must match {{pattern}}.",
unusedImport: 'All imports in import declaration are unused.',
unusedTypeParameters: 'All type parameters are unused.',
},
},
defaultOptions: [
{
ignoredNamesRegex: exports.DEFAULT_IGNORED_REGEX_STRING,
ignoreArgsIfArgsAfterAreUsed: false,
},
],
create(context, [userOptions]) {
var _a;
const parserServices = util.getParserServices(context, true);
const tsProgram = parserServices.program;
const afterAllDiagnosticsCallbacks = [];
const options = {
ignoredNames: userOptions && typeof userOptions.ignoredNamesRegex === 'string'
? new RegExp(userOptions.ignoredNamesRegex)
: null,
ignoreArgsIfArgsAfterAreUsed: (_a = userOptions.ignoreArgsIfArgsAfterAreUsed) !== null && _a !== void 0 ? _a : false,
};
function handleIdentifier(identifier) {
function report(type) {
const node = parserServices.tsNodeToESTreeNodeMap.get(identifier);
const regex = options.ignoredNames;
const name = identifier.getText();
if (regex) {
if (!regex.test(name)) {
context.report({
node,
messageId: 'unusedWithIgnorePattern',
data: {
name,
type,
pattern: regex.toString(),
},
});
}
}
else {
context.report({
node,
messageId: 'unused',
data: {
name,
type,
},
});
}
}
const parent = identifier.parent;
// is a single variable diagnostic
switch (parent.kind) {
case ts.SyntaxKind.BindingElement:
case ts.SyntaxKind.ObjectBindingPattern:
report('Destructured Variable');
break;
case ts.SyntaxKind.ClassDeclaration:
report('Class');
break;
case ts.SyntaxKind.EnumDeclaration:
report('Enum');
break;
case ts.SyntaxKind.FunctionDeclaration:
report('Function');
break;
// this won't happen because there are specific nodes that wrap up named/default import identifiers
// case ts.SyntaxKind.ImportDeclaration:
// import equals is always treated as a variable
case ts.SyntaxKind.ImportEqualsDeclaration:
// the default import is NOT used, but a named import is used
case ts.SyntaxKind.ImportClause:
// a named import is NOT used, but either another named import, or the default import is used
case ts.SyntaxKind.ImportSpecifier:
// a namespace import is NOT used, but the default import is used
case ts.SyntaxKind.NamespaceImport:
report('Import');
break;
case ts.SyntaxKind.InterfaceDeclaration:
report('Interface');
break;
case ts.SyntaxKind.MethodDeclaration:
report('Method');
break;
case ts.SyntaxKind.Parameter:
handleParameterDeclaration(identifier, parent);
break;
case ts.SyntaxKind.PropertyDeclaration:
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
report('Property');
break;
case ts.SyntaxKind.TypeAliasDeclaration:
report('Type');
break;
case ts.SyntaxKind.TypeParameter:
handleTypeParam(identifier);
break;
case ts.SyntaxKind.VariableDeclaration:
report('Variable');
break;
default:
throw new Error(`Unknown node with kind ${parent.kind}.`);
// TODO - should we just handle this gracefully?
// report('Unknown Node');
// break;
}
}
const unusedParameters = new Set();
function handleParameterDeclaration(identifier, parent) {
const name = identifier.getText();
// regardless of if the parameter is ignored, track that it had a diagnostic fired on it
unusedParameters.add(identifier);
/*
NOTE - Typescript will automatically ignore parameters that have a
leading underscore in their name. We cannot do anything about this.
*/
function report() {
const node = parserServices.tsNodeToESTreeNodeMap.get(identifier);
context.report({
node,
messageId: 'unused',
data: {
name,
type: 'Parameter',
},
});
}
const isLastParameter = parent.parent.parameters.indexOf(parent) ===
parent.parent.parameters.length - 1;
if (!isLastParameter && options.ignoreArgsIfArgsAfterAreUsed) {
// once all diagnostics are processed, we can check if the following args are unused
afterAllDiagnosticsCallbacks.push(() => {
for (const param of parent.parent.parameters) {
if (!unusedParameters.has(param.name)) {
return;
}
}
// none of the following params were unused, so report
report();
});
}
else {
report();
}
}
function handleImportDeclaration(parent) {
// the entire import statement is unused
/*
NOTE - Typescript will automatically ignore imports that have a
leading underscore in their name. We cannot do anything about this.
*/
context.report({
messageId: 'unusedImport',
node: parserServices.tsNodeToESTreeNodeMap.get(parent),
});
}
function handleDestructure(parent) {
// the entire destructure is unused
// note that this case only ever triggers for simple, single-level destructured objects
// i.e. these will not trigger it:
// - const {a:_a, b, c: {d}} = z;
// - const [a, b] = c;
parent.elements.forEach(element => {
if (element.kind === ts.SyntaxKind.BindingElement) {
const name = element.name;
if (name.kind === ts.SyntaxKind.Identifier) {
handleIdentifier(name);
}
}
});
}
function handleTypeParamList(node) {
// the entire generic decl list is unused
/*
NOTE - Typescript will automatically ignore generics that have a
leading underscore in their name. We cannot do anything about this.
*/
const parent = parserServices.tsNodeToESTreeNodeMap.get(node);
context.report({
messageId: 'unusedTypeParameters',
node: parent.typeParameters,
});
}
function handleTypeParam(identifier) {
context.report({
node: parserServices.tsNodeToESTreeNodeMap.get(identifier),
messageId: 'unused',
data: {
name: identifier.getText(),
type: 'Type Parameter',
},
});
}
return {
'Program:exit'(program) {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(program);
const sourceFile = util.getSourceFileOfNode(tsNode);
const diagnostics = tsProgram.getSemanticDiagnostics(sourceFile);
diagnostics.forEach(diag => {
if (isUnusedDiagnostic(diag.code)) {
if (diag.start !== undefined) {
const node = util.getTokenAtPosition(sourceFile, diag.start);
const parent = node.parent;
if (isIdentifier(node)) {
handleIdentifier(node);
}
else if (isImport(parent)) {
handleImportDeclaration(parent);
}
else if (isDestructure(parent)) {
handleDestructure(parent);
}
else if (isGeneric(node, parent)) {
handleTypeParamList(parent);
}
}
}
});
// trigger all the checks to be done after all the diagnostics have been evaluated
afterAllDiagnosticsCallbacks.forEach(cb => cb());
},
};
},
});
/**
* Checks if the diagnostic code is one of the expected "unused var" codes
*/
function isUnusedDiagnostic(code) {
return [
6133,
6138,
6192,
6196,
6198,
6199,
6205,
].includes(code);
}
/**
* Checks if the given node is a destructuring pattern
*/
function isDestructure(node) {
return (node.kind === ts.SyntaxKind.ObjectBindingPattern ||
node.kind === ts.SyntaxKind.ArrayBindingPattern);
}
function isImport(node) {
return node.kind === ts.SyntaxKind.ImportDeclaration;
}
function isIdentifier(node) {
return node.kind === ts.SyntaxKind.Identifier;
}
function isGeneric(node, parent) {
return (node.kind === ts.SyntaxKind.LessThanToken &&
parent.typeParameters !== undefined);
}
//# sourceMappingURL=no-unused-vars-experimental.js.map