"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; Object.defineProperty(exports, "__esModule", { value: true }); var Lint = require("tslint"); var config_1 = require("./angular/config"); var expressionTypes_1 = require("./angular/expressionTypes"); var ngWalker_1 = require("./angular/ngWalker"); var basicTemplateAstVisitor_1 = require("./angular/templates/basicTemplateAstVisitor"); var recursiveAngularExpressionVisitor_1 = require("./angular/templates/recursiveAngularExpressionVisitor"); var isNotNullOrUndefined_1 = require("./util/isNotNullOrUndefined"); var stickyFlagUsable = (function () { try { var reg = /d/y; return true; } catch (_a) { return false; } })(); var _a = config_1.Config.interpolation, InterpolationOpen = _a[0], InterpolationClose = _a[1]; var InterpolationWhitespaceRe = new RegExp(InterpolationOpen + "(\\s*)(.*?)(\\s*)" + InterpolationClose, 'g'); var SemicolonNoWhitespaceNotInSimpleQuoteRe = stickyFlagUsable ? new RegExp("(?:[^';]|'[^']*'|;(?=\\s))+;(?=\\S)", 'gy') : /(?:[^';]|'[^']*')+;/g; var SemicolonNoWhitespaceNotInDoubleQuoteRe = stickyFlagUsable ? new RegExp("(?:[^\";]|\"[^\"]*\"|;(?=\\s))+;(?=\\S)", 'gy') : /(?:[^";]|"[^"]*")+;/g; var getSemicolonReplacements = function (absolutePosition) { return [new Lint.Replacement(absolutePosition, 1, '; ')]; }; var checkSemicolonNoWhitespaceWithSticky = function (reg, context, expr, fixedOffset) { var error = "Missing whitespace after semicolon; expecting '; expr'"; var exprMatch; while ((exprMatch = reg.exec(expr))) { var start = fixedOffset + reg.lastIndex; var absolutePosition = context.getSourcePosition(start - 1); context.addFailureAt(start, 2, error, getSemicolonReplacements(absolutePosition)); } }; var checkSemicolonNoWhitespaceWithoutSticky = function (reg, context, expr, fixedOffset) { var error = "Missing whitespace after semicolon; expecting '; expr'"; var lastIndex = 0; var exprMatch; while ((exprMatch = reg.exec(expr))) { if (lastIndex !== exprMatch.index) { break; } var nextIndex = reg.lastIndex; if (nextIndex < expr.length && /\S/.test(expr[nextIndex])) { var start = fixedOffset + nextIndex; var absolutePosition = context.getSourcePosition(start - 1); context.addFailureAt(start, 2, error, getSemicolonReplacements(absolutePosition)); } lastIndex = nextIndex; } }; var checkSemicolonNoWhitespace = stickyFlagUsable ? checkSemicolonNoWhitespaceWithSticky : checkSemicolonNoWhitespaceWithoutSticky; var OPTION_CHECK_INTERPOLATION = 'check-interpolation'; var OPTION_CHECK_PIPE = 'check-pipe'; var OPTION_CHECK_SEMICOLON = 'check-semicolon'; var InterpolationWhitespaceVisitor = (function (_super) { __extends(InterpolationWhitespaceVisitor, _super); function InterpolationWhitespaceVisitor() { return _super !== null && _super.apply(this, arguments) || this; } InterpolationWhitespaceVisitor.prototype.visitBoundText = function (text, context) { if (expressionTypes_1.ExpTypes.ASTWithSource(text.value)) { var expr = text.value.source; var checkWhiteSpace = function (subMatch, location, fixTo, position, absolutePosition, lengthFix) { var length = subMatch.length; if (length === 1) { return; } var errorText = length === 0 ? 'Missing' : 'Extra'; context.addFailureAt(position, length + lengthFix, errorText + " whitespace in interpolation " + location + "; expecting " + InterpolationOpen + " expr " + InterpolationClose, [new Lint.Replacement(absolutePosition, length + lengthFix, fixTo)]); }; InterpolationWhitespaceRe.lastIndex = 0; var match = void 0; while ((match = InterpolationWhitespaceRe.exec(expr))) { var start = text.sourceSpan.start.offset + match.index; var absolutePosition = context.getSourcePosition(start); checkWhiteSpace(match[1], 'start', InterpolationOpen + " ", start, absolutePosition, InterpolationOpen.length); var positionFix = InterpolationOpen.length + match[1].length + match[2].length; checkWhiteSpace(match[3], 'end', " " + InterpolationClose, start + positionFix, absolutePosition + positionFix, InterpolationClose.length); } } _super.prototype.visitBoundText.call(this, text, context); return null; }; InterpolationWhitespaceVisitor.prototype.getCheckOption = function () { return 'check-interpolation'; }; return InterpolationWhitespaceVisitor; }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor)); var SemicolonTemplateVisitor = (function (_super) { __extends(SemicolonTemplateVisitor, _super); function SemicolonTemplateVisitor() { return _super !== null && _super.apply(this, arguments) || this; } SemicolonTemplateVisitor.prototype.visitDirectiveProperty = function (prop, context) { if (prop.sourceSpan) { var directive = prop.sourceSpan.toString(); var match = /^([^=]+=\s*)([^]*?)\s*$/.exec(directive); var rawExpression = match[2]; var positionFix = match[1].length + 1; var expr = rawExpression.slice(1, -1).trim(); var doubleQuote = rawExpression[0] === '"'; var reg = doubleQuote ? SemicolonNoWhitespaceNotInSimpleQuoteRe : SemicolonNoWhitespaceNotInDoubleQuoteRe; reg.lastIndex = 0; checkSemicolonNoWhitespace(reg, context, expr, prop.sourceSpan.start.offset + positionFix); } _super.prototype.visitDirectiveProperty.call(this, prop, context); }; SemicolonTemplateVisitor.prototype.getCheckOption = function () { return 'check-semicolon'; }; return SemicolonTemplateVisitor; }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor)); var TemplateVisitorCtrl = (function (_super) { __extends(TemplateVisitorCtrl, _super); function TemplateVisitorCtrl() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.visitors = [ new InterpolationWhitespaceVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.templateStart), new SemicolonTemplateVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.templateStart) ]; return _this; } TemplateVisitorCtrl.prototype.visitBoundText = function (text, context) { var _this = this; var options = this.getOptions(); this.visitors .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; }) .map(function (v) { return v.visitBoundText(text, _this); }) .filter(isNotNullOrUndefined_1.isNotNullOrUndefined) .forEach(function (f) { return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()); }); _super.prototype.visitBoundText.call(this, text, context); }; TemplateVisitorCtrl.prototype.visitDirectiveProperty = function (prop, context) { var _this = this; var options = this.getOptions(); this.visitors .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; }) .map(function (v) { return v.visitDirectiveProperty(prop, _this); }) .filter(isNotNullOrUndefined_1.isNotNullOrUndefined) .forEach(function (f) { return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()); }); _super.prototype.visitDirectiveProperty.call(this, prop, context); }; return TemplateVisitorCtrl; }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor)); var PipeWhitespaceVisitor = (function (_super) { __extends(PipeWhitespaceVisitor, _super); function PipeWhitespaceVisitor() { return _super !== null && _super.apply(this, arguments) || this; } PipeWhitespaceVisitor.prototype.visitPipe = function (ast, context) { var exprStart, exprEnd, exprText, sf; exprStart = context.getSourcePosition(ast.exp.span.start); exprEnd = context.getSourcePosition(ast.exp.span.end); sf = context.getSourceFile().getFullText(); exprText = sf.substring(exprStart, exprEnd); var replacements = []; var parentheses = false; var leftBeginning; if (sf[exprEnd] === ')') { parentheses = true; leftBeginning = exprEnd + 1 + 2; } else { leftBeginning = exprEnd + 1; } if (sf[leftBeginning] === ' ') { var ignoreSpace = 1; while (sf[leftBeginning + ignoreSpace] === ' ') { ignoreSpace += 1; } if (ignoreSpace > 1) { replacements.push(new Lint.Replacement(exprEnd + 1, ignoreSpace, ' ')); } } else { replacements.push(new Lint.Replacement(exprEnd + 1, 0, ' ')); } if (exprText[exprText.length - 1] === ' ') { var ignoreSpace = 1; while (exprText[exprText.length - 1 - ignoreSpace] === ' ') { ignoreSpace += 1; } if (ignoreSpace > 1) { replacements.push(new Lint.Replacement(exprEnd - ignoreSpace, ignoreSpace, ' ')); } } else { if (!parentheses) { replacements.push(new Lint.Replacement(exprEnd, 0, ' ')); } } if (replacements.length) { context.addFailureAt(ast.exp.span.end - 1, 3, 'The pipe operator should be surrounded by one space on each side, i.e. " | ".', replacements); } _super.prototype.visitPipe.call(this, ast, context); return null; }; PipeWhitespaceVisitor.prototype.getCheckOption = function () { return 'check-pipe'; }; return PipeWhitespaceVisitor; }(recursiveAngularExpressionVisitor_1.RecursiveAngularExpressionVisitor)); var ExpressionVisitorCtrl = (function (_super) { __extends(ExpressionVisitorCtrl, _super); function ExpressionVisitorCtrl() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.visitors = [ new PipeWhitespaceVisitor(_this.getSourceFile(), _this.getOptions(), _this.context, _this.basePosition) ]; return _this; } ExpressionVisitorCtrl.prototype.visitPipe = function (expr, context) { var _this = this; var options = this.getOptions(); this.visitors .map(function (v) { return v.addParentAST(_this.parentAST); }) .filter(function (v) { return options.indexOf(v.getCheckOption()) >= 0; }) .map(function (v) { return v.visitPipe(expr, _this); }) .filter(isNotNullOrUndefined_1.isNotNullOrUndefined) .forEach(function (f) { return _this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()); }); }; return ExpressionVisitorCtrl; }(recursiveAngularExpressionVisitor_1.RecursiveAngularExpressionVisitor)); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { return _super !== null && _super.apply(this, arguments) || this; } Rule.prototype.apply = function (sourceFile) { var walkerConfig = { expressionVisitorCtrl: ExpressionVisitorCtrl, templateVisitorCtrl: TemplateVisitorCtrl }; var walker = new ngWalker_1.NgWalker(sourceFile, this.getOptions(), walkerConfig); return this.applyWithWalker(walker); }; Rule.prototype.isEnabled = function () { var _a = Rule.metadata.options, maxLength = _a.maxLength, minLength = _a.minLength; var length = this.ruleArguments.length; return _super.prototype.isEnabled.call(this) && length >= minLength && length <= maxLength; }; Rule.metadata = { deprecationMessage: 'Use a formatter like Prettier for formatting purposes.', description: 'Ensures the proper formatting of Angular expressions.', hasFix: true, optionExamples: [ [true, OPTION_CHECK_INTERPOLATION], [true, OPTION_CHECK_PIPE], [true, OPTION_CHECK_SEMICOLON], [true, OPTION_CHECK_INTERPOLATION, OPTION_CHECK_PIPE, OPTION_CHECK_SEMICOLON] ], options: { items: { enum: [OPTION_CHECK_INTERPOLATION, OPTION_CHECK_PIPE, OPTION_CHECK_SEMICOLON], type: 'string' }, maxLength: 3, minLength: 1, type: 'array' }, 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), rationale: 'Having whitespace in the right places in an Angular expression makes the template more readable.', ruleName: 'angular-whitespace', type: 'style', typescriptOnly: true }; return Rule; }(Lint.Rules.AbstractRule)); exports.Rule = Rule; var templateObject_1;