180 lines
9.4 KiB
JavaScript
180 lines
9.4 KiB
JavaScript
|
"use strict";
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2013 Palantir Technologies, Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
var tslib_1 = require("tslib");
|
||
|
var ts = require("typescript");
|
||
|
var Lint = require("../index");
|
||
|
var OPTION_USE_TABS = "tabs";
|
||
|
var OPTION_USE_SPACES = "spaces";
|
||
|
var OPTION_INDENT_SIZE_2 = 2;
|
||
|
var OPTION_INDENT_SIZE_4 = 4;
|
||
|
var Rule = (function (_super) {
|
||
|
tslib_1.__extends(Rule, _super);
|
||
|
function Rule() {
|
||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||
|
}
|
||
|
Rule.prototype.apply = function (sourceFile) {
|
||
|
return this.applyWithWalker(new IndentWalker(sourceFile, this.getOptions()));
|
||
|
};
|
||
|
return Rule;
|
||
|
}(Lint.Rules.AbstractRule));
|
||
|
/* tslint:disable:object-literal-sort-keys */
|
||
|
Rule.metadata = {
|
||
|
ruleName: "indent",
|
||
|
description: "Enforces indentation with tabs or spaces.",
|
||
|
rationale: (_a = ["\n Using only one of tabs or spaces for indentation leads to more consistent editor behavior,\n cleaner diffs in version control, and easier programmatic manipulation."], _a.raw = ["\n Using only one of tabs or spaces for indentation leads to more consistent editor behavior,\n cleaner diffs in version control, and easier programmatic manipulation."], Lint.Utils.dedent(_a)),
|
||
|
optionsDescription: (_b = ["\n One of the following arguments must be provided:\n\n * `", "` enforces consistent spaces.\n * `", "` enforces consistent tabs.\n\n A second optional argument specifies indentation size:\n\n * `", "` enforces 2 space indentation.\n * `", "` enforces 4 space indentation.\n\n Indentation size is required for auto-fixing, but not for rule checking.\n "], _b.raw = ["\n One of the following arguments must be provided:\n\n * \\`", "\\` enforces consistent spaces.\n * \\`", "\\` enforces consistent tabs.\n\n A second optional argument specifies indentation size:\n\n * \\`", "\\` enforces 2 space indentation.\n * \\`", "\\` enforces 4 space indentation.\n\n Indentation size is required for auto-fixing, but not for rule checking.\n "], Lint.Utils.dedent(_b, OPTION_USE_SPACES, OPTION_USE_TABS, OPTION_INDENT_SIZE_2.toString(), OPTION_INDENT_SIZE_4.toString())),
|
||
|
options: {
|
||
|
type: "array",
|
||
|
items: [
|
||
|
{
|
||
|
type: "string",
|
||
|
enum: [OPTION_USE_TABS, OPTION_USE_SPACES],
|
||
|
},
|
||
|
{
|
||
|
type: "number",
|
||
|
enum: [OPTION_INDENT_SIZE_2, OPTION_INDENT_SIZE_4],
|
||
|
},
|
||
|
],
|
||
|
minLength: 0,
|
||
|
maxLength: 5,
|
||
|
},
|
||
|
optionExamples: [
|
||
|
[true, OPTION_USE_SPACES],
|
||
|
[true, OPTION_USE_SPACES, OPTION_INDENT_SIZE_4],
|
||
|
[true, OPTION_USE_TABS, OPTION_INDENT_SIZE_2],
|
||
|
],
|
||
|
type: "maintainability",
|
||
|
typescriptOnly: false,
|
||
|
};
|
||
|
/* tslint:enable:object-literal-sort-keys */
|
||
|
Rule.FAILURE_STRING_TABS = "tab indentation expected";
|
||
|
Rule.FAILURE_STRING_SPACES = "space indentation expected";
|
||
|
exports.Rule = Rule;
|
||
|
// visit every token and enforce that only the right character is used for indentation
|
||
|
var IndentWalker = (function (_super) {
|
||
|
tslib_1.__extends(IndentWalker, _super);
|
||
|
function IndentWalker(sourceFile, options) {
|
||
|
var _this = _super.call(this, sourceFile, options) || this;
|
||
|
// fixer is only provided with the indent size arg
|
||
|
if (_this.getOptions().length === 2 && typeof _this.getOptions()[1] === "number"
|
||
|
&& (_this.getOptions()[1] === OPTION_INDENT_SIZE_2 || _this.getOptions()[1] === OPTION_INDENT_SIZE_4)) {
|
||
|
// tslint:disable-next-line:no-unsafe-any
|
||
|
var size_1 = _this.getOptions()[1];
|
||
|
var replaceRegExp_1;
|
||
|
var replaceIndent_1;
|
||
|
if (_this.hasOption(OPTION_USE_TABS)) {
|
||
|
_this.regExp = new RegExp(" ".repeat(size_1));
|
||
|
_this.failureString = Rule.FAILURE_STRING_TABS;
|
||
|
// we want to find every group of `size` spaces, plus up to one 'incomplete' group
|
||
|
replaceRegExp_1 = new RegExp("^( {" + size_1 + "})+( {1," + (size_1 - 1) + "})?", "g");
|
||
|
replaceIndent_1 = "\t";
|
||
|
}
|
||
|
else if (_this.hasOption(OPTION_USE_SPACES)) {
|
||
|
_this.regExp = new RegExp("\t");
|
||
|
_this.failureString = size_1 + " " + Rule.FAILURE_STRING_SPACES;
|
||
|
replaceRegExp_1 = new RegExp("\t", "g");
|
||
|
replaceIndent_1 = " ".repeat(size_1);
|
||
|
}
|
||
|
_this.replacementFactory = function (lineStart, fullLeadingWhitespace) {
|
||
|
return new Lint.Replacement(lineStart, fullLeadingWhitespace.length, fullLeadingWhitespace.replace(replaceRegExp_1, function (match) { return replaceIndent_1.repeat(Math.ceil(match.length / size_1)); }));
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
if (_this.hasOption(OPTION_USE_TABS)) {
|
||
|
_this.regExp = new RegExp(" ");
|
||
|
_this.failureString = Rule.FAILURE_STRING_TABS;
|
||
|
}
|
||
|
else if (_this.hasOption(OPTION_USE_SPACES)) {
|
||
|
_this.regExp = new RegExp("\t");
|
||
|
_this.failureString = Rule.FAILURE_STRING_SPACES;
|
||
|
}
|
||
|
_this.replacementFactory = function () { return undefined; };
|
||
|
}
|
||
|
return _this;
|
||
|
}
|
||
|
IndentWalker.prototype.visitSourceFile = function (node) {
|
||
|
if (!this.hasOption(OPTION_USE_TABS) && !this.hasOption(OPTION_USE_SPACES)) {
|
||
|
// if we don't have either option, no need to check anything, and no need to call super, so just return
|
||
|
return;
|
||
|
}
|
||
|
var endOfComment = -1;
|
||
|
var endOfTemplateString = -1;
|
||
|
var scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text);
|
||
|
for (var _i = 0, _a = node.getLineStarts(); _i < _a.length; _i++) {
|
||
|
var lineStart = _a[_i];
|
||
|
if (lineStart < endOfComment || lineStart < endOfTemplateString) {
|
||
|
// skip checking lines inside multi-line comments or template strings
|
||
|
continue;
|
||
|
}
|
||
|
scanner.setTextPos(lineStart);
|
||
|
var currentScannedType = scanner.scan();
|
||
|
var fullLeadingWhitespace = "";
|
||
|
var lastStartPos = -1;
|
||
|
while (currentScannedType === ts.SyntaxKind.WhitespaceTrivia) {
|
||
|
var startPos = scanner.getStartPos();
|
||
|
if (startPos === lastStartPos) {
|
||
|
break;
|
||
|
}
|
||
|
lastStartPos = startPos;
|
||
|
fullLeadingWhitespace += scanner.getTokenText();
|
||
|
currentScannedType = scanner.scan();
|
||
|
}
|
||
|
var commentRanges = ts.getTrailingCommentRanges(node.text, lineStart);
|
||
|
if (commentRanges !== undefined) {
|
||
|
endOfComment = commentRanges[commentRanges.length - 1].end;
|
||
|
}
|
||
|
else {
|
||
|
var scanType = currentScannedType;
|
||
|
// scan until we reach end of line, skipping over template strings
|
||
|
while (scanType !== ts.SyntaxKind.NewLineTrivia && scanType !== ts.SyntaxKind.EndOfFileToken) {
|
||
|
if (scanType === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
||
|
// template string without expressions - skip past it
|
||
|
endOfTemplateString = scanner.getStartPos() + scanner.getTokenText().length;
|
||
|
}
|
||
|
else if (scanType === ts.SyntaxKind.TemplateHead) {
|
||
|
// find end of template string containing expressions...
|
||
|
while (scanType !== ts.SyntaxKind.TemplateTail && scanType !== ts.SyntaxKind.EndOfFileToken) {
|
||
|
scanType = scanner.scan();
|
||
|
if (scanType === ts.SyntaxKind.CloseBraceToken) {
|
||
|
scanType = scanner.reScanTemplateToken();
|
||
|
}
|
||
|
}
|
||
|
// ... and skip past it
|
||
|
endOfTemplateString = scanner.getStartPos() + scanner.getTokenText().length;
|
||
|
}
|
||
|
scanType = scanner.scan();
|
||
|
}
|
||
|
}
|
||
|
switch (currentScannedType) {
|
||
|
case ts.SyntaxKind.SingleLineCommentTrivia:
|
||
|
case ts.SyntaxKind.MultiLineCommentTrivia:
|
||
|
case ts.SyntaxKind.NewLineTrivia:
|
||
|
// ignore lines that have comments before the first token
|
||
|
continue;
|
||
|
}
|
||
|
if (this.regExp.test(fullLeadingWhitespace)) {
|
||
|
this.addFailureAt(lineStart, fullLeadingWhitespace.length, this.failureString, this.replacementFactory(lineStart, fullLeadingWhitespace));
|
||
|
}
|
||
|
}
|
||
|
// no need to call super to visit the rest of the nodes, so don't call super here
|
||
|
};
|
||
|
return IndentWalker;
|
||
|
}(Lint.RuleWalker));
|
||
|
var _a, _b;
|