From f77812c7cb103667185000c470f3b14ef01f2f47 Mon Sep 17 00:00:00 2001 From: Pavel Birukov Date: Wed, 25 Jul 2018 23:38:01 +0300 Subject: [PATCH] Support config option to enforce delimiter even in one liners (#3964) --- src/rules/typeLiteralDelimiterRule.ts | 52 +++++++++++++------ .../one-liners-with-no-trailings/test.ts.fix | 21 ++++++++ .../test.ts.lint | 0 .../tslint.json | 0 .../one-liners-with-trailings/test.ts.fix | 21 ++++++++ .../one-liners-with-trailings/test.ts.lint | 31 +++++++++++ .../one-liners-with-trailings/tslint.json | 10 ++++ 7 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.fix rename test/rules/type-literal-delimiter/{ => one-liners-with-no-trailings}/test.ts.lint (100%) rename test/rules/type-literal-delimiter/{ => one-liners-with-no-trailings}/tslint.json (100%) create mode 100644 test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.fix create mode 100644 test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.lint create mode 100644 test/rules/type-literal-delimiter/one-liners-with-trailings/tslint.json diff --git a/src/rules/typeLiteralDelimiterRule.ts b/src/rules/typeLiteralDelimiterRule.ts index ab0baeb1992..a072dc9ab45 100644 --- a/src/rules/typeLiteralDelimiterRule.ts +++ b/src/rules/typeLiteralDelimiterRule.ts @@ -20,6 +20,11 @@ import * as ts from "typescript"; import * as Lint from "../index"; +const singeLineConfigOptionName = "singleLine"; +interface Options { + [singeLineConfigOptionName]?: "never" | "always"; +} + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -27,8 +32,17 @@ export class Rule extends Lint.Rules.AbstractRule { description: Lint.Utils.dedent` Checks that type literal members are separated by semicolons. Enforces a trailing semicolon for multiline type literals.`, - optionsDescription: "Not configurable.", - options: null, + optionsDescription: `\`{${singeLineConfigOptionName}: "always"}\` enforces semicolon for one liners`, + options: { + type: "object", + properties: { + [singeLineConfigOptionName]: { + type: "string", + enum: ["always", "never"] as Array, + }, + }, + }, + hasFix: true, optionExamples: [true], type: "style", typescriptOnly: true, @@ -43,43 +57,51 @@ export class Rule extends Lint.Rules.AbstractRule { "Did not expect single-line type literal to have a trailing ';'."; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction( + sourceFile, + walk, + this.getRuleOptions()); + } + + private getRuleOptions(): Options { + if (this.ruleArguments[0] === undefined) { + return {}; + } else { + return this.ruleArguments[0] as Options; + } } } -function walk(ctx: Lint.WalkContext): void { - const { sourceFile } = ctx; +function walk(ctx: Lint.WalkContext): void { + const { sourceFile, options } = ctx; ts.forEachChild(sourceFile, function cb(node: ts.Node): void { if (isTypeLiteralNode(node)) { check(node); } ts.forEachChild(node, cb); }); - function check(node: ts.TypeLiteralNode): void { node.members.forEach((member, idx) => { const end = member.end - 1; - // Trailing delimiter should be ommitted for a single-line type literal. - const shouldOmit = idx === node.members.length - 1 && isSameLine(sourceFile, node.getStart(sourceFile), node.getEnd()); + // Check if delimiter should be ommitted for a single-line type literal. + const shouldOmit = options.singleLine === "always" + ? false + : idx === node.members.length - 1 && isSameLine(sourceFile, node.getStart(sourceFile), node.getEnd()); const delimiter = sourceFile.text[end]; switch (delimiter) { case ";": if (shouldOmit) { - fail(Rule.FAILURE_STRING_TRAILING); + ctx.addFailureAt(end, 1, Rule.FAILURE_STRING_TRAILING, Lint.Replacement.replaceFromTo(end, end + 1, "")); } break; case ",": - fail(Rule.FAILURE_STRING_COMMA); + ctx.addFailureAt(end, 1, Rule.FAILURE_STRING_COMMA, Lint.Replacement.replaceFromTo(end, end + 1, ";")); break; default: if (!shouldOmit) { - fail(Rule.FAILURE_STRING_MISSING); + ctx.addFailureAt(end, 1, Rule.FAILURE_STRING_MISSING, Lint.Replacement.replaceFromTo(end + 1, end + 1, ";")); } } - - function fail(failure: string): void { - ctx.addFailureAt(end, 1, failure); - } }); } } diff --git a/test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.fix b/test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.fix new file mode 100644 index 00000000000..75bb20c25af --- /dev/null +++ b/test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.fix @@ -0,0 +1,21 @@ +type T = { + x: number; + y: string; +}; + +type T = { + x: number; + y: string; +}; + +type T = { + x: number; + y: string; +}; + +type T = { x: number; y: string }; + +// Works even when there's extra whitespace +type T = { x: number ; y: number }; +type T = { x: number ; y: number }; + diff --git a/test/rules/type-literal-delimiter/test.ts.lint b/test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.lint similarity index 100% rename from test/rules/type-literal-delimiter/test.ts.lint rename to test/rules/type-literal-delimiter/one-liners-with-no-trailings/test.ts.lint diff --git a/test/rules/type-literal-delimiter/tslint.json b/test/rules/type-literal-delimiter/one-liners-with-no-trailings/tslint.json similarity index 100% rename from test/rules/type-literal-delimiter/tslint.json rename to test/rules/type-literal-delimiter/one-liners-with-no-trailings/tslint.json diff --git a/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.fix b/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.fix new file mode 100644 index 00000000000..7d8243768e1 --- /dev/null +++ b/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.fix @@ -0,0 +1,21 @@ +type T = { + x: number; + y: string; +}; + +type T = { + x: number; + y: string; +}; + +type T = { + x: number; + y: string; +}; + +type T = { x: number; y: string; }; + +// Works even when there's extra whitespace +type T = { x: number ; y: number; }; +type T = { x: number ; y: number ; }; + diff --git a/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.lint b/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.lint new file mode 100644 index 00000000000..49c25a3699a --- /dev/null +++ b/test/rules/type-literal-delimiter/one-liners-with-trailings/test.ts.lint @@ -0,0 +1,31 @@ +type T = { + x: number, + ~ [COMMA] + y: string + ~ [MISSING] +}; + +type T = { + x: number + ~ [MISSING] + y: string, + ~ [COMMA] +}; + +type T = { + x: number; + y: string; +}; + +type T = { x: number; y: string }; + ~ [MISSING] + +// Works even when there's extra whitespace +type T = { x: number ; y: number }; + ~ [MISSING] +type T = { x: number , y: number ; }; + ~ [COMMA] + +[MISSING]: Expected type literal to use ';' to separate members. +[COMMA]: Expected type literal to use ';' instead of ','. +[EXTRA]: Did not expect single-line type literal to have a trailing ';'. diff --git a/test/rules/type-literal-delimiter/one-liners-with-trailings/tslint.json b/test/rules/type-literal-delimiter/one-liners-with-trailings/tslint.json new file mode 100644 index 00000000000..a7f4da3afc2 --- /dev/null +++ b/test/rules/type-literal-delimiter/one-liners-with-trailings/tslint.json @@ -0,0 +1,10 @@ +{ + "rules": { + "type-literal-delimiter": [ + true, + { + "singleLine": "always" + } + ] + } +}