angularWhitespaceRule.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. "use strict";
  2. var __extends = (this && this.__extends) || (function () {
  3. var extendStatics = function (d, b) {
  4. extendStatics = Object.setPrototypeOf ||
  5. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  6. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  7. return extendStatics(d, b);
  8. };
  9. return function (d, b) {
  10. extendStatics(d, b);
  11. function __() { this.constructor = d; }
  12. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  13. };
  14. })();
  15. var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
  16. if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
  17. return cooked;
  18. };
  19. Object.defineProperty(exports, "__esModule", { value: true });
  20. var Lint = require("tslint");
  21. var config_1 = require("./angular/config");
  22. var expressionTypes_1 = require("./angular/expressionTypes");
  23. var ngWalker_1 = require("./angular/ngWalker");
  24. var basicTemplateAstVisitor_1 = require("./angular/templates/basicTemplateAstVisitor");
  25. var recursiveAngularExpressionVisitor_1 = require("./angular/templates/recursiveAngularExpressionVisitor");
  26. var isNotNullOrUndefined_1 = require("./util/isNotNullOrUndefined");
  27. var stickyFlagUsable = (function () {
  28. try {
  29. var reg = /d/y;
  30. return true;
  31. }
  32. catch (_a) {
  33. return false;
  34. }
  35. })();
  36. var _a = config_1.Config.interpolation, InterpolationOpen = _a[0], InterpolationClose = _a[1];
  37. var InterpolationWhitespaceRe = new RegExp(InterpolationOpen + "(\\s*)(.*?)(\\s*)" + InterpolationClose, 'g');
  38. var SemicolonNoWhitespaceNotInSimpleQuoteRe = stickyFlagUsable
  39. ? new RegExp("(?:[^';]|'[^']*'|;(?=\\s))+;(?=\\S)", 'gy')
  40. : /(?:[^';]|'[^']*')+;/g;
  41. var SemicolonNoWhitespaceNotInDoubleQuoteRe = stickyFlagUsable
  42. ? new RegExp("(?:[^\";]|\"[^\"]*\"|;(?=\\s))+;(?=\\S)", 'gy')
  43. : /(?:[^";]|"[^"]*")+;/g;
  44. var getSemicolonReplacements = function (absolutePosition) {
  45. return [new Lint.Replacement(absolutePosition, 1, '; ')];
  46. };
  47. var checkSemicolonNoWhitespaceWithSticky = function (reg, context, expr, fixedOffset) {
  48. var error = "Missing whitespace after semicolon; expecting '; expr'";
  49. var exprMatch;
  50. while ((exprMatch = reg.exec(expr))) {
  51. var start = fixedOffset + reg.lastIndex;
  52. var absolutePosition = context.getSourcePosition(start - 1);
  53. context.addFailureAt(start, 2, error, getSemicolonReplacements(absolutePosition));
  54. }
  55. };
  56. var checkSemicolonNoWhitespaceWithoutSticky = function (reg, context, expr, fixedOffset) {
  57. var error = "Missing whitespace after semicolon; expecting '; expr'";
  58. var lastIndex = 0;
  59. var exprMatch;
  60. while ((exprMatch = reg.exec(expr))) {
  61. if (lastIndex !== exprMatch.index) {
  62. break;
  63. }
  64. var nextIndex = reg.lastIndex;
  65. if (nextIndex < expr.length && /\S/.test(expr[nextIndex])) {
  66. var start = fixedOffset + nextIndex;
  67. var absolutePosition = context.getSourcePosition(start - 1);
  68. context.addFailureAt(start, 2, error, getSemicolonReplacements(absolutePosition));
  69. }
  70. lastIndex = nextIndex;
  71. }
  72. };
  73. var checkSemicolonNoWhitespace = stickyFlagUsable
  74. ? checkSemicolonNoWhitespaceWithSticky
  75. : checkSemicolonNoWhitespaceWithoutSticky;
  76. var OPTION_CHECK_INTERPOLATION = 'check-interpolation';
  77. var OPTION_CHECK_PIPE = 'check-pipe';
  78. var OPTION_CHECK_SEMICOLON = 'check-semicolon';
  79. var InterpolationWhitespaceVisitor = (function (_super) {
  80. __extends(InterpolationWhitespaceVisitor, _super);
  81. function InterpolationWhitespaceVisitor() {
  82. return _super !== null && _super.apply(this, arguments) || this;
  83. }
  84. InterpolationWhitespaceVisitor.prototype.visitBoundText = function (text, context) {
  85. if (expressionTypes_1.ExpTypes.ASTWithSource(text.value)) {
  86. var expr = text.value.source;
  87. var checkWhiteSpace = function (subMatch, location, fixTo, position, absolutePosition, lengthFix) {
  88. var length = subMatch.length;
  89. if (length === 1) {
  90. return;
  91. }
  92. var errorText = length === 0 ? 'Missing' : 'Extra';
  93. context.addFailureAt(position, length + lengthFix, errorText + " whitespace in interpolation " + location + "; expecting " + InterpolationOpen + " expr " + InterpolationClose, [new Lint.Replacement(absolutePosition, length + lengthFix, fixTo)]);
  94. };
  95. InterpolationWhitespaceRe.lastIndex = 0;
  96. var match = void 0;
  97. while ((match = InterpolationWhitespaceRe.exec(expr))) {
  98. var start = text.sourceSpan.start.offset + match.index;
  99. var absolutePosition = context.getSourcePosition(start);
  100. checkWhiteSpace(match[1], 'start', InterpolationOpen + " ", start, absolutePosition, InterpolationOpen.length);
  101. var positionFix = InterpolationOpen.length + match[1].length + match[2].length;
  102. checkWhiteSpace(match[3], 'end', " " + InterpolationClose, start + positionFix, absolutePosition + positionFix, InterpolationClose.length);
  103. }
  104. }
  105. _super.prototype.visitBoundText.call(this, text, context);
  106. return null;
  107. };
  108. InterpolationWhitespaceVisitor.prototype.getCheckOption = function () {
  109. return 'check-interpolation';
  110. };
  111. return InterpolationWhitespaceVisitor;
  112. }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
  113. var SemicolonTemplateVisitor = (function (_super) {
  114. __extends(SemicolonTemplateVisitor, _super);
  115. function SemicolonTemplateVisitor() {
  116. return _super !== null && _super.apply(this, arguments) || this;
  117. }
  118. SemicolonTemplateVisitor.prototype.visitDirectiveProperty = function (prop, context) {
  119. if (prop.sourceSpan) {
  120. var directive = prop.sourceSpan.toString();
  121. var match = /^([^=]+=\s*)([^]*?)\s*$/.exec(directive);
  122. var rawExpression = match[2];
  123. var positionFix = match[1].length + 1;
  124. var expr = rawExpression.slice(1, -1).trim();
  125. var doubleQuote = rawExpression[0] === '"';
  126. var reg = doubleQuote ? SemicolonNoWhitespaceNotInSimpleQuoteRe : SemicolonNoWhitespaceNotInDoubleQuoteRe;
  127. reg.lastIndex = 0;
  128. checkSemicolonNoWhitespace(reg, context, expr, prop.sourceSpan.start.offset + positionFix);
  129. }
  130. _super.prototype.visitDirectiveProperty.call(this, prop, context);
  131. };
  132. SemicolonTemplateVisitor.prototype.getCheckOption = function () {
  133. return 'check-semicolon';
  134. };
  135. return SemicolonTemplateVisitor;
  136. }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
  137. var TemplateVisitorCtrl = (function (_super) {
  138. __extends(TemplateVisitorCtrl, _super);
  139. function TemplateVisitorCtrl() {
  140. var _this = _super !== null && _super.apply(this, arguments) || this;
  141. _this.visitors = [
  142. new InterpolationWhitespaceVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.templateStart),
  143. new SemicolonTemplateVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.templateStart)
  144. ];
  145. return _this;
  146. }
  147. TemplateVisitorCtrl.prototype.visitBoundText = function (text, context) {
  148. var _this = this;
  149. var options = this.getOptions();
  150. this.visitors
  151. .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; })
  152. .map(function (v) { return v.visitBoundText(text, _this); })
  153. .filter(isNotNullOrUndefined_1.isNotNullOrUndefined)
  154. .forEach(function (f) {
  155. return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix());
  156. });
  157. _super.prototype.visitBoundText.call(this, text, context);
  158. };
  159. TemplateVisitorCtrl.prototype.visitDirectiveProperty = function (prop, context) {
  160. var _this = this;
  161. var options = this.getOptions();
  162. this.visitors
  163. .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; })
  164. .map(function (v) { return v.visitDirectiveProperty(prop, _this); })
  165. .filter(isNotNullOrUndefined_1.isNotNullOrUndefined)
  166. .forEach(function (f) {
  167. return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix());
  168. });
  169. _super.prototype.visitDirectiveProperty.call(this, prop, context);
  170. };
  171. return TemplateVisitorCtrl;
  172. }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
  173. var PipeWhitespaceVisitor = (function (_super) {
  174. __extends(PipeWhitespaceVisitor, _super);
  175. function PipeWhitespaceVisitor() {
  176. return _super !== null && _super.apply(this, arguments) || this;
  177. }
  178. PipeWhitespaceVisitor.prototype.visitPipe = function (ast, context) {
  179. var exprStart, exprEnd, exprText, sf;
  180. exprStart = context.getSourcePosition(ast.exp.span.start);
  181. exprEnd = context.getSourcePosition(ast.exp.span.end);
  182. sf = context.getSourceFile().getFullText();
  183. exprText = sf.substring(exprStart, exprEnd);
  184. var replacements = [];
  185. var parentheses = false;
  186. var leftBeginning;
  187. if (sf[exprEnd] === ')') {
  188. parentheses = true;
  189. leftBeginning = exprEnd + 1 + 2;
  190. }
  191. else {
  192. leftBeginning = exprEnd + 1;
  193. }
  194. if (sf[leftBeginning] === ' ') {
  195. var ignoreSpace = 1;
  196. while (sf[leftBeginning + ignoreSpace] === ' ') {
  197. ignoreSpace += 1;
  198. }
  199. if (ignoreSpace > 1) {
  200. replacements.push(new Lint.Replacement(exprEnd + 1, ignoreSpace, ' '));
  201. }
  202. }
  203. else {
  204. replacements.push(new Lint.Replacement(exprEnd + 1, 0, ' '));
  205. }
  206. if (exprText[exprText.length - 1] === ' ') {
  207. var ignoreSpace = 1;
  208. while (exprText[exprText.length - 1 - ignoreSpace] === ' ') {
  209. ignoreSpace += 1;
  210. }
  211. if (ignoreSpace > 1) {
  212. replacements.push(new Lint.Replacement(exprEnd - ignoreSpace, ignoreSpace, ' '));
  213. }
  214. }
  215. else {
  216. if (!parentheses) {
  217. replacements.push(new Lint.Replacement(exprEnd, 0, ' '));
  218. }
  219. }
  220. if (replacements.length) {
  221. context.addFailureAt(ast.exp.span.end - 1, 3, 'The pipe operator should be surrounded by one space on each side, i.e. " | ".', replacements);
  222. }
  223. _super.prototype.visitPipe.call(this, ast, context);
  224. return null;
  225. };
  226. PipeWhitespaceVisitor.prototype.getCheckOption = function () {
  227. return 'check-pipe';
  228. };
  229. return PipeWhitespaceVisitor;
  230. }(recursiveAngularExpressionVisitor_1.RecursiveAngularExpressionVisitor));
  231. var ExpressionVisitorCtrl = (function (_super) {
  232. __extends(ExpressionVisitorCtrl, _super);
  233. function ExpressionVisitorCtrl() {
  234. var _this = _super !== null && _super.apply(this, arguments) || this;
  235. _this.visitors = [
  236. new PipeWhitespaceVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.basePosition)
  237. ];
  238. return _this;
  239. }
  240. ExpressionVisitorCtrl.prototype.visitPipe = function (expr, context) {
  241. var _this = this;
  242. var options = this.getOptions();
  243. this.visitors
  244. .map(function (v) { return v.addParentAST(_this.parentAST); })
  245. .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; })
  246. .map(function (v) { return v.visitPipe(expr, _this); })
  247. .filter(isNotNullOrUndefined_1.isNotNullOrUndefined)
  248. .forEach(function (f) {
  249. return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix());
  250. });
  251. };
  252. return ExpressionVisitorCtrl;
  253. }(recursiveAngularExpressionVisitor_1.RecursiveAngularExpressionVisitor));
  254. var Rule = (function (_super) {
  255. __extends(Rule, _super);
  256. function Rule() {
  257. return _super !== null && _super.apply(this, arguments) || this;
  258. }
  259. Rule.prototype.apply = function (sourceFile) {
  260. var walkerConfig = {
  261. expressionVisitorCtrl: ExpressionVisitorCtrl,
  262. templateVisitorCtrl: TemplateVisitorCtrl
  263. };
  264. var walker = new ngWalker_1.NgWalker(sourceFile, this.getOptions(), walkerConfig);
  265. return this.applyWithWalker(walker);
  266. };
  267. Rule.prototype.isEnabled = function () {
  268. var _a = Rule.metadata.options, maxLength = _a.maxLength, minLength = _a.minLength;
  269. var length = this.ruleArguments.length;
  270. return _super.prototype.isEnabled.call(this) && length >= minLength && length <= maxLength;
  271. };
  272. Rule.metadata = {
  273. deprecationMessage: 'Use a formatter like Prettier for formatting purposes.',
  274. description: 'Ensures the proper formatting of Angular expressions.',
  275. hasFix: true,
  276. optionExamples: [
  277. [true, OPTION_CHECK_INTERPOLATION],
  278. [true, OPTION_CHECK_PIPE],
  279. [true, OPTION_CHECK_SEMICOLON],
  280. [true, OPTION_CHECK_INTERPOLATION, OPTION_CHECK_PIPE, OPTION_CHECK_SEMICOLON]
  281. ],
  282. options: {
  283. items: {
  284. enum: [OPTION_CHECK_INTERPOLATION, OPTION_CHECK_PIPE, OPTION_CHECK_SEMICOLON],
  285. type: 'string'
  286. },
  287. maxLength: 3,
  288. minLength: 1,
  289. type: 'array'
  290. },
  291. optionsDescription: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n One (or both) of the following arguments must be provided:\n * `", "` - checks for whitespace before and after the interpolation characters.\n * `", "` - checks for whitespace before and after a pipe.\n * `", "` - checks for whitespace after semicolon.\n "], ["\n One (or both) of the following arguments must be provided:\n * \\`", "\\` - checks for whitespace before and after the interpolation characters.\n * \\`", "\\` - checks for whitespace before and after a pipe.\n * \\`", "\\` - checks for whitespace after semicolon.\n "])), OPTION_CHECK_INTERPOLATION, OPTION_CHECK_PIPE, OPTION_CHECK_SEMICOLON),
  292. rationale: 'Having whitespace in the right places in an Angular expression makes the template more readable.',
  293. ruleName: 'angular-whitespace',
  294. type: 'style',
  295. typescriptOnly: true
  296. };
  297. return Rule;
  298. }(Lint.Rules.AbstractRule));
  299. exports.Rule = Rule;
  300. var templateObject_1;