noUnusedVariableRule.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright 2014 Palantir Technologies, Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. var tslib_1 = require("tslib");
  20. var utils = require("tsutils");
  21. var ts = require("typescript");
  22. var Lint = require("../index");
  23. var OPTION_CHECK_PARAMETERS = "check-parameters";
  24. var OPTION_IGNORE_PATTERN = "ignore-pattern";
  25. var Rule = /** @class */ (function (_super) {
  26. tslib_1.__extends(Rule, _super);
  27. function Rule() {
  28. return _super !== null && _super.apply(this, arguments) || this;
  29. }
  30. /* tslint:enable:object-literal-sort-keys */
  31. Rule.prototype.applyWithProgram = function (sourceFile, program) {
  32. return this.applyWithFunction(sourceFile, walk, parseOptions(this.ruleArguments), program);
  33. };
  34. /* tslint:disable:object-literal-sort-keys */
  35. Rule.metadata = {
  36. ruleName: "no-unused-variable",
  37. description: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["Disallows unused imports, variables, functions and\n private class members. Similar to tsc's --noUnusedParameters and --noUnusedLocals\n options, but does not interrupt code compilation."], ["Disallows unused imports, variables, functions and\n private class members. Similar to tsc's --noUnusedParameters and --noUnusedLocals\n options, but does not interrupt code compilation."]))),
  38. descriptionDetails: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n In addition to avoiding compilation errors, this rule may still be useful if you\n wish to have `tslint` automatically remove unused imports, variables, functions,\n and private class members, when using TSLint's `--fix` option."], ["\n In addition to avoiding compilation errors, this rule may still be useful if you\n wish to have \\`tslint\\` automatically remove unused imports, variables, functions,\n and private class members, when using TSLint's \\`--fix\\` option."]))),
  39. hasFix: true,
  40. optionsDescription: Lint.Utils.dedent(templateObject_3 || (templateObject_3 = tslib_1.__makeTemplateObject(["\n Three optional arguments may be optionally provided:\n\n * `\"check-parameters\"` disallows unused function and constructor parameters.\n * NOTE: this option is experimental and does not work with classes\n that use abstract method declarations, among other things.\n * `{\"ignore-pattern\": \"pattern\"}` where pattern is a case-sensitive regexp.\n Variable names and imports that match the pattern will be ignored."], ["\n Three optional arguments may be optionally provided:\n\n * \\`\"check-parameters\"\\` disallows unused function and constructor parameters.\n * NOTE: this option is experimental and does not work with classes\n that use abstract method declarations, among other things.\n * \\`{\"ignore-pattern\": \"pattern\"}\\` where pattern is a case-sensitive regexp.\n Variable names and imports that match the pattern will be ignored."]))),
  41. options: {
  42. type: "array",
  43. items: {
  44. oneOf: [
  45. {
  46. type: "string",
  47. enum: ["check-parameters"],
  48. },
  49. {
  50. type: "object",
  51. properties: {
  52. "ignore-pattern": { type: "string" },
  53. },
  54. additionalProperties: false,
  55. },
  56. ],
  57. },
  58. minLength: 0,
  59. maxLength: 3,
  60. },
  61. optionExamples: [true, [true, { "ignore-pattern": "^_" }]],
  62. type: "functionality",
  63. typescriptOnly: true,
  64. requiresTypeInfo: true,
  65. };
  66. return Rule;
  67. }(Lint.Rules.TypedRule));
  68. exports.Rule = Rule;
  69. function parseOptions(options) {
  70. var checkParameters = options.indexOf(OPTION_CHECK_PARAMETERS) !== -1;
  71. var ignorePattern;
  72. for (var _i = 0, options_1 = options; _i < options_1.length; _i++) {
  73. var o = options_1[_i];
  74. if (typeof o === "object") {
  75. // tslint:disable-next-line no-unsafe-any
  76. var ignore = o[OPTION_IGNORE_PATTERN];
  77. if (ignore != undefined) {
  78. ignorePattern = new RegExp(ignore);
  79. break;
  80. }
  81. }
  82. }
  83. return { checkParameters: checkParameters, ignorePattern: ignorePattern };
  84. }
  85. function walk(ctx, program) {
  86. var sourceFile = ctx.sourceFile, _a = ctx.options, checkParameters = _a.checkParameters, ignorePattern = _a.ignorePattern;
  87. var unusedCheckedProgram = getUnusedCheckedProgram(program, checkParameters);
  88. var diagnostics = ts.getPreEmitDiagnostics(unusedCheckedProgram, sourceFile);
  89. var checker = unusedCheckedProgram.getTypeChecker(); // Doesn't matter which program is used for this.
  90. var declaration = program.getCompilerOptions().declaration;
  91. // If all specifiers in an import are unused, we elide the entire import.
  92. var importSpecifierFailures = new Map();
  93. for (var _i = 0, diagnostics_1 = diagnostics; _i < diagnostics_1.length; _i++) {
  94. var diag = diagnostics_1[_i];
  95. if (diag.start === undefined) {
  96. continue;
  97. }
  98. var kind = getUnusedDiagnostic(diag);
  99. if (kind === undefined) {
  100. continue;
  101. }
  102. var failure = ts.flattenDiagnosticMessageText(diag.messageText, "\n");
  103. if (ignorePattern !== undefined) {
  104. var varName = /'(.*)'/.exec(failure)[1];
  105. if (ignorePattern.test(varName)) {
  106. continue;
  107. }
  108. }
  109. if (kind === 0 /* VARIABLE_OR_PARAMETER */) {
  110. var importName = findImport(diag.start, sourceFile);
  111. if (importName !== undefined) {
  112. if (declaration && isImportUsed(importName, sourceFile, checker)) {
  113. continue;
  114. }
  115. if (importSpecifierFailures.has(importName)) {
  116. throw new Error("Should not get 2 errors for the same import.");
  117. }
  118. importSpecifierFailures.set(importName, failure);
  119. continue;
  120. }
  121. }
  122. ctx.addFailureAt(diag.start, diag.length, failure);
  123. }
  124. if (importSpecifierFailures.size !== 0) {
  125. addImportSpecifierFailures(ctx, importSpecifierFailures, sourceFile);
  126. }
  127. }
  128. /**
  129. * Handle import-specifier failures separately.
  130. * - If all of the import specifiers in an import are unused, add a combined failure for them all.
  131. * - Unused imports are fixable.
  132. */
  133. function addImportSpecifierFailures(ctx, failures, sourceFile) {
  134. forEachImport(sourceFile, function (importNode) {
  135. if (importNode.kind === ts.SyntaxKind.ImportEqualsDeclaration) {
  136. tryRemoveAll(importNode.name);
  137. return;
  138. }
  139. if (importNode.importClause === undefined) {
  140. // Error node
  141. return;
  142. }
  143. var _a = importNode.importClause, defaultName = _a.name, namedBindings = _a.namedBindings;
  144. if (namedBindings !== undefined && namedBindings.kind === ts.SyntaxKind.NamespaceImport) {
  145. tryRemoveAll(namedBindings.name);
  146. return;
  147. }
  148. var allNamedBindingsAreFailures = namedBindings === undefined || namedBindings.elements.every(function (e) { return failures.has(e.name); });
  149. if (namedBindings !== undefined && allNamedBindingsAreFailures) {
  150. for (var _i = 0, _b = namedBindings.elements; _i < _b.length; _i++) {
  151. var e = _b[_i];
  152. failures.delete(e.name);
  153. }
  154. }
  155. if ((defaultName === undefined || failures.has(defaultName)) && allNamedBindingsAreFailures) {
  156. if (defaultName !== undefined) {
  157. failures.delete(defaultName);
  158. }
  159. removeAll(importNode, "All imports are unused.");
  160. return;
  161. }
  162. if (defaultName !== undefined) {
  163. var failure = tryDelete(defaultName);
  164. if (failure !== undefined) {
  165. var start = defaultName.getStart();
  166. var end = namedBindings !== undefined ? namedBindings.getStart() : importNode.moduleSpecifier.getStart();
  167. var fix = Lint.Replacement.deleteFromTo(start, end);
  168. ctx.addFailureAtNode(defaultName, failure, fix);
  169. }
  170. }
  171. if (namedBindings !== undefined) {
  172. if (allNamedBindingsAreFailures) {
  173. var start = defaultName !== undefined ? defaultName.getEnd() : namedBindings.getStart();
  174. var fix = Lint.Replacement.deleteFromTo(start, namedBindings.getEnd());
  175. var failure = "All named bindings are unused.";
  176. ctx.addFailureAtNode(namedBindings, failure, fix);
  177. }
  178. else {
  179. var elements = namedBindings.elements;
  180. for (var i = 0; i < elements.length; i++) {
  181. var element = elements[i];
  182. var failure = tryDelete(element.name);
  183. if (failure === undefined) {
  184. continue;
  185. }
  186. var prevElement = elements[i - 1];
  187. var nextElement = elements[i + 1];
  188. var start = prevElement !== undefined ? prevElement.getEnd() : element.getStart();
  189. var end = nextElement !== undefined && prevElement == undefined ? nextElement.getStart() : element.getEnd();
  190. var fix = Lint.Replacement.deleteFromTo(start, end);
  191. ctx.addFailureAtNode(element.name, failure, fix);
  192. }
  193. }
  194. }
  195. function tryRemoveAll(name) {
  196. var failure = tryDelete(name);
  197. if (failure !== undefined) {
  198. removeAll(name, failure);
  199. }
  200. }
  201. function removeAll(errorNode, failure) {
  202. var start = importNode.getStart();
  203. var end = importNode.getEnd();
  204. utils.forEachToken(importNode, function (token) {
  205. ts.forEachTrailingCommentRange(ctx.sourceFile.text, token.end, function (_, commentEnd, __) {
  206. end = commentEnd;
  207. });
  208. }, ctx.sourceFile);
  209. if (isEntireLine(start, end)) {
  210. end = getNextLineStart(end);
  211. }
  212. var fix = Lint.Replacement.deleteFromTo(start, end);
  213. ctx.addFailureAtNode(errorNode, failure, fix);
  214. }
  215. function isEntireLine(start, end) {
  216. return ctx.sourceFile.getLineAndCharacterOfPosition(start).character === 0 &&
  217. ctx.sourceFile.getLineEndOfPosition(end) === end;
  218. }
  219. function getNextLineStart(position) {
  220. var nextLine = ctx.sourceFile.getLineAndCharacterOfPosition(position).line + 1;
  221. var lineStarts = ctx.sourceFile.getLineStarts();
  222. if (nextLine < lineStarts.length) {
  223. return lineStarts[nextLine];
  224. }
  225. else {
  226. return position;
  227. }
  228. }
  229. });
  230. if (failures.size !== 0) {
  231. throw new Error("Should have revisited all import specifier failures.");
  232. }
  233. function tryDelete(name) {
  234. var failure = failures.get(name);
  235. if (failure !== undefined) {
  236. failures.delete(name);
  237. return failure;
  238. }
  239. return undefined;
  240. }
  241. }
  242. /**
  243. * Ignore this import if it's used as an implicit type somewhere.
  244. * Workround for https://github.com/Microsoft/TypeScript/issues/9944
  245. */
  246. function isImportUsed(importSpecifier, sourceFile, checker) {
  247. var importedSymbol = checker.getSymbolAtLocation(importSpecifier);
  248. if (importedSymbol === undefined) {
  249. return false;
  250. }
  251. var symbol = checker.getAliasedSymbol(importedSymbol);
  252. if (!utils.isSymbolFlagSet(symbol, ts.SymbolFlags.Type)) {
  253. return false;
  254. }
  255. return ts.forEachChild(sourceFile, function cb(child) {
  256. if (isImportLike(child)) {
  257. return false;
  258. }
  259. var type = getImplicitType(child, checker);
  260. // TODO: checker.typeEquals https://github.com/Microsoft/TypeScript/issues/13502
  261. if (type !== undefined && checker.typeToString(type) === checker.symbolToString(symbol)) {
  262. return true;
  263. }
  264. return ts.forEachChild(child, cb);
  265. }) === true;
  266. }
  267. function getImplicitType(node, checker) {
  268. if ((utils.isPropertyDeclaration(node) || utils.isVariableDeclaration(node)) &&
  269. node.type === undefined && node.name.kind === ts.SyntaxKind.Identifier ||
  270. utils.isBindingElement(node) && node.name.kind === ts.SyntaxKind.Identifier) {
  271. return checker.getTypeAtLocation(node);
  272. }
  273. else if (utils.isSignatureDeclaration(node) && node.type === undefined) {
  274. var sig = checker.getSignatureFromDeclaration(node);
  275. return sig === undefined ? undefined : sig.getReturnType();
  276. }
  277. else {
  278. return undefined;
  279. }
  280. }
  281. function isImportLike(node) {
  282. return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration;
  283. }
  284. function forEachImport(sourceFile, f) {
  285. return ts.forEachChild(sourceFile, function (child) {
  286. if (isImportLike(child)) {
  287. var res = f(child);
  288. if (res !== undefined) {
  289. return res;
  290. }
  291. }
  292. return undefined;
  293. });
  294. }
  295. function findImport(pos, sourceFile) {
  296. return forEachImport(sourceFile, function (i) {
  297. if (i.kind === ts.SyntaxKind.ImportEqualsDeclaration) {
  298. if (i.name.getStart() === pos) {
  299. return i.name;
  300. }
  301. }
  302. else {
  303. if (i.importClause === undefined) {
  304. // Error node
  305. return undefined;
  306. }
  307. var _a = i.importClause, defaultName = _a.name, namedBindings = _a.namedBindings;
  308. if (namedBindings !== undefined && namedBindings.kind === ts.SyntaxKind.NamespaceImport) {
  309. var name = namedBindings.name;
  310. if (name.getStart() === pos) {
  311. return name;
  312. }
  313. return undefined;
  314. }
  315. if (defaultName !== undefined && defaultName.getStart() === pos) {
  316. return defaultName;
  317. }
  318. else if (namedBindings !== undefined) {
  319. for (var _i = 0, _b = namedBindings.elements; _i < _b.length; _i++) {
  320. var name = _b[_i].name;
  321. if (name.getStart() === pos) {
  322. return name;
  323. }
  324. }
  325. }
  326. }
  327. return undefined;
  328. });
  329. }
  330. function getUnusedDiagnostic(diag) {
  331. switch (diag.code) {
  332. case 6133:
  333. return 0 /* VARIABLE_OR_PARAMETER */; // "'{0}' is declared but never used.
  334. case 6138:
  335. return 1 /* PROPERTY */; // "Property '{0}' is declared but never used."
  336. default:
  337. return undefined;
  338. }
  339. }
  340. var programToUnusedCheckedProgram = new WeakMap();
  341. function getUnusedCheckedProgram(program, checkParameters) {
  342. // Assuming checkParameters will always have the same value, so only lookup by program.
  343. var checkedProgram = programToUnusedCheckedProgram.get(program);
  344. if (checkedProgram !== undefined) {
  345. return checkedProgram;
  346. }
  347. checkedProgram = makeUnusedCheckedProgram(program, checkParameters);
  348. programToUnusedCheckedProgram.set(program, checkedProgram);
  349. return checkedProgram;
  350. }
  351. function makeUnusedCheckedProgram(program, checkParameters) {
  352. var originalOptions = program.getCompilerOptions();
  353. var options = tslib_1.__assign({}, originalOptions, { noEmit: true, noUnusedLocals: true, noUnusedParameters: originalOptions.noUnusedParameters || checkParameters });
  354. var sourceFilesByName = new Map(program.getSourceFiles().map(function (s) { return [getCanonicalFileName(s.fileName), s]; }));
  355. // tslint:disable object-literal-sort-keys
  356. return ts.createProgram(Array.from(sourceFilesByName.keys()), options, {
  357. fileExists: function (f) { return sourceFilesByName.has(getCanonicalFileName(f)); },
  358. readFile: function (f) { return sourceFilesByName.get(getCanonicalFileName(f)).text; },
  359. getSourceFile: function (f) { return sourceFilesByName.get(getCanonicalFileName(f)); },
  360. getDefaultLibFileName: function () { return ts.getDefaultLibFileName(options); },
  361. writeFile: function () { return undefined; },
  362. getCurrentDirectory: function () { return ""; },
  363. getDirectories: function () { return []; },
  364. getCanonicalFileName: getCanonicalFileName,
  365. useCaseSensitiveFileNames: function () { return ts.sys.useCaseSensitiveFileNames; },
  366. getNewLine: function () { return "\n"; },
  367. });
  368. // tslint:enable object-literal-sort-keys
  369. // We need to be careful with file system case sensitivity
  370. function getCanonicalFileName(fileName) {
  371. return ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
  372. }
  373. }
  374. var templateObject_1, templateObject_2, templateObject_3;