alignRule.js 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 tsutils_1 = require("tsutils");
  21. var ts = require("typescript");
  22. var Lint = require("../index");
  23. var OPTION_STATEMENTS = "statements";
  24. var OPTION_MEMBERS = "members";
  25. var OPTION_ELEMENTS = "elements";
  26. var OPTION_PARAMETERS = "parameters";
  27. var OPTION_ARGUMENTS = "arguments";
  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. return this.applyWithWalker(new AlignWalker(sourceFile, this.ruleName, {
  35. arguments: this.ruleArguments.indexOf(OPTION_ARGUMENTS) !== -1,
  36. elements: this.ruleArguments.indexOf(OPTION_ELEMENTS) !== -1,
  37. members: this.ruleArguments.indexOf(OPTION_MEMBERS) !== -1,
  38. parameters: this.ruleArguments.indexOf(OPTION_PARAMETERS) !== -1,
  39. statements: this.ruleArguments.indexOf(OPTION_STATEMENTS) !== -1,
  40. }));
  41. };
  42. /* tslint:disable:object-literal-sort-keys */
  43. Rule.metadata = {
  44. ruleName: "align",
  45. description: "Enforces vertical alignment.",
  46. hasFix: true,
  47. rationale: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n Helps maintain a readable, consistent style in your codebase.\n\n Consistent alignment for code statements helps keep code readable and clear.\n Statements misaligned from the standard can be harder to read and understand."], ["\n Helps maintain a readable, consistent style in your codebase.\n\n Consistent alignment for code statements helps keep code readable and clear.\n Statements misaligned from the standard can be harder to read and understand."]))),
  48. optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n Five arguments may be optionally provided:\n\n * `\"", "\"` checks alignment of function parameters.\n * `\"", "\"` checks alignment of function call arguments.\n * `\"", "\"` checks alignment of statements.\n * `\"", "\"` checks alignment of members of classes, interfaces, type literal, object literals and\n object destructuring.\n * `\"", "\"` checks alignment of elements of array iterals, array destructuring and tuple types."], ["\n Five arguments may be optionally provided:\n\n * \\`\"", "\"\\` checks alignment of function parameters.\n * \\`\"", "\"\\` checks alignment of function call arguments.\n * \\`\"", "\"\\` checks alignment of statements.\n * \\`\"", "\"\\` checks alignment of members of classes, interfaces, type literal, object literals and\n object destructuring.\n * \\`\"", "\"\\` checks alignment of elements of array iterals, array destructuring and tuple types."])), OPTION_PARAMETERS, OPTION_ARGUMENTS, OPTION_STATEMENTS, OPTION_MEMBERS, OPTION_ELEMENTS),
  49. options: {
  50. type: "array",
  51. items: {
  52. type: "string",
  53. enum: [OPTION_ARGUMENTS, OPTION_ELEMENTS, OPTION_MEMBERS, OPTION_PARAMETERS, OPTION_STATEMENTS],
  54. },
  55. minLength: 1,
  56. maxLength: 5,
  57. },
  58. optionExamples: [[true, "parameters", "statements"]],
  59. type: "style",
  60. typescriptOnly: false,
  61. };
  62. /* tslint:enable:object-literal-sort-keys */
  63. Rule.FAILURE_STRING_SUFFIX = " are not aligned";
  64. return Rule;
  65. }(Lint.Rules.AbstractRule));
  66. exports.Rule = Rule;
  67. var AlignWalker = /** @class */ (function (_super) {
  68. tslib_1.__extends(AlignWalker, _super);
  69. function AlignWalker() {
  70. return _super !== null && _super.apply(this, arguments) || this;
  71. }
  72. AlignWalker.prototype.walk = function (sourceFile) {
  73. var _this = this;
  74. var cb = function (node) {
  75. if (_this.options.statements && tsutils_1.isBlockLike(node)) {
  76. _this.checkAlignment(node.statements.filter(function (s) { return s.kind !== ts.SyntaxKind.EmptyStatement; }), OPTION_STATEMENTS);
  77. }
  78. else {
  79. switch (node.kind) {
  80. case ts.SyntaxKind.NewExpression:
  81. if (node.arguments === undefined) {
  82. break;
  83. }
  84. // falls through
  85. case ts.SyntaxKind.CallExpression:
  86. if (_this.options.arguments) {
  87. _this.checkAlignment(node.arguments, OPTION_ARGUMENTS);
  88. }
  89. break;
  90. case ts.SyntaxKind.FunctionDeclaration:
  91. case ts.SyntaxKind.FunctionExpression:
  92. case ts.SyntaxKind.Constructor:
  93. case ts.SyntaxKind.MethodDeclaration:
  94. case ts.SyntaxKind.ArrowFunction:
  95. case ts.SyntaxKind.CallSignature:
  96. case ts.SyntaxKind.ConstructSignature:
  97. case ts.SyntaxKind.MethodSignature:
  98. case ts.SyntaxKind.FunctionType:
  99. case ts.SyntaxKind.ConstructorType:
  100. if (_this.options.parameters) {
  101. _this.checkAlignment(node.parameters, OPTION_PARAMETERS);
  102. }
  103. break;
  104. case ts.SyntaxKind.ArrayLiteralExpression:
  105. case ts.SyntaxKind.ArrayBindingPattern:
  106. if (_this.options.elements) {
  107. _this.checkAlignment(node.elements, OPTION_ELEMENTS);
  108. }
  109. break;
  110. case ts.SyntaxKind.TupleType:
  111. if (_this.options.elements) {
  112. _this.checkAlignment(node.elementTypes, OPTION_ELEMENTS);
  113. }
  114. break;
  115. case ts.SyntaxKind.ObjectLiteralExpression:
  116. if (_this.options.members) {
  117. _this.checkAlignment(node.properties, OPTION_MEMBERS);
  118. }
  119. break;
  120. case ts.SyntaxKind.ObjectBindingPattern:
  121. if (_this.options.members) {
  122. _this.checkAlignment(node.elements, OPTION_MEMBERS);
  123. }
  124. break;
  125. case ts.SyntaxKind.ClassDeclaration:
  126. case ts.SyntaxKind.ClassExpression:
  127. if (_this.options.members) {
  128. _this.checkAlignment(node.members.filter(function (m) { return m.kind !== ts.SyntaxKind.SemicolonClassElement; }), OPTION_MEMBERS);
  129. }
  130. break;
  131. case ts.SyntaxKind.InterfaceDeclaration:
  132. case ts.SyntaxKind.TypeLiteral:
  133. if (_this.options.members) {
  134. _this.checkAlignment(node.members, OPTION_MEMBERS);
  135. }
  136. }
  137. }
  138. return ts.forEachChild(node, cb);
  139. };
  140. return cb(sourceFile);
  141. };
  142. AlignWalker.prototype.checkAlignment = function (nodes, kind) {
  143. if (nodes.length <= 1) {
  144. return;
  145. }
  146. var sourceFile = this.sourceFile;
  147. var pos = getLineAndCharacterWithoutBom(sourceFile, this.getStart(nodes[0]));
  148. var alignToColumn = pos.character;
  149. var line = pos.line;
  150. // skip first node in list
  151. for (var i = 1; i < nodes.length; ++i) {
  152. var node = nodes[i];
  153. var start = this.getStart(node);
  154. pos = ts.getLineAndCharacterOfPosition(sourceFile, start);
  155. if (line !== pos.line && pos.character !== alignToColumn) {
  156. var diff = alignToColumn - pos.character;
  157. var fix = void 0;
  158. if (diff >= 0) {
  159. fix = Lint.Replacement.appendText(start, " ".repeat(diff));
  160. }
  161. else if (node.pos <= start + diff && /^\s+$/.test(sourceFile.text.substring(start + diff, start))) {
  162. // only delete text if there is only whitespace
  163. fix = Lint.Replacement.deleteText(start + diff, -diff);
  164. }
  165. this.addFailure(start, Math.max(node.end, start), kind + Rule.FAILURE_STRING_SUFFIX, fix);
  166. }
  167. line = pos.line;
  168. }
  169. };
  170. AlignWalker.prototype.getStart = function (node) {
  171. return node.kind !== ts.SyntaxKind.OmittedExpression
  172. ? node.getStart(this.sourceFile)
  173. // find the comma token following the OmmitedExpression
  174. : tsutils_1.getNextToken(node, this.sourceFile).getStart(this.sourceFile);
  175. };
  176. return AlignWalker;
  177. }(Lint.AbstractWalker));
  178. function getLineAndCharacterWithoutBom(sourceFile, pos) {
  179. var result = ts.getLineAndCharacterOfPosition(sourceFile, pos);
  180. if (result.line === 0 && sourceFile.text[0] === "\uFEFF") {
  181. result.character -= 1;
  182. }
  183. return result;
  184. }
  185. var templateObject_1, templateObject_2;