Skip to content
This repository was archived by the owner on Feb 21, 2022. It is now read-only.

Commit 2ed91e2

Browse files
committed
[feat] added array-bracket-spacing rule (closes #57)
1 parent aa5c342 commit 2ed91e2

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

src/rules/arrayBracketSpacingRule.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import * as ts from 'typescript';
2+
import * as Lint from 'tslint/lib/lint';
3+
4+
const OPTION_ALWAYS = 'always';
5+
6+
export class Rule extends Lint.Rules.AbstractRule {
7+
public static FAILURE_STRING = {
8+
noBeginningSpace: 'There should be no space after "["',
9+
noEndingSpace: 'There should be no space before "]"',
10+
requiredBeginningSpace: 'A space is required after "["',
11+
requiredEndingSpace: 'A space is required before "]"'
12+
};
13+
14+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
15+
const walker = new ArrayBracketSpacingWalker(sourceFile, this.getOptions());
16+
return this.applyWithWalker(walker);
17+
}
18+
}
19+
20+
class ArrayBracketSpacingWalker extends Lint.RuleWalker {
21+
22+
private always: boolean;
23+
private never: boolean;
24+
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
25+
super(sourceFile, options);
26+
this.always = this.hasOption(OPTION_ALWAYS) || (this.getOptions() && this.getOptions().length === 0);
27+
28+
// this redundant variable is for readability below
29+
this.never = !this.always;
30+
}
31+
32+
protected visitNode(node: ts.Node): void {
33+
if (node.kind === ts.SyntaxKind.ArrayBindingPattern) {
34+
this.validateSquareBrackets(node);
35+
}
36+
super.visitNode(node);
37+
}
38+
39+
protected visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): void {
40+
this.validateSquareBrackets(node);
41+
super.visitArrayLiteralExpression(node);
42+
}
43+
44+
// note: this function assumes that the node will always have at
45+
// least two children: an opening bracket and a closing bracket
46+
private validateSquareBrackets(node: ts.Node): void {
47+
const children = node.getChildren();
48+
const isEmptyArrayLiteral = !this.isSpaceBetween(node.getFirstToken(), node.getLastToken());
49+
const isSpaceAfterOpeningBracket = this.isSpaceBetween(children[0], children[1]);
50+
const isBreakAfterOpeningBracket = this.isLineBreakBetween(children[0], children[1]);
51+
const isSpaceBeforeClosingBracket = this.isSpaceBetween(children[children.length - 2], children[children.length - 1]);
52+
const isBreakBeforeClosingBracket = this.isLineBreakBetween(children[children.length - 2], children[children.length - 1]);
53+
54+
node.getChildren().forEach((child, index, parent) => {
55+
if (child.kind === ts.SyntaxKind.OpenBracketToken) {
56+
if (this.never && isSpaceAfterOpeningBracket && !isBreakAfterOpeningBracket) {
57+
this.addFailure(this.createFailure(child.getStart(), child.getWidth(), Rule.FAILURE_STRING.noBeginningSpace));
58+
}
59+
60+
if (this.always && !isSpaceAfterOpeningBracket && !isEmptyArrayLiteral) {
61+
this.addFailure(this.createFailure(child.getStart(), child.getWidth(), Rule.FAILURE_STRING.requiredBeginningSpace));
62+
}
63+
}
64+
65+
if (child.kind === ts.SyntaxKind.CloseBracketToken) {
66+
if (this.never && isSpaceBeforeClosingBracket && !isBreakBeforeClosingBracket) {
67+
this.addFailure(this.createFailure(child.getStart(), child.getWidth(), Rule.FAILURE_STRING.noEndingSpace));
68+
}
69+
70+
if (this.always && !isSpaceBeforeClosingBracket && !isEmptyArrayLiteral) {
71+
this.addFailure(this.createFailure(child.getStart(), child.getWidth(), Rule.FAILURE_STRING.requiredEndingSpace));
72+
}
73+
}
74+
});
75+
}
76+
77+
// space/line break detection helpers
78+
private isSpaceBetween(node: ts.Node, nextNode: ts.Node): boolean {
79+
return nextNode.getStart() - node.getEnd() > 0;
80+
}
81+
82+
private isLineBreakBetween(node: ts.Node, nextNode: ts.Node): boolean {
83+
return this.getEndPosition(node).line !== this.getStartPosition(nextNode).line;
84+
}
85+
86+
private getStartPosition(node: ts.Node) {
87+
return node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
88+
}
89+
90+
private getEndPosition(node: ts.Node) {
91+
return node.getSourceFile().getLineAndCharacterOfPosition(node.getEnd());
92+
}
93+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/// <reference path='../../../typings/mocha/mocha.d.ts' />
2+
import {makeTest} from './helper';
3+
4+
const rule = 'array-bracket-spacing';
5+
const scripts = {
6+
always: {
7+
valid: [
8+
`var arr = [ 'foo', bar' ];`,
9+
`var [ x, y ] = z;`,
10+
`var arr = [];`,
11+
`var arr = [ 'foo', 'bar', 'baz' ];`,
12+
`var arr = [ [ 'foo' ], 'bar', 'baz' ];`,
13+
`var arr = [ 'foo',
14+
'bar'
15+
];`,
16+
`var arr = [
17+
'foo',
18+
'bar' ];`,
19+
`var arr = [
20+
'foo',
21+
'bar',
22+
'baz'
23+
];`,
24+
`var [ x, y ] = z;`,
25+
`var [ x,y ] = z;`,
26+
`var [ x, ...y ] = z;`,
27+
`var [ ,,x, ] = z;`
28+
],
29+
invalid: [
30+
`var arr = ['foo', 'bar'];`,
31+
`var arr = ['foo', 'bar' ];`,
32+
`var arr = [ ['foo'], 'bar' ];`,
33+
`var arr = ['foo',
34+
'bar'
35+
];`,
36+
`var arr = [
37+
'foo',
38+
'bar'];`,
39+
`var [x, y] = z;`,
40+
`var [x,y] = z;`,
41+
`var [x, ...y] = z;`,
42+
`var [,,x,] = z;`
43+
]
44+
},
45+
never: {
46+
valid: [
47+
`var arr = [];`,
48+
`var arr = ['foo', 'bar', 'baz'];`,
49+
`var arr = [['foo'], 'bar', 'baz'];`,
50+
`var arr = [
51+
'foo',
52+
'bar',
53+
'baz'
54+
];`,
55+
`var arr = ['foo',
56+
'bar'
57+
];`,
58+
`var arr = [
59+
'foo',
60+
'bar'];`,
61+
`var [x, y] = z;`,
62+
`var [x,y] = z;`,
63+
`var [x, ...y] = z;`,
64+
`var [,,x,] = z;`
65+
],
66+
invalid: [
67+
`var arr = [ 'foo', 'bar' ];`,
68+
`var arr = ['foo', 'bar' ];`,
69+
`var arr = [ ['foo'], 'bar'];`,
70+
`var arr = [[ 'foo' ], 'bar'];`,
71+
`var arr = [ 'foo',
72+
'bar'
73+
];`,
74+
`var [ x, y ] = z;`,
75+
`var [ x,y ] = z;`,
76+
`var [ x, ...y ] = z;`,
77+
`var [ ,,x, ] = z;`
78+
]
79+
}
80+
};
81+
82+
describe(rule, function test() {
83+
84+
const alwaysConfig = { rules: { 'array-bracket-spacing': [true, 'always'] } };
85+
const neverConfig = { rules: { 'array-bracket-spacing': [true, 'never'] } };
86+
87+
it('should pass when there is space inside of array brackets for rule option "always"', function testVariables() {
88+
makeTest(rule, scripts.always.valid, true, alwaysConfig);
89+
});
90+
91+
it('should fail when there is not a space inside of array brackets for rule option "always"', function testVariables() {
92+
makeTest(rule, scripts.always.invalid, false, alwaysConfig);
93+
});
94+
95+
it('should pass when there is no space inside of array brackets for rule option "never"', function testVariables() {
96+
makeTest(rule, scripts.never.valid, true, neverConfig);
97+
});
98+
99+
it('should fail when there are spaces inside of array brackets for rule option "never"', function testVariables() {
100+
makeTest(rule, scripts.never.invalid, false, neverConfig);
101+
});
102+
});

0 commit comments

Comments
 (0)