Skip to content

Commit 69236c9

Browse files
committed
Add rule to check spaces around intersection/union type operators
1 parent 28c2887 commit 69236c9

File tree

6 files changed

+46
-16
lines changed

6 files changed

+46
-16
lines changed

Jakefile.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,8 @@ var tslintRuleDir = "scripts/tslint";
820820
var tslintRules = ([
821821
"nextLineRule",
822822
"noNullRule",
823-
"booleanTriviaRule"
823+
"booleanTriviaRule",
824+
"typeOperatorSpacingRule"
824825
]);
825826
var tslintRulesFiles = tslintRules.map(function(p) {
826827
return path.join(tslintRuleDir, p + ".ts");
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path="../../node_modules/tslint/typings/typescriptServices.d.ts" />
2+
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
3+
4+
5+
export class Rule extends Lint.Rules.AbstractRule {
6+
public static FAILURE_STRING = "Place spaces around the '|' and '&' type operators";;
7+
8+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9+
return this.applyWithWalker(new TypeOperatorSpacingWalker(sourceFile, this.getOptions()));
10+
}
11+
}
12+
13+
class TypeOperatorSpacingWalker extends Lint.RuleWalker {
14+
public visitNode(node: ts.Node) {
15+
if(node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType) {
16+
let typeNode = <ts.UnionOrIntersectionTypeNode>node;
17+
let expectedStart = typeNode.types[0].end + 2; // space, | or &
18+
for (let i = 1; i < typeNode.types.length; i++) {
19+
if(expectedStart !== typeNode.types[i].pos || typeNode.types[i].getLeadingTriviaWidth() !== 1) {
20+
const failure = this.createFailure(typeNode.types[i].pos, typeNode.types[i].getWidth(), Rule.FAILURE_STRING);
21+
this.addFailure(failure);
22+
}
23+
expectedStart = typeNode.types[i].end + 2;
24+
}
25+
}
26+
super.visitNode(node);
27+
}
28+
}

src/compiler/checker.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -7021,7 +7021,7 @@ namespace ts {
70217021
return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined;
70227022
}
70237023

7024-
function getContextualTypeForJsxExpression(expr: JsxExpression|JsxSpreadAttribute): Type {
7024+
function getContextualTypeForJsxExpression(expr: JsxExpression | JsxSpreadAttribute): Type {
70257025
// Contextual type only applies to JSX expressions that are in attribute assignments (not in 'Children' positions)
70267026
if (expr.parent.kind === SyntaxKind.JsxAttribute) {
70277027
let attrib = <JsxAttribute>expr.parent;
@@ -7532,7 +7532,7 @@ namespace ts {
75327532
/**
75337533
* Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
75347534
*/
7535-
function isJsxIntrinsicIdentifier(tagName: Identifier|QualifiedName) {
7535+
function isJsxIntrinsicIdentifier(tagName: Identifier | QualifiedName) {
75367536
if (tagName.kind === SyntaxKind.QualifiedName) {
75377537
return false;
75387538
}
@@ -7618,7 +7618,7 @@ namespace ts {
76187618
/// If this is a class-based tag (otherwise returns undefined), returns the symbol of the class
76197619
/// type or factory function.
76207620
/// Otherwise, returns unknownSymbol.
7621-
function getJsxElementTagSymbol(node: JsxOpeningLikeElement|JsxClosingElement): Symbol {
7621+
function getJsxElementTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
76227622
let flags: JsxFlags = JsxFlags.UnknownElement;
76237623
let links = getNodeLinks(node);
76247624
if (!links.resolvedSymbol) {
@@ -7631,7 +7631,7 @@ namespace ts {
76317631
}
76327632
return links.resolvedSymbol;
76337633

7634-
function lookupIntrinsicTag(node: JsxOpeningLikeElement|JsxClosingElement): Symbol {
7634+
function lookupIntrinsicTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
76357635
let intrinsicElementsType = getJsxIntrinsicElementsType();
76367636
if (intrinsicElementsType !== unknownType) {
76377637
// Property case
@@ -7659,7 +7659,7 @@ namespace ts {
76597659
}
76607660
}
76617661

7662-
function lookupClassTag(node: JsxOpeningLikeElement|JsxClosingElement): Symbol {
7662+
function lookupClassTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
76637663
let valueSymbol: Symbol = resolveJsxTagName(node);
76647664

76657665
// Look up the value in the current scope
@@ -7673,7 +7673,7 @@ namespace ts {
76737673
return valueSymbol || unknownSymbol;
76747674
}
76757675

7676-
function resolveJsxTagName(node: JsxOpeningLikeElement|JsxClosingElement): Symbol {
7676+
function resolveJsxTagName(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
76777677
if (node.tagName.kind === SyntaxKind.Identifier) {
76787678
let tag = <Identifier>node.tagName;
76797679
let sym = getResolvedSymbol(tag);
@@ -15540,7 +15540,7 @@ namespace ts {
1554015540
}
1554115541
}
1554215542

15543-
function checkGrammarJsxElement(node: JsxOpeningElement|JsxSelfClosingElement) {
15543+
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
1554415544
const seen: Map<boolean> = {};
1554515545
for (let attr of node.attributes) {
1554615546
if (attr.kind === SyntaxKind.JsxSpreadAttribute) {

src/compiler/emitter.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1404,10 +1404,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
14041404
emit(span.literal);
14051405
}
14061406

1407-
function jsxEmitReact(node: JsxElement|JsxSelfClosingElement) {
1407+
function jsxEmitReact(node: JsxElement | JsxSelfClosingElement) {
14081408
/// Emit a tag name, which is either '"div"' for lower-cased names, or
14091409
/// 'Div' for upper-cased or dotted names
1410-
function emitTagName(name: Identifier|QualifiedName) {
1410+
function emitTagName(name: Identifier | QualifiedName) {
14111411
if (name.kind === SyntaxKind.Identifier && isIntrinsicJsxName((<Identifier>name).text)) {
14121412
write("\"");
14131413
emit(name);
@@ -1557,7 +1557,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
15571557
}
15581558
}
15591559

1560-
function jsxEmitPreserve(node: JsxElement|JsxSelfClosingElement) {
1560+
function jsxEmitPreserve(node: JsxElement | JsxSelfClosingElement) {
15611561
function emitJsxAttribute(node: JsxAttribute) {
15621562
emit(node.name);
15631563
if (node.initializer) {
@@ -1572,7 +1572,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
15721572
write("}");
15731573
}
15741574

1575-
function emitAttributes(attribs: NodeArray<JsxAttribute|JsxSpreadAttribute>) {
1575+
function emitAttributes(attribs: NodeArray<JsxAttribute | JsxSpreadAttribute>) {
15761576
for (let i = 0, n = attribs.length; i < n; i++) {
15771577
if (i > 0) {
15781578
write(" ");
@@ -1588,7 +1588,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
15881588
}
15891589
}
15901590

1591-
function emitJsxOpeningOrSelfClosingElement(node: JsxOpeningElement|JsxSelfClosingElement) {
1591+
function emitJsxOpeningOrSelfClosingElement(node: JsxOpeningElement | JsxSelfClosingElement) {
15921592
write("<");
15931593
emit(node.tagName);
15941594
if (node.attributes.length > 0 || (node.kind === SyntaxKind.JsxSelfClosingElement)) {
@@ -7241,7 +7241,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
72417241
return emitTemplateSpan(<TemplateSpan>node);
72427242
case SyntaxKind.JsxElement:
72437243
case SyntaxKind.JsxSelfClosingElement:
7244-
return emitJsxElement(<JsxElement|JsxSelfClosingElement>node);
7244+
return emitJsxElement(<JsxElement | JsxSelfClosingElement>node);
72457245
case SyntaxKind.JsxText:
72467246
return emitJsxText(<JsxText>node);
72477247
case SyntaxKind.JsxExpression:

src/compiler/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3512,7 +3512,7 @@ namespace ts {
35123512
return result;
35133513
}
35143514

3515-
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement|JsxSelfClosingElement {
3515+
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement {
35163516
let fullStart = scanner.getStartPos();
35173517

35183518
parseExpected(SyntaxKind.LessThanToken);

tslint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"no-trailing-whitespace": true,
3939
"no-inferrable-types": true,
4040
"no-null": true,
41-
"boolean-trivia": true
41+
"boolean-trivia": true,
42+
"type-operator-spacing": true
4243
}
4344
}

0 commit comments

Comments
 (0)