Skip to content

Commit 6f2ba15

Browse files
author
Andy
authored
Start linting for double spaces (microsoft#20820)
* Start linting for double spaces * Code review * Fix cases that were excluded by countDoubleSpaces * Remove extraneous closing parenthesis
1 parent 943e522 commit 6f2ba15

35 files changed

+1481
-572
lines changed

package-lock.json

Lines changed: 1320 additions & 470 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as Lint from "tslint/lib";
2+
import * as ts from "typescript";
3+
4+
export class Rule extends Lint.Rules.AbstractRule {
5+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
6+
return this.applyWithFunction(sourceFile, walk);
7+
}
8+
}
9+
10+
function walk(ctx: Lint.WalkContext<void>): void {
11+
const { sourceFile } = ctx;
12+
const lines = sourceFile.text.split("\n");
13+
const strings = getLiterals(sourceFile);
14+
lines.forEach((line, idx) => {
15+
// Skip indentation.
16+
const firstNonSpace = /\S/.exec(line);
17+
if (firstNonSpace === null) {
18+
return;
19+
}
20+
// Allow common uses of double spaces
21+
// * To align `=` or `!=` signs
22+
// * To align comments at the end of lines
23+
// * To indent inside a comment
24+
// * To use two spaces after a period
25+
// * To include aligned `->` in a comment
26+
const rgx = /[^/*. ] [^-!/= ]/g;
27+
rgx.lastIndex = firstNonSpace.index;
28+
const doubleSpace = rgx.exec(line);
29+
// Also allow to align comments after `@param`
30+
if (doubleSpace !== null && !line.includes("@param")) {
31+
const pos = lines.slice(0, idx).reduce((len, line) => len + 1 + line.length, 0) + doubleSpace.index;
32+
if (!strings.some(s => s.getStart() <= pos && s.end > pos)) {
33+
ctx.addFailureAt(pos + 1, 2, "Use only one space.");
34+
}
35+
}
36+
});
37+
}
38+
39+
function getLiterals(sourceFile: ts.SourceFile): ReadonlyArray<ts.Node> {
40+
const out: ts.Node[] = [];
41+
sourceFile.forEachChild(function cb(node) {
42+
switch (node.kind) {
43+
case ts.SyntaxKind.StringLiteral:
44+
case ts.SyntaxKind.TemplateHead:
45+
case ts.SyntaxKind.TemplateMiddle:
46+
case ts.SyntaxKind.TemplateTail:
47+
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
48+
case ts.SyntaxKind.RegularExpressionLiteral:
49+
out.push(node);
50+
}
51+
node.forEachChild(cb);
52+
});
53+
return out;
54+
}

src/compiler/binder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,7 @@ namespace ts {
16331633
// to the one we would get for: { <...>(...): T }
16341634
//
16351635
// We do that by making an anonymous type literal symbol, and then setting the function
1636-
// symbol as its sole member. To the rest of the system, this symbol will be indistinguishable
1636+
// symbol as its sole member. To the rest of the system, this symbol will be indistinguishable
16371637
// from an actual type literal symbol you would have gotten had you used the long form.
16381638
const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node));
16391639
addDeclarationToSymbol(symbol, node, SymbolFlags.Signature);
@@ -2431,7 +2431,7 @@ namespace ts {
24312431
if (!isPrototypeProperty && (!targetSymbol || !(targetSymbol.flags & SymbolFlags.Namespace)) && isLegalPosition) {
24322432
Debug.assert(isIdentifier(propertyAccess.expression));
24332433
const identifier = propertyAccess.expression as Identifier;
2434-
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
2434+
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
24352435
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
24362436
if (targetSymbol) {
24372437
addDeclarationToSymbol(symbol, identifier, flags);

src/compiler/checker.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,7 @@ namespace ts {
12181218
!checkAndReportErrorForExtendingInterface(errorLocation) &&
12191219
!checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) &&
12201220
!checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) &&
1221-
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) {
1221+
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) {
12221222
let suggestion: string | undefined;
12231223
if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) {
12241224
suggestion = getSuggestionForNonexistentSymbol(originalLocation, name, meaning);
@@ -9656,7 +9656,7 @@ namespace ts {
96569656
}
96579657
if (target.flags & TypeFlags.Union) {
96589658
const discriminantType = findMatchingDiscriminantType(source, target as UnionType);
9659-
if (discriminantType) {
9659+
if (discriminantType) {
96609660
// check excess properties against discriminant type only, not the entire union
96619661
return hasExcessProperties(source, discriminantType, reportErrors);
96629662
}
@@ -11826,7 +11826,7 @@ namespace ts {
1182611826
const container = (node as BindingElement).parent.parent;
1182711827
const key = container.kind === SyntaxKind.BindingElement ? getFlowCacheKey(container) : (container.initializer && getFlowCacheKey(container.initializer));
1182811828
const text = getBindingElementNameText(node as BindingElement);
11829-
const result = key && text && (key + "." + text);
11829+
const result = key && text && (key + "." + text);
1183011830
return result;
1183111831
}
1183211832
return undefined;
@@ -12749,7 +12749,7 @@ namespace ts {
1274912749
firstAntecedentType = flowType;
1275012750
}
1275112751
const type = getTypeFromFlowType(flowType);
12752-
// If we see a value appear in the cache it is a sign that control flow analysis
12752+
// If we see a value appear in the cache it is a sign that control flow analysis
1275312753
// was restarted and completed by checkExpressionCached. We can simply pick up
1275412754
// the resulting type and bail out.
1275512755
const cached = cache.get(key);
@@ -21931,7 +21931,7 @@ namespace ts {
2193121931
checkGrammarForInOrForOfStatement(node);
2193221932

2193321933
const rightType = checkNonNullExpression(node.expression);
21934-
// TypeScript 1.0 spec (April 2014): 5.4
21934+
// TypeScript 1.0 spec (April 2014): 5.4
2193521935
// In a 'for-in' statement of the form
2193621936
// for (let VarDecl in Expr) Statement
2193721937
// VarDecl must be a variable declaration without a type annotation that declares a variable of type Any,
@@ -24864,7 +24864,7 @@ namespace ts {
2486424864
// AND
2486524865
// - binding is not declared in loop, should be renamed to avoid name reuse across siblings
2486624866
// let a, b
24867-
// { let x = 1; a = () => x; }
24867+
// { let x = 1; a = () => x; }
2486824868
// { let x = 100; b = () => x; }
2486924869
// console.log(a()); // should print '1'
2487024870
// console.log(b()); // should print '100'
@@ -25325,7 +25325,7 @@ namespace ts {
2532525325

2532625326
// walk the parent chain for symbols to make sure that top level parent symbol is in the global scope
2532725327
// external modules cannot define or contribute to type declaration files
25328-
let current = symbol;
25328+
let current = symbol;
2532925329
while (true) {
2533025330
const parent = getParentOfSymbol(current);
2533125331
if (parent) {

src/compiler/commandLineParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1864,7 +1864,7 @@ namespace ts {
18641864
return normalizeNonListOptionValue(option, basePath, value);
18651865
}
18661866

1867-
function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
1867+
function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
18681868
if (option.isFilePath) {
18691869
value = normalizePath(combinePaths(basePath, value));
18701870
if (value === "") {

src/compiler/core.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,7 +2080,7 @@ namespace ts {
20802080

20812081
function getNormalizedPathComponentsOfUrl(url: string) {
20822082
// Get root length of http://www.website.com/folder1/folder2/
2083-
// In this example the root is: http://www.website.com/
2083+
// In this example the root is: http://www.website.com/
20842084
// normalized path components should be ["http://www.website.com/", "folder1", "folder2"]
20852085

20862086
const urlLength = url.length;
@@ -2113,7 +2113,7 @@ namespace ts {
21132113
}
21142114
else {
21152115
// Can't find the host assume the rest of the string as component
2116-
// but make sure we append "/" to it as root is not joined using "/"
2116+
// but make sure we append "/" to it as root is not joined using "/"
21172117
// eg. if url passed in was http://website.com we want to use root as [http://website.com/]
21182118
// so that other path manipulations will be correct and it can be merged with relative paths correctly
21192119
return [url + directorySeparator];
@@ -2134,7 +2134,7 @@ namespace ts {
21342134
const directoryComponents = getNormalizedPathOrUrlComponents(directoryPathOrUrl, currentDirectory);
21352135
if (directoryComponents.length > 1 && lastOrUndefined(directoryComponents) === "") {
21362136
// If the directory path given was of type test/cases/ then we really need components of directory to be only till its name
2137-
// that is ["test", "cases", ""] needs to be actually ["test", "cases"]
2137+
// that is ["test", "cases", ""] needs to be actually ["test", "cases"]
21382138
directoryComponents.pop();
21392139
}
21402140

src/compiler/declarationEmitter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,7 @@ namespace ts {
11991199
write(">");
12001200
}
12011201
}
1202-
else {
1202+
else {
12031203
emitHeritageClause([baseTypeNode], /*isImplementsList*/ false);
12041204
}
12051205
}
@@ -1866,6 +1866,7 @@ namespace ts {
18661866
// it allows emitSeparatedList to write separator appropriately)
18671867
// Example:
18681868
// original: function foo([, x, ,]) {}
1869+
// tslint:disable-next-line no-double-space
18691870
// emit : function foo([ , x, , ]) {}
18701871
write(" ");
18711872
}

src/compiler/factory.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3831,13 +3831,13 @@ namespace ts {
38313831
if (isLeftSideOfBinary) {
38323832
// No need to parenthesize the left operand when the binary operator is
38333833
// left associative:
3834-
// (a*b)/x -> a*b/x
3835-
// (a**b)/x -> a**b/x
3834+
// (a*b)/x -> a*b/x
3835+
// (a**b)/x -> a**b/x
38363836
//
38373837
// Parentheses are needed for the left operand when the binary operator is
38383838
// right associative:
3839-
// (a/b)**x -> (a/b)**x
3840-
// (a**b)**x -> (a**b)**x
3839+
// (a/b)**x -> (a/b)**x
3840+
// (a**b)**x -> (a**b)**x
38413841
return binaryOperatorAssociativity === Associativity.Right;
38423842
}
38433843
else {

src/compiler/parser.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ namespace ts {
585585
// 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost
586586
// all nodes would need extra state on them to store this info.
587587
//
588-
// Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6
588+
// Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6
589589
// grammar specification.
590590
//
591591
// An important thing about these context concepts. By default they are effectively inherited
@@ -701,7 +701,7 @@ namespace ts {
701701

702702
function getLanguageVariant(scriptKind: ScriptKind) {
703703
// .tsx and .jsx files are treated as jsx language variant.
704-
return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard;
704+
return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard;
705705
}
706706

707707
function initializeState(_sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, scriptKind: ScriptKind) {
@@ -1453,7 +1453,7 @@ namespace ts {
14531453
function isValidHeritageClauseObjectLiteral() {
14541454
Debug.assert(token() === SyntaxKind.OpenBraceToken);
14551455
if (nextToken() === SyntaxKind.CloseBraceToken) {
1456-
// if we see "extends {}" then only treat the {} as what we're extending (and not
1456+
// if we see "extends {}" then only treat the {} as what we're extending (and not
14571457
// the class body) if we have:
14581458
//
14591459
// extends {} {
@@ -1549,7 +1549,7 @@ namespace ts {
15491549

15501550
function isVariableDeclaratorListTerminator(): boolean {
15511551
// If we can consume a semicolon (either explicitly, or with ASI), then consider us done
1552-
// with parsing the list of variable declarators.
1552+
// with parsing the list of variable declarators.
15531553
if (canParseSemicolon()) {
15541554
return true;
15551555
}
@@ -2269,7 +2269,7 @@ namespace ts {
22692269
//
22702270
// <T extends "">
22712271
//
2272-
// We do *not* want to consume the > as we're consuming the expression for "".
2272+
// We do *not* want to consume the `>` as we're consuming the expression for "".
22732273
node.expression = parseUnaryExpressionOrHigher();
22742274
}
22752275
}
@@ -3089,7 +3089,7 @@ namespace ts {
30893089
// And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression".
30903090
//
30913091
// If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is
3092-
// not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done
3092+
// not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done
30933093
// with AssignmentExpression if we see one.
30943094
const arrowExpression = tryParseParenthesizedArrowFunctionExpression() || tryParseAsyncSimpleArrowFunctionExpression();
30953095
if (arrowExpression) {
@@ -3119,7 +3119,7 @@ namespace ts {
31193119
// we're in '2' or '3'. Consume the assignment and return.
31203120
//
31213121
// Note: we call reScanGreaterToken so that we get an appropriately merged token
3122-
// for cases like > > = becoming >>=
3122+
// for cases like `> > =` becoming `>>=`
31233123
if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) {
31243124
return makeBinaryExpression(expr, <BinaryOperatorToken>parseTokenNode(), parseAssignmentExpressionOrHigher());
31253125
}
@@ -3275,7 +3275,7 @@ namespace ts {
32753275

32763276
if (first === SyntaxKind.OpenParenToken) {
32773277
if (second === SyntaxKind.CloseParenToken) {
3278-
// Simple cases: "() =>", "(): ", and "() {".
3278+
// Simple cases: "() =>", "(): ", and "() {".
32793279
// This is an arrow function with no parameters.
32803280
// The last one is not actually an arrow function,
32813281
// but this is probably what the user intended.
@@ -3895,7 +3895,8 @@ namespace ts {
38953895
// We don't want to eagerly consume all import keyword as import call expression so we look a head to find "("
38963896
// For example:
38973897
// var foo3 = require("subfolder
3898-
// import * as foo1 from "module-from-node -> we want this import to be a statement rather than import call expression
3898+
// import * as foo1 from "module-from-node
3899+
// We want this import to be a statement rather than import call expression
38993900
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
39003901
expression = parseTokenNode<PrimaryExpression>();
39013902
}
@@ -3945,7 +3946,7 @@ namespace ts {
39453946
// treated as the invocation of "new Foo". We disambiguate that in code (to match
39463947
// the original grammar) by making sure that if we see an ObjectCreationExpression
39473948
// we always consume arguments if they are there. So we treat "new Foo()" as an
3948-
// object creation only, and not at all as an invocation) Another way to think
3949+
// object creation only, and not at all as an invocation. Another way to think
39493950
// about this is that for every "new" that we see, we will consume an argument list if
39503951
// it is there as part of the *associated* object creation node. Any additional
39513952
// argument lists we see, will become invocation expressions.
@@ -4361,7 +4362,7 @@ namespace ts {
43614362

43624363
const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType);
43634364
if (!parseExpected(SyntaxKind.GreaterThanToken)) {
4364-
// If it doesn't have the closing > then it's definitely not an type argument list.
4365+
// If it doesn't have the closing `>` then it's definitely not an type argument list.
43654366
return undefined;
43664367
}
43674368

@@ -5394,8 +5395,8 @@ namespace ts {
53945395
// off. The grammar would look something like this:
53955396
//
53965397
// MemberVariableDeclaration[Yield]:
5397-
// AccessibilityModifier_opt PropertyName TypeAnnotation_opt Initializer_opt[In];
5398-
// AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initializer_opt[In, ?Yield];
5398+
// AccessibilityModifier_opt PropertyName TypeAnnotation_opt Initializer_opt[In];
5399+
// AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initializer_opt[In, ?Yield];
53995400
//
54005401
// The checker may still error in the static case to explicitly disallow the yield expression.
54015402
node.initializer = hasModifier(node, ModifierFlags.Static)
@@ -7077,7 +7078,7 @@ namespace ts {
70777078

70787079
// If the 'pos' is before the start of the change, then we don't need to touch it.
70797080
// If it isn't, then the 'pos' must be inside the change. How we update it will
7080-
// depend if delta is positive or negative. If delta is positive then we have
7081+
// depend if delta is positive or negative. If delta is positive then we have
70817082
// something like:
70827083
//
70837084
// -------------------AAA-----------------
@@ -7102,7 +7103,7 @@ namespace ts {
71027103

71037104
// If the 'end' is after the change range, then we always adjust it by the delta
71047105
// amount. However, if the end is in the change range, then how we adjust it
7105-
// will depend on if delta is positive or negative. If delta is positive then we
7106+
// will depend on if delta is positive or negative. If delta is positive then we
71067107
// have something like:
71077108
//
71087109
// -------------------AAA-----------------

0 commit comments

Comments
 (0)