From 69236c9be8e2d95b2ccdeb558ea6fd8fd2b5e042 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 22 Oct 2015 11:35:48 -0700 Subject: [PATCH 1/3] Add rule to check spaces around intersection/union type operators --- Jakefile.js | 3 ++- scripts/tslint/typeOperatorSpacingRule.ts | 28 +++++++++++++++++++++++ src/compiler/checker.ts | 14 ++++++------ src/compiler/emitter.ts | 12 +++++----- src/compiler/parser.ts | 2 +- tslint.json | 3 ++- 6 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 scripts/tslint/typeOperatorSpacingRule.ts diff --git a/Jakefile.js b/Jakefile.js index 5b93462d85..b025bba408 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -820,7 +820,8 @@ var tslintRuleDir = "scripts/tslint"; var tslintRules = ([ "nextLineRule", "noNullRule", - "booleanTriviaRule" + "booleanTriviaRule", + "typeOperatorSpacingRule" ]); var tslintRulesFiles = tslintRules.map(function(p) { return path.join(tslintRuleDir, p + ".ts"); diff --git a/scripts/tslint/typeOperatorSpacingRule.ts b/scripts/tslint/typeOperatorSpacingRule.ts new file mode 100644 index 0000000000..4143f273dd --- /dev/null +++ b/scripts/tslint/typeOperatorSpacingRule.ts @@ -0,0 +1,28 @@ +/// +/// + + +export class Rule extends Lint.Rules.AbstractRule { + public static FAILURE_STRING = "Place spaces around the '|' and '&' type operators";; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new TypeOperatorSpacingWalker(sourceFile, this.getOptions())); + } +} + +class TypeOperatorSpacingWalker extends Lint.RuleWalker { + public visitNode(node: ts.Node) { + if(node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType) { + let typeNode = node; + let expectedStart = typeNode.types[0].end + 2; // space, | or & + for (let i = 1; i < typeNode.types.length; i++) { + if(expectedStart !== typeNode.types[i].pos || typeNode.types[i].getLeadingTriviaWidth() !== 1) { + const failure = this.createFailure(typeNode.types[i].pos, typeNode.types[i].getWidth(), Rule.FAILURE_STRING); + this.addFailure(failure); + } + expectedStart = typeNode.types[i].end + 2; + } + } + super.visitNode(node); + } +} diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b31bb6673d..0982d58584 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7021,7 +7021,7 @@ namespace ts { return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; } - function getContextualTypeForJsxExpression(expr: JsxExpression|JsxSpreadAttribute): Type { + function getContextualTypeForJsxExpression(expr: JsxExpression | JsxSpreadAttribute): Type { // Contextual type only applies to JSX expressions that are in attribute assignments (not in 'Children' positions) if (expr.parent.kind === SyntaxKind.JsxAttribute) { let attrib = expr.parent; @@ -7532,7 +7532,7 @@ namespace ts { /** * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name */ - function isJsxIntrinsicIdentifier(tagName: Identifier|QualifiedName) { + function isJsxIntrinsicIdentifier(tagName: Identifier | QualifiedName) { if (tagName.kind === SyntaxKind.QualifiedName) { return false; } @@ -7618,7 +7618,7 @@ namespace ts { /// If this is a class-based tag (otherwise returns undefined), returns the symbol of the class /// type or factory function. /// Otherwise, returns unknownSymbol. - function getJsxElementTagSymbol(node: JsxOpeningLikeElement|JsxClosingElement): Symbol { + function getJsxElementTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { let flags: JsxFlags = JsxFlags.UnknownElement; let links = getNodeLinks(node); if (!links.resolvedSymbol) { @@ -7631,7 +7631,7 @@ namespace ts { } return links.resolvedSymbol; - function lookupIntrinsicTag(node: JsxOpeningLikeElement|JsxClosingElement): Symbol { + function lookupIntrinsicTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { let intrinsicElementsType = getJsxIntrinsicElementsType(); if (intrinsicElementsType !== unknownType) { // Property case @@ -7659,7 +7659,7 @@ namespace ts { } } - function lookupClassTag(node: JsxOpeningLikeElement|JsxClosingElement): Symbol { + function lookupClassTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { let valueSymbol: Symbol = resolveJsxTagName(node); // Look up the value in the current scope @@ -7673,7 +7673,7 @@ namespace ts { return valueSymbol || unknownSymbol; } - function resolveJsxTagName(node: JsxOpeningLikeElement|JsxClosingElement): Symbol { + function resolveJsxTagName(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { if (node.tagName.kind === SyntaxKind.Identifier) { let tag = node.tagName; let sym = getResolvedSymbol(tag); @@ -15540,7 +15540,7 @@ namespace ts { } } - function checkGrammarJsxElement(node: JsxOpeningElement|JsxSelfClosingElement) { + function checkGrammarJsxElement(node: JsxOpeningLikeElement) { const seen: Map = {}; for (let attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f2468e7244..9c9f5f416c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1404,10 +1404,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emit(span.literal); } - function jsxEmitReact(node: JsxElement|JsxSelfClosingElement) { + function jsxEmitReact(node: JsxElement | JsxSelfClosingElement) { /// Emit a tag name, which is either '"div"' for lower-cased names, or /// 'Div' for upper-cased or dotted names - function emitTagName(name: Identifier|QualifiedName) { + function emitTagName(name: Identifier | QualifiedName) { if (name.kind === SyntaxKind.Identifier && isIntrinsicJsxName((name).text)) { write("\""); emit(name); @@ -1557,7 +1557,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function jsxEmitPreserve(node: JsxElement|JsxSelfClosingElement) { + function jsxEmitPreserve(node: JsxElement | JsxSelfClosingElement) { function emitJsxAttribute(node: JsxAttribute) { emit(node.name); if (node.initializer) { @@ -1572,7 +1572,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("}"); } - function emitAttributes(attribs: NodeArray) { + function emitAttributes(attribs: NodeArray) { for (let i = 0, n = attribs.length; i < n; i++) { if (i > 0) { write(" "); @@ -1588,7 +1588,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function emitJsxOpeningOrSelfClosingElement(node: JsxOpeningElement|JsxSelfClosingElement) { + function emitJsxOpeningOrSelfClosingElement(node: JsxOpeningElement | JsxSelfClosingElement) { write("<"); emit(node.tagName); if (node.attributes.length > 0 || (node.kind === SyntaxKind.JsxSelfClosingElement)) { @@ -7241,7 +7241,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return emitTemplateSpan(node); case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: - return emitJsxElement(node); + return emitJsxElement(node); case SyntaxKind.JsxText: return emitJsxText(node); case SyntaxKind.JsxExpression: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1551706a33..84c5548b28 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3512,7 +3512,7 @@ namespace ts { return result; } - function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement|JsxSelfClosingElement { + function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement { let fullStart = scanner.getStartPos(); parseExpected(SyntaxKind.LessThanToken); diff --git a/tslint.json b/tslint.json index 19ccd30ca9..db10daa3fc 100644 --- a/tslint.json +++ b/tslint.json @@ -38,6 +38,7 @@ "no-trailing-whitespace": true, "no-inferrable-types": true, "no-null": true, - "boolean-trivia": true + "boolean-trivia": true, + "type-operator-spacing": true } } From 9da964f019b60880516d549b39849caf23e97a89 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 22 Oct 2015 11:39:41 -0700 Subject: [PATCH 2/3] Remove extra semicolon (the irony) --- scripts/tslint/typeOperatorSpacingRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tslint/typeOperatorSpacingRule.ts b/scripts/tslint/typeOperatorSpacingRule.ts index 4143f273dd..3519936dd6 100644 --- a/scripts/tslint/typeOperatorSpacingRule.ts +++ b/scripts/tslint/typeOperatorSpacingRule.ts @@ -3,7 +3,7 @@ export class Rule extends Lint.Rules.AbstractRule { - public static FAILURE_STRING = "Place spaces around the '|' and '&' type operators";; + public static FAILURE_STRING = "Place spaces around the '|' and '&' type operators"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new TypeOperatorSpacingWalker(sourceFile, this.getOptions())); From d284a90cc2242666344375c113795e85a4eb1404 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 22 Oct 2015 15:43:18 -0700 Subject: [PATCH 3/3] Style changes --- scripts/tslint/typeOperatorSpacingRule.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/tslint/typeOperatorSpacingRule.ts b/scripts/tslint/typeOperatorSpacingRule.ts index 3519936dd6..2392549334 100644 --- a/scripts/tslint/typeOperatorSpacingRule.ts +++ b/scripts/tslint/typeOperatorSpacingRule.ts @@ -3,7 +3,7 @@ export class Rule extends Lint.Rules.AbstractRule { - public static FAILURE_STRING = "Place spaces around the '|' and '&' type operators"; + public static FAILURE_STRING = "The '|' and '&' operators must be surrounded by single spaces"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new TypeOperatorSpacingWalker(sourceFile, this.getOptions())); @@ -12,15 +12,16 @@ export class Rule extends Lint.Rules.AbstractRule { class TypeOperatorSpacingWalker extends Lint.RuleWalker { public visitNode(node: ts.Node) { - if(node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType) { - let typeNode = node; - let expectedStart = typeNode.types[0].end + 2; // space, | or & - for (let i = 1; i < typeNode.types.length; i++) { - if(expectedStart !== typeNode.types[i].pos || typeNode.types[i].getLeadingTriviaWidth() !== 1) { - const failure = this.createFailure(typeNode.types[i].pos, typeNode.types[i].getWidth(), Rule.FAILURE_STRING); + if (node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType) { + let types = (node).types; + let expectedStart = types[0].end + 2; // space, | or & + for (let i = 1; i < types.length; i++) { + let currentType = types[i]; + if (expectedStart !== currentType.pos || currentType.getLeadingTriviaWidth() !== 1) { + const failure = this.createFailure(currentType.pos, currentType.getWidth(), Rule.FAILURE_STRING); this.addFailure(failure); } - expectedStart = typeNode.types[i].end + 2; + expectedStart = currentType.end + 2; } } super.visitNode(node);