semicolonRule.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright 2013 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_ALWAYS = "always";
  24. var OPTION_NEVER = "never";
  25. var OPTION_IGNORE_BOUND_CLASS_METHODS = "ignore-bound-class-methods";
  26. var OPTION_STRICT_BOUND_CLASS_METHODS = "strict-bound-class-methods";
  27. var OPTION_IGNORE_INTERFACES = "ignore-interfaces";
  28. var Rule = /** @class */ (function (_super) {
  29. tslib_1.__extends(Rule, _super);
  30. function Rule() {
  31. return _super !== null && _super.apply(this, arguments) || this;
  32. }
  33. Rule.prototype.apply = function (sourceFile) {
  34. var options = {
  35. boundClassMethods: this.ruleArguments.indexOf(OPTION_STRICT_BOUND_CLASS_METHODS) !== -1
  36. ? 2 /* Strict */
  37. : this.ruleArguments.indexOf(OPTION_IGNORE_BOUND_CLASS_METHODS) !== -1
  38. ? 1 /* Ignore */
  39. : 0 /* Default */,
  40. interfaces: this.ruleArguments.indexOf(OPTION_IGNORE_INTERFACES) === -1,
  41. };
  42. var Walker = this.ruleArguments.indexOf(OPTION_NEVER) === -1 ? SemicolonAlwaysWalker : SemicolonNeverWalker;
  43. return this.applyWithWalker(new Walker(sourceFile, this.ruleName, options));
  44. };
  45. /* tslint:disable:object-literal-sort-keys */
  46. Rule.metadata = {
  47. ruleName: "semicolon",
  48. description: "Enforces consistent semicolon usage at the end of every statement.",
  49. hasFix: true,
  50. optionsDescription: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n One of the following arguments must be provided:\n\n * `\"", "\"` enforces semicolons at the end of every statement.\n * `\"", "\"` disallows semicolons at the end of every statement except for when they are necessary.\n\n The following arguments may be optionally provided:\n\n * `\"", "\"` skips checking semicolons at the end of interface members.\n * `\"", "\"` skips checking semicolons at the end of bound class methods.\n * `\"", "\"` disables any special handling of bound class methods and treats them as any\n other assignment. This option overrides `\"", "\"`.\n "], ["\n One of the following arguments must be provided:\n\n * \\`\"", "\"\\` enforces semicolons at the end of every statement.\n * \\`\"", "\"\\` disallows semicolons at the end of every statement except for when they are necessary.\n\n The following arguments may be optionally provided:\n\n * \\`\"", "\"\\` skips checking semicolons at the end of interface members.\n * \\`\"", "\"\\` skips checking semicolons at the end of bound class methods.\n * \\`\"", "\"\\` disables any special handling of bound class methods and treats them as any\n other assignment. This option overrides \\`\"", "\"\\`.\n "])), OPTION_ALWAYS, OPTION_NEVER, OPTION_IGNORE_INTERFACES, OPTION_IGNORE_BOUND_CLASS_METHODS, OPTION_STRICT_BOUND_CLASS_METHODS, OPTION_IGNORE_BOUND_CLASS_METHODS),
  51. options: {
  52. type: "array",
  53. items: [
  54. {
  55. type: "string",
  56. enum: [OPTION_ALWAYS, OPTION_NEVER],
  57. },
  58. {
  59. type: "string",
  60. enum: [OPTION_IGNORE_INTERFACES],
  61. },
  62. ],
  63. additionalItems: false,
  64. },
  65. optionExamples: [
  66. [true, OPTION_ALWAYS],
  67. [true, OPTION_NEVER],
  68. [true, OPTION_ALWAYS, OPTION_IGNORE_INTERFACES],
  69. [true, OPTION_ALWAYS, OPTION_IGNORE_BOUND_CLASS_METHODS],
  70. ],
  71. type: "style",
  72. typescriptOnly: false,
  73. };
  74. /* tslint:enable:object-literal-sort-keys */
  75. Rule.FAILURE_STRING_MISSING = "Missing semicolon";
  76. Rule.FAILURE_STRING_COMMA = "Properties should be separated by semicolons";
  77. Rule.FAILURE_STRING_UNNECESSARY = "Unnecessary semicolon";
  78. return Rule;
  79. }(Lint.Rules.AbstractRule));
  80. exports.Rule = Rule;
  81. var SemicolonWalker = /** @class */ (function (_super) {
  82. tslib_1.__extends(SemicolonWalker, _super);
  83. function SemicolonWalker() {
  84. return _super !== null && _super.apply(this, arguments) || this;
  85. }
  86. SemicolonWalker.prototype.walk = function (sourceFile) {
  87. var _this = this;
  88. var cb = function (node) {
  89. _this.visitNode(node);
  90. return ts.forEachChild(node, cb);
  91. };
  92. return ts.forEachChild(sourceFile, cb);
  93. };
  94. SemicolonWalker.prototype.visitNode = function (node) {
  95. switch (node.kind) {
  96. case ts.SyntaxKind.SemicolonClassElement:
  97. return this.reportUnnecessary(node.end);
  98. case ts.SyntaxKind.EmptyStatement:
  99. return this.checkEmptyStatement(node);
  100. case ts.SyntaxKind.PropertyDeclaration:
  101. return this.visitPropertyDeclaration(node);
  102. }
  103. };
  104. SemicolonWalker.prototype.reportUnnecessary = function (pos, noFix) {
  105. this.addFailure(pos - 1, pos, Rule.FAILURE_STRING_UNNECESSARY, noFix ? undefined : Lint.Replacement.deleteText(pos - 1, 1));
  106. };
  107. SemicolonWalker.prototype.checkSemicolonOrLineBreak = function (node) {
  108. if (this.sourceFile.text[node.end - 1] !== ";") {
  109. return;
  110. }
  111. var nextToken = utils.getNextToken(node, this.sourceFile);
  112. switch (nextToken.kind) {
  113. case ts.SyntaxKind.EndOfFileToken:
  114. case ts.SyntaxKind.CloseBraceToken:
  115. return this.reportUnnecessary(node.end);
  116. default:
  117. if (!utils.isSameLine(this.sourceFile, node.end, nextToken.end)) {
  118. this.reportUnnecessary(node.end);
  119. }
  120. }
  121. };
  122. SemicolonWalker.prototype.checkUnnecessary = function (node) {
  123. if (this.sourceFile.text[node.end - 1] !== ";") {
  124. return;
  125. }
  126. var lastToken = utils.getPreviousToken(node.getLastToken(this.sourceFile), this.sourceFile);
  127. // yield does not continue on the next line if there is no yielded expression
  128. if (lastToken.kind === ts.SyntaxKind.YieldKeyword && lastToken.parent.kind === ts.SyntaxKind.YieldExpression ||
  129. // arrow functions with block as body don't continue on the next line
  130. lastToken.kind === ts.SyntaxKind.CloseBraceToken && lastToken.parent.kind === ts.SyntaxKind.Block &&
  131. lastToken.parent.parent.kind === ts.SyntaxKind.ArrowFunction) {
  132. return this.checkSemicolonOrLineBreak(node);
  133. }
  134. var nextToken = utils.getNextToken(node, this.sourceFile);
  135. switch (nextToken.kind) {
  136. case ts.SyntaxKind.OpenParenToken:
  137. case ts.SyntaxKind.OpenBracketToken:
  138. case ts.SyntaxKind.PlusToken:
  139. case ts.SyntaxKind.MinusToken:
  140. case ts.SyntaxKind.RegularExpressionLiteral:
  141. case ts.SyntaxKind.LessThanToken:
  142. case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
  143. case ts.SyntaxKind.TemplateHead:
  144. break;
  145. case ts.SyntaxKind.CloseBraceToken:
  146. case ts.SyntaxKind.EndOfFileToken:
  147. return this.reportUnnecessary(node.end);
  148. default:
  149. if (!utils.isSameLine(this.sourceFile, node.end, nextToken.end)) {
  150. this.reportUnnecessary(node.end);
  151. }
  152. }
  153. };
  154. SemicolonWalker.prototype.checkEmptyStatement = function (node) {
  155. // An empty statement is only ever useful when it is the only statement inside a loop
  156. if (!utils.isIterationStatement(node.parent)) {
  157. var parentKind = node.parent.kind;
  158. // don't remove empty statement if it is a direct child of if, with or a LabeledStatement
  159. // otherwise this would unintentionally change control flow
  160. var noFix = parentKind === ts.SyntaxKind.IfStatement ||
  161. parentKind === ts.SyntaxKind.LabeledStatement ||
  162. parentKind === ts.SyntaxKind.WithStatement;
  163. this.reportUnnecessary(node.end, noFix);
  164. }
  165. };
  166. SemicolonWalker.prototype.visitPropertyDeclaration = function (node) {
  167. // check if this is a multi-line arrow function
  168. if (this.options.boundClassMethods !== 2 /* Strict */ &&
  169. node.initializer !== undefined &&
  170. node.initializer.kind === ts.SyntaxKind.ArrowFunction &&
  171. !utils.isSameLine(this.sourceFile, node.getStart(this.sourceFile), node.end)) {
  172. if (this.options.boundClassMethods === 0 /* Default */) {
  173. this.checkUnnecessary(node);
  174. }
  175. }
  176. else {
  177. this.checkPropertyDeclaration(node);
  178. }
  179. };
  180. return SemicolonWalker;
  181. }(Lint.AbstractWalker));
  182. var SemicolonAlwaysWalker = /** @class */ (function (_super) {
  183. tslib_1.__extends(SemicolonAlwaysWalker, _super);
  184. function SemicolonAlwaysWalker() {
  185. return _super !== null && _super.apply(this, arguments) || this;
  186. }
  187. SemicolonAlwaysWalker.prototype.visitNode = function (node) {
  188. switch (node.kind) {
  189. case ts.SyntaxKind.VariableStatement:
  190. case ts.SyntaxKind.ExpressionStatement:
  191. case ts.SyntaxKind.ReturnStatement:
  192. case ts.SyntaxKind.BreakStatement:
  193. case ts.SyntaxKind.ContinueStatement:
  194. case ts.SyntaxKind.ThrowStatement:
  195. case ts.SyntaxKind.ImportEqualsDeclaration:
  196. case ts.SyntaxKind.DoStatement:
  197. case ts.SyntaxKind.ExportAssignment:
  198. case ts.SyntaxKind.TypeAliasDeclaration:
  199. case ts.SyntaxKind.ImportDeclaration:
  200. case ts.SyntaxKind.ExportDeclaration:
  201. case ts.SyntaxKind.DebuggerStatement:
  202. return this.checkMissing(node);
  203. case ts.SyntaxKind.ModuleDeclaration:
  204. case ts.SyntaxKind.MethodDeclaration:
  205. case ts.SyntaxKind.FunctionDeclaration:
  206. // check shorthand module declarations and method / function signatures
  207. if (node.body === undefined) {
  208. this.checkMissing(node);
  209. }
  210. break;
  211. case ts.SyntaxKind.InterfaceDeclaration:
  212. if (this.options.interfaces) {
  213. this.checkInterface(node);
  214. }
  215. break;
  216. default:
  217. return _super.prototype.visitNode.call(this, node);
  218. }
  219. };
  220. SemicolonAlwaysWalker.prototype.checkPropertyDeclaration = function (node) {
  221. return this.checkMissing(node);
  222. };
  223. SemicolonAlwaysWalker.prototype.checkMissing = function (node) {
  224. if (this.sourceFile.text[node.end - 1] !== ";") {
  225. this.reportMissing(node.end);
  226. }
  227. };
  228. SemicolonAlwaysWalker.prototype.reportMissing = function (pos) {
  229. this.addFailureAt(pos, 0, Rule.FAILURE_STRING_MISSING, Lint.Replacement.appendText(pos, ";"));
  230. };
  231. SemicolonAlwaysWalker.prototype.checkInterface = function (node) {
  232. for (var _i = 0, _a = node.members; _i < _a.length; _i++) {
  233. var member = _a[_i];
  234. switch (this.sourceFile.text[member.end - 1]) {
  235. case ";": break;
  236. case ",":
  237. this.addFailureAt(member.end - 1, 1, Rule.FAILURE_STRING_COMMA, new Lint.Replacement(member.end - 1, 1, ";"));
  238. break;
  239. default:
  240. this.reportMissing(member.end);
  241. }
  242. }
  243. };
  244. return SemicolonAlwaysWalker;
  245. }(SemicolonWalker));
  246. var SemicolonNeverWalker = /** @class */ (function (_super) {
  247. tslib_1.__extends(SemicolonNeverWalker, _super);
  248. function SemicolonNeverWalker() {
  249. return _super !== null && _super.apply(this, arguments) || this;
  250. }
  251. SemicolonNeverWalker.prototype.visitNode = function (node) {
  252. switch (node.kind) {
  253. case ts.SyntaxKind.ExpressionStatement:
  254. case ts.SyntaxKind.ThrowStatement:
  255. case ts.SyntaxKind.ExportAssignment:
  256. return this.checkUnnecessary(node);
  257. case ts.SyntaxKind.VariableStatement:
  258. return this.checkVariableStatement(node);
  259. case ts.SyntaxKind.ReturnStatement:
  260. if (node.expression === undefined) {
  261. // return does not continue on the next line if the is no returned expression
  262. return this.checkSemicolonOrLineBreak(node);
  263. }
  264. return this.checkUnnecessary(node);
  265. case ts.SyntaxKind.TypeAliasDeclaration:
  266. case ts.SyntaxKind.ImportEqualsDeclaration:
  267. case ts.SyntaxKind.ImportDeclaration:
  268. case ts.SyntaxKind.ExportDeclaration:
  269. case ts.SyntaxKind.DebuggerStatement:
  270. case ts.SyntaxKind.BreakStatement:
  271. case ts.SyntaxKind.ContinueStatement:
  272. case ts.SyntaxKind.DoStatement:
  273. return this.checkSemicolonOrLineBreak(node);
  274. case ts.SyntaxKind.ModuleDeclaration:
  275. // shorthand module declaration
  276. if (node.body === undefined) {
  277. this.checkShorthandModuleDeclaration(node);
  278. }
  279. break;
  280. case ts.SyntaxKind.MethodDeclaration:
  281. // check method signature
  282. if (node.body === undefined) {
  283. this.checkSemicolonOrLineBreak(node);
  284. }
  285. break;
  286. case ts.SyntaxKind.FunctionDeclaration:
  287. // check function signature
  288. if (node.body === undefined) {
  289. this.checkSemicolonOrLineBreak(node);
  290. }
  291. break;
  292. case ts.SyntaxKind.InterfaceDeclaration:
  293. if (this.options.interfaces) {
  294. this.checkInterface(node);
  295. }
  296. break;
  297. default:
  298. return _super.prototype.visitNode.call(this, node);
  299. }
  300. };
  301. SemicolonNeverWalker.prototype.checkPropertyDeclaration = function (node) {
  302. if (node.initializer === undefined) {
  303. return this.checkSemicolonOrLineBreak(node);
  304. }
  305. return this.checkUnnecessary(node);
  306. };
  307. SemicolonNeverWalker.prototype.checkVariableStatement = function (node) {
  308. var declarations = node.declarationList.declarations;
  309. if (declarations[declarations.length - 1].initializer === undefined) {
  310. // variable declaration does not continue on the next line if it has no initializer
  311. return this.checkSemicolonOrLineBreak(node);
  312. }
  313. return this.checkUnnecessary(node);
  314. };
  315. SemicolonNeverWalker.prototype.checkShorthandModuleDeclaration = function (node) {
  316. var nextStatement = utils.getNextStatement(node);
  317. if (nextStatement === undefined || nextStatement.kind !== ts.SyntaxKind.Block) {
  318. this.checkSemicolonOrLineBreak(node);
  319. }
  320. };
  321. SemicolonNeverWalker.prototype.checkInterface = function (node) {
  322. for (var _i = 0, _a = node.members; _i < _a.length; _i++) {
  323. var member = _a[_i];
  324. this.checkSemicolonOrLineBreak(member);
  325. }
  326. };
  327. return SemicolonNeverWalker;
  328. }(SemicolonWalker));
  329. var templateObject_1;