Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

indent: support size and fix #2723

Merged
merged 3 commits into from
May 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 65 additions & 12 deletions src/rules/indentRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import * as Lint from "../index";

const OPTION_USE_TABS = "tabs";
const OPTION_USE_SPACES = "spaces";
const OPTION_INDENT_SIZE_2 = 2;
const OPTION_INDENT_SIZE_4 = 4;

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
Expand All @@ -33,13 +35,33 @@ export class Rule extends Lint.Rules.AbstractRule {
optionsDescription: Lint.Utils.dedent`
One of the following arguments must be provided:

* \`"spaces"\` enforces consistent spaces.
* \`"tabs"\` enforces consistent tabs.`,
* \`${OPTION_USE_SPACES}\` enforces consistent spaces.
* \`${OPTION_USE_TABS}\` enforces consistent tabs.

A second optional argument specifies indentation size:

* \`${OPTION_INDENT_SIZE_2.toString()}\` enforces 2 space indentation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ts requires toString here because the dedent function takes an array of strings. We can probably make it take in (string | number)[] later

* \`${OPTION_INDENT_SIZE_4.toString()}\` enforces 4 space indentation.

Indentation size is required for auto-fixing, but not for rule checking.
`,
options: {
type: "string",
enum: ["tabs", "spaces"],
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, "spaces"]],
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,
};
Expand All @@ -57,16 +79,45 @@ export class Rule extends Lint.Rules.AbstractRule {
class IndentWalker extends Lint.RuleWalker {
private failureString: string;
private regExp: RegExp;
private replacementFactory: (lineStart: number, fullLeadingWhitespace: string) => Lint.Replacement | undefined;

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);

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;
// 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
const size = this.getOptions()[1] as number;
let replaceRegExp: RegExp;
let replaceIndent: string;

if (this.hasOption(OPTION_USE_TABS)) {
this.regExp = new RegExp(" ".repeat(size));
this.failureString = Rule.FAILURE_STRING_TABS;
// we want to find every group of `size` spaces, plus up to one 'incomplete' group
replaceRegExp = new RegExp(`^( {${size}})+( {1,${size - 1}})?`, "g");
replaceIndent = "\t";
} else if (this.hasOption(OPTION_USE_SPACES)) {
this.regExp = new RegExp("\t");
this.failureString = `${size} ${Rule.FAILURE_STRING_SPACES}`;
replaceRegExp = new RegExp("\t", "g");
replaceIndent = " ".repeat(size);
}

this.replacementFactory = (lineStart, fullLeadingWhitespace) =>
new Lint.Replacement(lineStart, fullLeadingWhitespace.length, fullLeadingWhitespace.replace(
replaceRegExp, (match) => replaceIndent.repeat(Math.ceil(match.length / size)),
));
} 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 = () => undefined;
}
}

Expand Down Expand Up @@ -137,7 +188,9 @@ class IndentWalker extends Lint.RuleWalker {
}

if (this.regExp.test(fullLeadingWhitespace)) {
this.addFailureAt(lineStart, fullLeadingWhitespace.length, this.failureString);
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
Expand Down
139 changes: 139 additions & 0 deletions test/rules/indent/spaces-2/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// valid code
module TestModule {
var func = () => {
console.warn("hi");
};

class TestClass {
private variable;

testFunction() {
this.variable = 3;
}
}

var obj = {
a: 1,
b: 2,
c: 3
};

// ignore leading tabs inside template strings
var s1 = `
multiline` + ` template
string`;
var s2 = `
multiline ${ "A" }
template ${ "B"
+ "C" }
string`;

export enum TestEnum {
VALUE1,
VALUE2
}

switch (integerValue) {
case 1:
console.warn("1");
break;
default:
console.warn("default");
break;
}

function loops() {
for (var i = 0; i < 1; ++i) {
console.warn(i);
}

while (i < 1) {
console.warn(i);
}

do {
console.warn(i);
} while (i < 1);

if (i < 1) {
console.warn(i);
} else {
console.warn(i + 1);
}
}
}

// invalid code
// we get a weird scenario here where our ~~ underlines don't quite line up with the line above
// this is because tabs are only one character and thus only one ~ goes beneath them.
module TestModule {
var testVariable = 123;
}

function() {
var test = 123;
}

class TestClass {
private variable;

testFunction() {
this.variable = 3;
}
}

var obj = {
a: 1,
b: 2,
c: 3
};

enum TestEnum {
VALUE1,
VALUE2
}

switch (integerValue) {
case 0:
console.warn("1");
break;
case 1:
console.warn("1");
break;
default:
console.warn("default");
break;
}

for (var i = 0; i < 1; ++i) {
console.warn("123");
}

while (i < 1) {
console.warn("123");
}

do {
console.warn("123");
} while (i < 1);

if (i < 1) {
console.warn("123");
}

var arr = [
1,
2
];

var arr2 = [
{
a: 1,
b: 2
},
{
a: 3,
b: 4
}
];

Loading