From 02175557eb563b9b6f9ef58d07ae3fbbce51ea4d Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sun, 27 Aug 2017 22:40:16 +0200 Subject: [PATCH 1/5] Add option for JS spec compatibility --- src/rules/trailingCommaRule.ts | 105 ++++++++++++++---- .../multiline-always/test.ts.fix | 20 ++++ .../multiline-always/test.ts.lint | 25 +++++ .../multiline-always/tslint.json | 2 +- 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 0ffaa036cb9..70ffd848874 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -15,15 +15,15 @@ * limitations under the License. */ -import { getChildOfKind, isSameLine } from "tsutils"; +import { getChildOfKind, isReassignmentTarget, isSameLine } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; type OptionValue = "always" | "never" | "ignore"; type OptionName = "arrays" | "exports" | "functions" | "imports" | "objects" | "typeLiterals"; -type CustomOptionValue = Partial>; -type Options = Record<"multiline" | "singleline", CustomOptionValue>; +type CustomOptionValue = Record; +type Options = Record<"multiline" | "singleline", CustomOptionValue> & {specCompliant: boolean}; const defaultOptions: CustomOptionValue = fillOptions("ignore" as "ignore"); // tslint:disable-line no-unnecessary-type-assertion @@ -38,9 +38,9 @@ function fillOptions(value: T): Record { }; } -type OptionsJson = Record<"multiline" | "singleline", Partial | OptionValue>; +type OptionsJson = Partial | OptionValue> & {specCompliant: boolean}>; function normalizeOptions(options: OptionsJson): Options { - return { multiline: normalize(options.multiline), singleline: normalize(options.singleline) }; + return { multiline: normalize(options.multiline), singleline: normalize(options.singleline), specCompliant: !!options.specCompliant}; function normalize(value: OptionsJson["multiline"]): CustomOptionValue { return typeof value === "string" ? fillOptions(value) : { ...defaultOptions, ...value }; @@ -119,7 +119,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_ALWAYS = "Missing trailing comma"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const options = normalizeOptions(this.ruleArguments[0] as Options); + const options = normalizeOptions(this.ruleArguments[0] as OptionsJson); return this.applyWithWalker(new TrailingCommaWalker(sourceFile, this.ruleName, options)); } @@ -133,11 +133,13 @@ class TrailingCommaWalker extends Lint.AbstractWalker { const cb = (node: ts.Node): void => { switch (node.kind) { case ts.SyntaxKind.ArrayLiteralExpression: + this.checkArrayLiteralExpression(node as ts.ArrayLiteralExpression); + break; case ts.SyntaxKind.ArrayBindingPattern: - this.checkList((node as ts.ArrayLiteralExpression | ts.ArrayBindingPattern).elements, node.end, "arrays"); + this.checkBindingPattern(node as ts.BindingPattern, "arrays"); break; case ts.SyntaxKind.ObjectBindingPattern: - this.checkList((node as ts.ObjectBindingPattern).elements, node.end, "objects"); + this.checkBindingPattern(node as ts.ObjectBindingPattern, "objects"); break; case ts.SyntaxKind.NamedImports: this.checkList((node as ts.NamedImports).elements, node.end, "imports"); @@ -146,7 +148,7 @@ class TrailingCommaWalker extends Lint.AbstractWalker { this.checkList((node as ts.NamedExports).elements, node.end, "exports"); break; case ts.SyntaxKind.ObjectLiteralExpression: - this.checkList((node as ts.ObjectLiteralExpression).properties, node.end, "objects"); + this.checkObjectLiteralExpression(node as ts.ObjectLiteralExpression); break; case ts.SyntaxKind.EnumDeclaration: this.checkList((node as ts.EnumDeclaration).members, node.end, "objects"); @@ -170,8 +172,7 @@ class TrailingCommaWalker extends Lint.AbstractWalker { case ts.SyntaxKind.ConstructorType: case ts.SyntaxKind.FunctionType: case ts.SyntaxKind.CallSignature: - this.checkListWithEndToken(node, (node as ts.SignatureDeclaration).parameters, - ts.SyntaxKind.CloseParenToken, "functions"); + this.checkSignatureDeclaration(node as ts.SignatureDeclaration); break; case ts.SyntaxKind.TypeLiteral: this.checkTypeLiteral(node as ts.TypeLiteralNode); @@ -200,14 +201,65 @@ class TrailingCommaWalker extends Lint.AbstractWalker { return this.checkComma(hasTrailingComma, members, node.end, "typeLiterals"); } - private checkListWithEndToken(node: ts.Node, list: ts.NodeArray, closeTokenKind: ts.SyntaxKind, optionKey: OptionName) { - if (list.length === 0) { + private checkSignatureDeclaration(node: ts.SignatureDeclaration) { + const {parameters} = node; + if (parameters.length === 0) { + return; + } + return this.checkComma( + parameters.hasTrailingComma, + parameters, + getChildOfKind(node, ts.SyntaxKind.CloseParenToken, this.sourceFile)!.end, + "functions", + /*never*/ this.options.specCompliant && parameters[parameters.length - 1].dotDotDotToken !== undefined, + ); + } + + private checkBindingPattern(node: ts.BindingPattern, optionKey: "arrays" | "objects") { + const {elements} = node; + if (elements.length === 0) { + return; + } + const last = elements[elements.length - 1]; + return this.checkComma( + elements.hasTrailingComma, + elements, + node.end, + optionKey, + this.options.specCompliant && last.kind === ts.SyntaxKind.BindingElement && last.dotDotDotToken !== undefined, + ); + } + + private checkObjectLiteralExpression(node: ts.ObjectLiteralExpression) { + const {properties} = node; + if (properties.length === 0) { return; } - const token = getChildOfKind(node, closeTokenKind, this.sourceFile); - if (token !== undefined) { - return this.checkComma(list.hasTrailingComma, list, token.end, optionKey); + return this.checkComma( + properties.hasTrailingComma, + properties, + node.end, + "objects", + /*never*/ this.options.specCompliant && + properties[properties.length - 1].kind === ts.SyntaxKind.SpreadAssignment && + isReassignmentTarget(node), + ); + } + + private checkArrayLiteralExpression(node: ts.ArrayLiteralExpression) { + const {elements} = node; + if (elements.length === 0) { + return; } + return this.checkComma( + elements.hasTrailingComma, + elements, + node.end, + "arrays", + /*never*/ this.options.specCompliant && + elements[elements.length - 1].kind === ts.SyntaxKind.SpreadElement && + isReassignmentTarget(node), + ); } private checkList(list: ts.NodeArray, closeElementPos: number, optionKey: OptionName) { @@ -218,11 +270,22 @@ class TrailingCommaWalker extends Lint.AbstractWalker { } /* Expects `list.length !== 0` */ - private checkComma(hasTrailingComma: boolean | undefined, list: ts.NodeArray, closeTokenPos: number, optionKey: OptionName) { - const options = isSameLine(this.sourceFile, list[list.length - 1].end, closeTokenPos) - ? this.options.singleline - : this.options.multiline; - const option = options[optionKey]; + private checkComma( + hasTrailingComma: boolean | undefined, + list: ts.NodeArray, + closeTokenPos: number, + optionKey: OptionName, + never?: boolean, + ) { + let option: OptionValue; + if (never) { + option = "never"; + } else { + const options = isSameLine(this.sourceFile, list[list.length - 1].end, closeTokenPos) + ? this.options.singleline + : this.options.multiline; + option = options[optionKey]; + } if (option === "always" && !hasTrailingComma) { this.addFailureAt(list.end, 0, Rule.FAILURE_STRING_ALWAYS, Lint.Replacement.appendText(list.end, ",")); diff --git a/test/rules/trailing-comma/multiline-always/test.ts.fix b/test/rules/trailing-comma/multiline-always/test.ts.fix index 38a6cf4013a..626f692b435 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.fix +++ b/test/rules/trailing-comma/multiline-always/test.ts.fix @@ -547,3 +547,23 @@ export { // don't crash new Foo + +let { + x, + ...y +} = {}; +let [ + x, + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +funciton foo( + x, + ...y +) {} diff --git a/test/rules/trailing-comma/multiline-always/test.ts.lint b/test/rules/trailing-comma/multiline-always/test.ts.lint index 77d4f47fee5..a3b8014ae7c 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.lint +++ b/test/rules/trailing-comma/multiline-always/test.ts.lint @@ -576,3 +576,28 @@ export { // don't crash new Foo + +let { + x, + ...y, + ~ [Unnecessary trailing comma] +} = {}; +let [ + x, + ...y, + ~ [Unnecessary trailing comma] +] = []; + +[ + ...y, + ~ [Unnecessary trailing comma] +] = []; +({ + ...y, + ~ [Unnecessary trailing comma] +} = {}) +funciton foo( + x, + ...y, + ~ [Unnecessary trailing comma] +) {} diff --git a/test/rules/trailing-comma/multiline-always/tslint.json b/test/rules/trailing-comma/multiline-always/tslint.json index b9d77c2ab7c..0b77b7daf23 100644 --- a/test/rules/trailing-comma/multiline-always/tslint.json +++ b/test/rules/trailing-comma/multiline-always/tslint.json @@ -1,5 +1,5 @@ { "rules": { - "trailing-comma": [true, {"multiline": "always"}] + "trailing-comma": [true, {"multiline": "always", "specCompliant": true}] } } From ff3692d167a676fb062f9a91930268e01e7a26a8 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 28 Aug 2017 18:16:33 +0200 Subject: [PATCH 2/5] Reduce code duplication --- src/rules/trailingCommaRule.ts | 157 ++++++++---------- .../multiline-always/test.ts.fix | 2 +- .../multiline-always/test.ts.lint | 12 +- 3 files changed, 75 insertions(+), 96 deletions(-) diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 70ffd848874..a994b88d8f2 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -23,7 +23,11 @@ import * as Lint from "../index"; type OptionValue = "always" | "never" | "ignore"; type OptionName = "arrays" | "exports" | "functions" | "imports" | "objects" | "typeLiterals"; type CustomOptionValue = Record; -type Options = Record<"multiline" | "singleline", CustomOptionValue> & {specCompliant: boolean}; +interface Options { + multiline: CustomOptionValue; + singleline: CustomOptionValue; + specCompliant: boolean; +} const defaultOptions: CustomOptionValue = fillOptions("ignore" as "ignore"); // tslint:disable-line no-unnecessary-type-assertion @@ -42,9 +46,9 @@ type OptionsJson = Partial { const cb = (node: ts.Node): void => { switch (node.kind) { case ts.SyntaxKind.ArrayLiteralExpression: - this.checkArrayLiteralExpression(node as ts.ArrayLiteralExpression); + this.checkList((node as ts.ArrayLiteralExpression).elements, node.end, "arrays", isArrayRest); break; case ts.SyntaxKind.ArrayBindingPattern: - this.checkBindingPattern(node as ts.BindingPattern, "arrays"); + this.checkList((node as ts.BindingPattern).elements, node.end, "arrays", isDestructuringRest); break; case ts.SyntaxKind.ObjectBindingPattern: - this.checkBindingPattern(node as ts.ObjectBindingPattern, "objects"); + this.checkList((node as ts.BindingPattern).elements, node.end, "objects", isDestructuringRest); break; case ts.SyntaxKind.NamedImports: - this.checkList((node as ts.NamedImports).elements, node.end, "imports"); + this.checkList((node as ts.NamedImports).elements, node.end, "imports", noRest); break; case ts.SyntaxKind.NamedExports: - this.checkList((node as ts.NamedExports).elements, node.end, "exports"); + this.checkList((node as ts.NamedExports).elements, node.end, "exports", noRest); break; case ts.SyntaxKind.ObjectLiteralExpression: - this.checkObjectLiteralExpression(node as ts.ObjectLiteralExpression); + this.checkList((node as ts.ObjectLiteralExpression).properties, node.end, "objects", isObjectRest); break; case ts.SyntaxKind.EnumDeclaration: - this.checkList((node as ts.EnumDeclaration).members, node.end, "objects"); + this.checkList((node as ts.EnumDeclaration).members, node.end, "objects", noRest); break; case ts.SyntaxKind.NewExpression: if ((node as ts.NewExpression).arguments === undefined) { @@ -159,7 +171,7 @@ class TrailingCommaWalker extends Lint.AbstractWalker { } // falls through case ts.SyntaxKind.CallExpression: - this.checkList((node as ts.CallExpression | ts.NewExpression).arguments!, node.end, "functions"); + this.checkList((node as ts.CallExpression | ts.NewExpression).arguments!, node.end, "functions", noRest); break; case ts.SyntaxKind.ArrowFunction: case ts.SyntaxKind.Constructor: @@ -172,7 +184,12 @@ class TrailingCommaWalker extends Lint.AbstractWalker { case ts.SyntaxKind.ConstructorType: case ts.SyntaxKind.FunctionType: case ts.SyntaxKind.CallSignature: - this.checkSignatureDeclaration(node as ts.SignatureDeclaration); + this.checkList( + (node as ts.SignatureDeclaration).parameters, + getChildOfKind(node, ts.SyntaxKind.CloseParenToken, this.sourceFile)!.end, + "functions", + isRestParameter, + ); break; case ts.SyntaxKind.TypeLiteral: this.checkTypeLiteral(node as ts.TypeLiteralNode); @@ -198,95 +215,37 @@ class TrailingCommaWalker extends Lint.AbstractWalker { } // The trailing comma is part of the last member and therefore not present as hasTrailingComma on the NodeArray const hasTrailingComma = sourceText[members.end - 1] === ","; - return this.checkComma(hasTrailingComma, members, node.end, "typeLiterals"); + return this.checkComma(hasTrailingComma, members, node.end, "typeLiterals", noRest); } - private checkSignatureDeclaration(node: ts.SignatureDeclaration) { - const {parameters} = node; - if (parameters.length === 0) { - return; - } - return this.checkComma( - parameters.hasTrailingComma, - parameters, - getChildOfKind(node, ts.SyntaxKind.CloseParenToken, this.sourceFile)!.end, - "functions", - /*never*/ this.options.specCompliant && parameters[parameters.length - 1].dotDotDotToken !== undefined, - ); - } - - private checkBindingPattern(node: ts.BindingPattern, optionKey: "arrays" | "objects") { - const {elements} = node; - if (elements.length === 0) { - return; - } - const last = elements[elements.length - 1]; - return this.checkComma( - elements.hasTrailingComma, - elements, - node.end, - optionKey, - this.options.specCompliant && last.kind === ts.SyntaxKind.BindingElement && last.dotDotDotToken !== undefined, - ); - } - - private checkObjectLiteralExpression(node: ts.ObjectLiteralExpression) { - const {properties} = node; - if (properties.length === 0) { - return; - } - return this.checkComma( - properties.hasTrailingComma, - properties, - node.end, - "objects", - /*never*/ this.options.specCompliant && - properties[properties.length - 1].kind === ts.SyntaxKind.SpreadAssignment && - isReassignmentTarget(node), - ); - } - - private checkArrayLiteralExpression(node: ts.ArrayLiteralExpression) { - const {elements} = node; - if (elements.length === 0) { - return; - } - return this.checkComma( - elements.hasTrailingComma, - elements, - node.end, - "arrays", - /*never*/ this.options.specCompliant && - elements[elements.length - 1].kind === ts.SyntaxKind.SpreadElement && - isReassignmentTarget(node), - ); - } - - private checkList(list: ts.NodeArray, closeElementPos: number, optionKey: OptionName) { + private checkList(list: ts.NodeArray, closeElementPos: number, optionKey: OptionName, isRest: (n: T) => boolean) { if (list.length === 0) { return; } - return this.checkComma(list.hasTrailingComma, list, closeElementPos, optionKey); + return this.checkComma(list.hasTrailingComma, list, closeElementPos, optionKey, isRest); } /* Expects `list.length !== 0` */ - private checkComma( + private checkComma( hasTrailingComma: boolean | undefined, - list: ts.NodeArray, + list: ts.NodeArray, closeTokenPos: number, optionKey: OptionName, - never?: boolean, + isRest: (node: T) => boolean, ) { - let option: OptionValue; - if (never) { - option = "never"; - } else { - const options = isSameLine(this.sourceFile, list[list.length - 1].end, closeTokenPos) - ? this.options.singleline - : this.options.multiline; - option = options[optionKey]; + const last = list[list.length - 1]; + if (this.options.specCompliant && isRest(last)) { + if (hasTrailingComma) { + this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_FORBIDDEN, Lint.Replacement.deleteText(list.end - 1, 1)); + } + return; } + const options = isSameLine(this.sourceFile, last.end, closeTokenPos) + ? this.options.singleline + : this.options.multiline; + const option = options[optionKey]; + if (option === "always" && !hasTrailingComma) { this.addFailureAt(list.end, 0, Rule.FAILURE_STRING_ALWAYS, Lint.Replacement.appendText(list.end, ",")); } else if (option === "never" && hasTrailingComma) { @@ -294,3 +253,23 @@ class TrailingCommaWalker extends Lint.AbstractWalker { } } } + +function isRestParameter(node: ts.ParameterDeclaration) { + return node.dotDotDotToken !== undefined; +} + +function isDestructuringRest(node: ts.ArrayBindingElement) { + return node.kind === ts.SyntaxKind.BindingElement && node.dotDotDotToken !== undefined; +} + +function isObjectRest(node: ts.ObjectLiteralElementLike) { + return node.kind === ts.SyntaxKind.SpreadAssignment && isReassignmentTarget(node.expression); +} + +function isArrayRest(node: ts.Expression) { + return node.kind === ts.SyntaxKind.SpreadElement && isReassignmentTarget(node); +} + +function noRest() { + return false; +} diff --git a/test/rules/trailing-comma/multiline-always/test.ts.fix b/test/rules/trailing-comma/multiline-always/test.ts.fix index 626f692b435..7cbaba024b0 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.fix +++ b/test/rules/trailing-comma/multiline-always/test.ts.fix @@ -563,7 +563,7 @@ let [ ({ ...y } = {}) -funciton foo( +function foo( x, ...y ) {} diff --git a/test/rules/trailing-comma/multiline-always/test.ts.lint b/test/rules/trailing-comma/multiline-always/test.ts.lint index a3b8014ae7c..0a9f0788d43 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.lint +++ b/test/rules/trailing-comma/multiline-always/test.ts.lint @@ -580,24 +580,24 @@ new Foo let { x, ...y, - ~ [Unnecessary trailing comma] + ~ [Forbidden trailing comma] } = {}; let [ x, ...y, - ~ [Unnecessary trailing comma] + ~ [Forbidden trailing comma] ] = []; [ ...y, - ~ [Unnecessary trailing comma] + ~ [Forbidden trailing comma] ] = []; ({ ...y, - ~ [Unnecessary trailing comma] + ~ [Forbidden trailing comma] } = {}) -funciton foo( +function foo( x, ...y, - ~ [Unnecessary trailing comma] + ~ [Forbidden trailing comma] ) {} From f18f22bfcd472c58eff8c71a39a959e00a349d84 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 28 Aug 2017 22:20:23 +0200 Subject: [PATCH 3/5] Add tests --- .../multiline-always/test.ts.fix | 44 +++++++++-- .../multiline-always/test.ts.lint | 48 +++++++++--- .../multiline-always/tslint.json | 2 +- .../multiline-never/test.ts.fix | 47 ++++++++++++ .../multiline-never/test.ts.lint | 52 +++++++++++++ .../singleline-always/test.ts.fix | 47 ++++++++++++ .../singleline-always/test.ts.lint | 52 +++++++++++++ .../singleline-never/test.ts.fix | 47 ++++++++++++ .../singleline-never/test.ts.lint | 52 +++++++++++++ .../trailing-comma/spec-compliant/test.ts.fix | 62 +++++++++++++++ .../spec-compliant/test.ts.lint | 76 +++++++++++++++++++ .../trailing-comma/spec-compliant/tslint.json | 12 +++ 12 files changed, 522 insertions(+), 19 deletions(-) create mode 100644 test/rules/trailing-comma/spec-compliant/test.ts.fix create mode 100644 test/rules/trailing-comma/spec-compliant/test.ts.lint create mode 100644 test/rules/trailing-comma/spec-compliant/tslint.json diff --git a/test/rules/trailing-comma/multiline-always/test.ts.fix b/test/rules/trailing-comma/multiline-always/test.ts.fix index 7cbaba024b0..83867505978 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.fix +++ b/test/rules/trailing-comma/multiline-always/test.ts.fix @@ -549,21 +549,49 @@ export { new Foo let { - x, - ...y + ...x, } = {}; let [ - x, - ...y + ...y, ] = []; [ - ...y + ...y, ] = []; ({ - ...y + ...y, } = {}) function foo( - x, - ...y + ...z, ) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x, +} = {}; +let [ + ...y, +] = []; + +[ + ...y, +] = []; +({ + ...y, +} = {}) +function foo( + ...z, +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/multiline-always/test.ts.lint b/test/rules/trailing-comma/multiline-always/test.ts.lint index 0a9f0788d43..54ae5b9be4e 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.lint +++ b/test/rules/trailing-comma/multiline-always/test.ts.lint @@ -578,26 +578,54 @@ export { new Foo let { - x, - ...y, - ~ [Forbidden trailing comma] + ...x, } = {}; let [ - x, ...y, - ~ [Forbidden trailing comma] ] = []; [ ...y, - ~ [Forbidden trailing comma] ] = []; ({ ...y, - ~ [Forbidden trailing comma] } = {}) function foo( - x, - ...y, - ~ [Forbidden trailing comma] + ...z, ) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x + ~nil [Missing trailing comma] +} = {}; +let [ + ...y + ~nil [Missing trailing comma] +] = []; + +[ + ...y + ~nil [Missing trailing comma] +] = []; +({ + ...y + ~nil [Missing trailing comma] +} = {}) +function foo( + ...z + ~nil [Missing trailing comma] +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/multiline-always/tslint.json b/test/rules/trailing-comma/multiline-always/tslint.json index 0b77b7daf23..b9d77c2ab7c 100644 --- a/test/rules/trailing-comma/multiline-always/tslint.json +++ b/test/rules/trailing-comma/multiline-always/tslint.json @@ -1,5 +1,5 @@ { "rules": { - "trailing-comma": [true, {"multiline": "always", "specCompliant": true}] + "trailing-comma": [true, {"multiline": "always"}] } } diff --git a/test/rules/trailing-comma/multiline-never/test.ts.fix b/test/rules/trailing-comma/multiline-never/test.ts.fix index bd92433c252..9847bac3e8d 100644 --- a/test/rules/trailing-comma/multiline-never/test.ts.fix +++ b/test/rules/trailing-comma/multiline-never/test.ts.fix @@ -496,3 +496,50 @@ const enum Foo { Baz = 2 } +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/multiline-never/test.ts.lint b/test/rules/trailing-comma/multiline-never/test.ts.lint index 910d09ebe9d..f3e0937fa2f 100644 --- a/test/rules/trailing-comma/multiline-never/test.ts.lint +++ b/test/rules/trailing-comma/multiline-never/test.ts.lint @@ -525,3 +525,55 @@ const enum Foo { ~ [Unnecessary trailing comma] } +let { + ...x, + ~ [Unnecessary trailing comma] +} = {}; +let [ + ...y, + ~ [Unnecessary trailing comma] +] = []; + +[ + ...y, + ~ [Unnecessary trailing comma] +] = []; +({ + ...y, + ~ [Unnecessary trailing comma] +} = {}) +function foo( + ...z, + ~ [Unnecessary trailing comma] +) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/singleline-always/test.ts.fix b/test/rules/trailing-comma/singleline-always/test.ts.fix index 2aa1e2ec29b..4bdc196a641 100644 --- a/test/rules/trailing-comma/singleline-always/test.ts.fix +++ b/test/rules/trailing-comma/singleline-always/test.ts.fix @@ -538,3 +538,50 @@ const enum Foo { Baz = 2, } +let { + ...x, +} = {}; +let [ + ...y, +] = []; + +[ + ...y, +] = []; +({ + ...y, +} = {}) +function foo( + ...z, +) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} diff --git a/test/rules/trailing-comma/singleline-always/test.ts.lint b/test/rules/trailing-comma/singleline-always/test.ts.lint index e2fb321bdda..949b532c9a2 100644 --- a/test/rules/trailing-comma/singleline-always/test.ts.lint +++ b/test/rules/trailing-comma/singleline-always/test.ts.lint @@ -563,3 +563,55 @@ const enum Foo { Baz = 2, } +let { + ...x, +} = {}; +let [ + ...y, +] = []; + +[ + ...y, +] = []; +({ + ...y, +} = {}) +function foo( + ...z, +) {} + + +let {...x,} = {}; +let [...y,] = []; +[...y,] = []; +({...y,} = {}) +function foo(...z,) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; + ~nil [Missing trailing comma] +let [...y] = []; + ~nil [Missing trailing comma] +[...y] = []; + ~nil [Missing trailing comma] +({...y} = {}) + ~nil [Missing trailing comma] +function foo(...z) {} + ~nil [Missing trailing comma] diff --git a/test/rules/trailing-comma/singleline-never/test.ts.fix b/test/rules/trailing-comma/singleline-never/test.ts.fix index 5198ed5e6d3..de3ae58c3b1 100644 --- a/test/rules/trailing-comma/singleline-never/test.ts.fix +++ b/test/rules/trailing-comma/singleline-never/test.ts.fix @@ -498,3 +498,50 @@ const enum Foo { Baz = 2, } +let { + ...x, +} = {}; +let [ + ...y, +] = []; + +[ + ...y, +] = []; +({ + ...y, +} = {}) +function foo( + ...z, +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/singleline-never/test.ts.lint b/test/rules/trailing-comma/singleline-never/test.ts.lint index e1d597707fb..ba133a56149 100644 --- a/test/rules/trailing-comma/singleline-never/test.ts.lint +++ b/test/rules/trailing-comma/singleline-never/test.ts.lint @@ -521,3 +521,55 @@ const enum Foo { Baz = 2, } +let { + ...x, +} = {}; +let [ + ...y, +] = []; + +[ + ...y, +] = []; +({ + ...y, +} = {}) +function foo( + ...z, +) {} + + +let {...x,} = {}; + ~ [Unnecessary trailing comma] +let [...y,] = []; + ~ [Unnecessary trailing comma] +[...y,] = []; + ~ [Unnecessary trailing comma] +({...y,} = {}) + ~ [Unnecessary trailing comma] +function foo(...z,) {} + ~ [Unnecessary trailing comma] + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} diff --git a/test/rules/trailing-comma/spec-compliant/test.ts.fix b/test/rules/trailing-comma/spec-compliant/test.ts.fix new file mode 100644 index 00000000000..a3564eb60fe --- /dev/null +++ b/test/rules/trailing-comma/spec-compliant/test.ts.fix @@ -0,0 +1,62 @@ +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} + + +[...y] = [...y,]; +({ + ...x +} = { + ...x, +}); +({ + ...x +} = { + ...x, + y, +}) +foo(...y,); diff --git a/test/rules/trailing-comma/spec-compliant/test.ts.lint b/test/rules/trailing-comma/spec-compliant/test.ts.lint new file mode 100644 index 00000000000..5691dfa06ac --- /dev/null +++ b/test/rules/trailing-comma/spec-compliant/test.ts.lint @@ -0,0 +1,76 @@ +let { + ...x, + ~ [Forbidden trailing comma] +} = {}; +let [ + ...y, + ~ [Forbidden trailing comma] +] = []; + +[ + ...y, + ~ [Forbidden trailing comma] +] = []; +({ + ...y, + ~ [Forbidden trailing comma] +} = {}) +function foo( + ...z, + ~ [Forbidden trailing comma] +) {} + + +let {...x,} = {}; + ~ [Forbidden trailing comma] +let [...y,] = []; + ~ [Forbidden trailing comma] +[...y,] = []; + ~ [Forbidden trailing comma] +({...y,} = {}) + ~ [Forbidden trailing comma] +function foo(...z,) {} + ~ [Forbidden trailing comma] + +let { + ...x +} = {}; +let [ + ...y +] = []; + +[ + ...y +] = []; +({ + ...y +} = {}) +function foo( + ...z +) {} + + +let {...x} = {}; +let [...y] = []; +[...y] = []; +({...y} = {}) +function foo(...z) {} + + +[...y] = [...y]; + ~nil [Missing trailing comma] +({ + ...x +} = { + ...x + ~nil [Missing trailing comma] +}); +({ + ...x +} = { + ...x, + y + ~nil [Missing trailing comma] +}) +foo(...y); + ~nil [Missing trailing comma] diff --git a/test/rules/trailing-comma/spec-compliant/tslint.json b/test/rules/trailing-comma/spec-compliant/tslint.json new file mode 100644 index 00000000000..c1640763527 --- /dev/null +++ b/test/rules/trailing-comma/spec-compliant/tslint.json @@ -0,0 +1,12 @@ +{ + "rules": { + "trailing-comma": [ + true, + { + "multiline": "always", + "singleline": "always", + "specCompliant": true + } + ] + } +} From 8f93a97d3cbd11e3e6daed7686fc1e8845770ddf Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Tue, 12 Sep 2017 21:55:20 +0200 Subject: [PATCH 4/5] add tests --- test/rules/trailing-comma/spec-compliant/test.ts.fix | 4 ++++ test/rules/trailing-comma/spec-compliant/test.ts.lint | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/test/rules/trailing-comma/spec-compliant/test.ts.fix b/test/rules/trailing-comma/spec-compliant/test.ts.fix index a3564eb60fe..73c83825bb4 100644 --- a/test/rules/trailing-comma/spec-compliant/test.ts.fix +++ b/test/rules/trailing-comma/spec-compliant/test.ts.fix @@ -60,3 +60,7 @@ function foo(...z) {} y, }) foo(...y,); + +function combined([a, ...arr], {b, ...obj}, regular,) {} + +function combined([a, ...arr], {b, ...obj}, regular,) {} diff --git a/test/rules/trailing-comma/spec-compliant/test.ts.lint b/test/rules/trailing-comma/spec-compliant/test.ts.lint index 5691dfa06ac..a2b896d2409 100644 --- a/test/rules/trailing-comma/spec-compliant/test.ts.lint +++ b/test/rules/trailing-comma/spec-compliant/test.ts.lint @@ -74,3 +74,10 @@ function foo(...z) {} }) foo(...y); ~nil [Missing trailing comma] + +function combined([a, ...arr], {b, ...obj}, regular) {} + ~nil [Missing trailing comma] + +function combined([a, ...arr,], {b, ...obj,}, regular,) {} + ~ [Forbidden trailing comma] + ~ [Forbidden trailing comma] From 407441c089e1fe90facdec5ebdcdfd425cf76458 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Wed, 13 Sep 2017 22:11:42 +0200 Subject: [PATCH 5/5] Rename to esSpecCompliant --- src/rules/trailingCommaRule.ts | 10 +++++----- test/rules/trailing-comma/spec-compliant/tslint.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index a994b88d8f2..ffc67b07dfc 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -42,9 +42,9 @@ function fillOptions(value: T): Record { }; } -type OptionsJson = Partial | OptionValue> & {specCompliant: boolean}>; +type OptionsJson = Partial | OptionValue> & {esSpecCompliant: boolean}>; function normalizeOptions(options: OptionsJson): Options { - return { multiline: normalize(options.multiline), singleline: normalize(options.singleline), specCompliant: !!options.specCompliant}; + return { multiline: normalize(options.multiline), singleline: normalize(options.singleline), specCompliant: !!options.esSpecCompliant}; } function normalize(value: OptionsJson["multiline"]): CustomOptionValue { @@ -94,7 +94,7 @@ export class Rule extends Lint.Rules.AbstractRule { and function parameters. To align this rule with the ECMAScript specification that is implemented in modern JavaScript VMs, - there is a third option \`specCompliant\`. Set this option to \`true\` to disallow trailing comma on + there is a third option \`esSpecCompliant\`. Set this option to \`true\` to disallow trailing comma on object and array rest and rest parameters. `, options: { @@ -102,7 +102,7 @@ export class Rule extends Lint.Rules.AbstractRule { properties: { multiline: metadataOptionShape, singleline: metadataOptionShape, - specCompliant: {type: "boolean"}, + esSpecCompliant: {type: "boolean"}, }, additionalProperties: false, }, @@ -117,7 +117,7 @@ export class Rule extends Lint.Rules.AbstractRule { functions: "never", typeLiterals: "ignore", }, - specCompliant: true, + esSpecCompliant: true, }, ], ], diff --git a/test/rules/trailing-comma/spec-compliant/tslint.json b/test/rules/trailing-comma/spec-compliant/tslint.json index c1640763527..3005eb3add4 100644 --- a/test/rules/trailing-comma/spec-compliant/tslint.json +++ b/test/rules/trailing-comma/spec-compliant/tslint.json @@ -5,7 +5,7 @@ { "multiline": "always", "singleline": "always", - "specCompliant": true + "esSpecCompliant": true } ] }