4 * Copyright 2013 Palantir Technologies, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 Object.defineProperty(exports, "__esModule", { value: true });
19 var tslib_1 = require("tslib");
20 var tsutils_1 = require("tsutils");
21 var ts = require("typescript");
22 var Lint = require("../index");
23 var defaultOptions = fillOptions("ignore");
24 function fillOptions(value) {
34 function normalizeOptions(options) {
36 multiline: normalize(options.multiline),
37 singleline: normalize(options.singleline),
38 specCompliant: !!options.esSpecCompliant,
41 function normalize(value) {
42 return typeof value === "string" ? fillOptions(value) : tslib_1.__assign({}, defaultOptions, value);
44 /* tslint:disable:object-literal-sort-keys */
45 var metadataOptionShape = {
49 enum: ["always", "never"],
53 properties: fillOptions({
55 enum: ["always", "never", "ignore"],
60 /* tslint:enable:object-literal-sort-keys */
61 var Rule = /** @class */ (function (_super) {
62 tslib_1.__extends(Rule, _super);
64 return _super !== null && _super.apply(this, arguments) || this;
66 Rule.prototype.apply = function (sourceFile) {
67 var options = normalizeOptions(this.ruleArguments[0]);
68 return this.applyWithWalker(new TrailingCommaWalker(sourceFile, this.ruleName, options));
70 Rule.prototype.isEnabled = function () {
71 return _super.prototype.isEnabled.call(this) && this.ruleArguments.length !== 0;
73 /* tslint:disable:object-literal-sort-keys */
75 ruleName: "trailing-comma",
76 description: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings,\n named imports and exports and function parameters."], ["\n Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings,\n named imports and exports and function parameters."]))),
78 optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n One argument which is an object with the keys `multiline` and `singleline`.\n Both can be set to a string (`\"always\"` or `\"never\"`) or an object.\n\n The object can contain any of the following keys: `\"arrays\"`, `\"objects\"`, `\"functions\"`,\n `\"imports\"`, `\"exports\"`, and `\"typeLiterals\"`; each key can have one of the following\n values: `\"always\"`, `\"never\"`, and `\"ignore\"`. Any missing keys will default to `\"ignore\"`.\n\n * `\"multiline\"` checks multi-line object literals.\n * `\"singleline\"` checks single-line object literals.\n\n An array is considered \"multiline\" if its closing bracket is on a line\n after the last array element. The same general logic is followed for\n object literals, function typings, named import statements\n and function parameters.\n\n To align this rule with the ECMAScript specification that is implemented in modern JavaScript VMs,\n there is a third option `esSpecCompliant`. Set this option to `true` to disallow trailing comma on\n object and array rest and rest parameters.\n "], ["\n One argument which is an object with the keys \\`multiline\\` and \\`singleline\\`.\n Both can be set to a string (\\`\"always\"\\` or \\`\"never\"\\`) or an object.\n\n The object can contain any of the following keys: \\`\"arrays\"\\`, \\`\"objects\"\\`, \\`\"functions\"\\`,\n \\`\"imports\"\\`, \\`\"exports\"\\`, and \\`\"typeLiterals\"\\`; each key can have one of the following\n values: \\`\"always\"\\`, \\`\"never\"\\`, and \\`\"ignore\"\\`. Any missing keys will default to \\`\"ignore\"\\`.\n\n * \\`\"multiline\"\\` checks multi-line object literals.\n * \\`\"singleline\"\\` checks single-line object literals.\n\n An array is considered \"multiline\" if its closing bracket is on a line\n after the last array element. The same general logic is followed for\n object literals, function typings, named import statements\n and function parameters.\n\n To align this rule with the ECMAScript specification that is implemented in modern JavaScript VMs,\n there is a third option \\`esSpecCompliant\\`. Set this option to \\`true\\` to disallow trailing comma on\n object and array rest and rest parameters.\n "]))),
82 multiline: metadataOptionShape,
83 singleline: metadataOptionShape,
84 esSpecCompliant: { type: "boolean" },
86 additionalProperties: false,
89 [true, { multiline: "always", singleline: "never" }],
97 typeLiterals: "ignore",
99 esSpecCompliant: true,
104 typescriptOnly: false,
106 /* tslint:enable:object-literal-sort-keys */
107 Rule.FAILURE_STRING_NEVER = "Unnecessary trailing comma";
108 Rule.FAILURE_STRING_FORBIDDEN = "Forbidden trailing comma";
109 Rule.FAILURE_STRING_ALWAYS = "Missing trailing comma";
111 }(Lint.Rules.AbstractRule));
113 var TrailingCommaWalker = /** @class */ (function (_super) {
114 tslib_1.__extends(TrailingCommaWalker, _super);
115 function TrailingCommaWalker() {
116 return _super !== null && _super.apply(this, arguments) || this;
118 TrailingCommaWalker.prototype.walk = function (sourceFile) {
120 var cb = function (node) {
122 case ts.SyntaxKind.ArrayLiteralExpression:
123 _this.checkList(node.elements, node.end, "arrays", isArrayRest);
125 case ts.SyntaxKind.ArrayBindingPattern:
126 _this.checkList(node.elements, node.end, "arrays", isDestructuringRest);
128 case ts.SyntaxKind.ObjectBindingPattern:
129 _this.checkList(node.elements, node.end, "objects", isDestructuringRest);
131 case ts.SyntaxKind.NamedImports:
132 _this.checkList(node.elements, node.end, "imports", noRest);
134 case ts.SyntaxKind.NamedExports:
135 _this.checkList(node.elements, node.end, "exports", noRest);
137 case ts.SyntaxKind.ObjectLiteralExpression:
138 _this.checkList(node.properties, node.end, "objects", isObjectRest);
140 case ts.SyntaxKind.EnumDeclaration:
141 _this.checkList(node.members, node.end, "objects", noRest);
143 case ts.SyntaxKind.NewExpression:
144 if (node.arguments === undefined) {
148 case ts.SyntaxKind.CallExpression:
149 _this.checkList(node.arguments, node.end, "functions", noRest);
151 case ts.SyntaxKind.ArrowFunction:
152 // don't check arrow functions without parens around the parameter
153 if (tsutils_1.getChildOfKind(node, ts.SyntaxKind.OpenParenToken, _this.sourceFile) ===
158 case ts.SyntaxKind.Constructor:
159 case ts.SyntaxKind.FunctionDeclaration:
160 case ts.SyntaxKind.FunctionExpression:
161 case ts.SyntaxKind.MethodDeclaration:
162 case ts.SyntaxKind.SetAccessor:
163 case ts.SyntaxKind.MethodSignature:
164 case ts.SyntaxKind.ConstructSignature:
165 case ts.SyntaxKind.ConstructorType:
166 case ts.SyntaxKind.FunctionType:
167 case ts.SyntaxKind.CallSignature:
168 var closingParen = tsutils_1.getChildOfKind(node, ts.SyntaxKind.CloseParenToken, _this.sourceFile);
169 if (closingParen !== undefined) {
170 _this.checkList(node.parameters, closingParen.end, "functions", isRestParameter);
173 case ts.SyntaxKind.TypeLiteral:
174 _this.checkTypeLiteral(node);
178 return ts.forEachChild(node, cb);
180 return ts.forEachChild(sourceFile, cb);
182 TrailingCommaWalker.prototype.checkTypeLiteral = function (node) {
183 var members = node.members;
184 if (members.length === 0) {
187 var sourceText = this.sourceFile.text;
188 for (var _i = 0, members_1 = members; _i < members_1.length; _i++) {
189 var member = members_1[_i];
190 // PropertySignature in TypeLiteral can end with semicolon or comma. If one ends with a semicolon don't check for trailing comma
191 if (sourceText[member.end - 1] === ";") {
195 // The trailing comma is part of the last member and therefore not present as hasTrailingComma on the NodeArray
196 var hasTrailingComma = sourceText[members.end - 1] === ",";
197 return this.checkComma(hasTrailingComma, members, node.end, "typeLiterals", noRest);
199 TrailingCommaWalker.prototype.checkList = function (list, closeElementPos, optionKey, isRest) {
200 if (list.length === 0) {
203 return this.checkComma(list.hasTrailingComma, list, closeElementPos, optionKey, isRest);
205 /* Expects `list.length !== 0` */
206 TrailingCommaWalker.prototype.checkComma = function (hasTrailingComma, list, closeTokenPos, optionKey, isRest) {
207 var last = list[list.length - 1];
208 if (this.options.specCompliant && isRest(last)) {
209 if (hasTrailingComma) {
210 this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_FORBIDDEN, Lint.Replacement.deleteText(list.end - 1, 1));
214 var options = tsutils_1.isSameLine(this.sourceFile, last.end, closeTokenPos)
215 ? this.options.singleline
216 : this.options.multiline;
217 var option = options[optionKey];
218 if (option === "always" && !hasTrailingComma) {
219 this.addFailureAt(list.end, 0, Rule.FAILURE_STRING_ALWAYS, Lint.Replacement.appendText(list.end, ","));
221 else if (option === "never" && hasTrailingComma) {
222 this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_NEVER, Lint.Replacement.deleteText(list.end - 1, 1));
225 return TrailingCommaWalker;
226 }(Lint.AbstractWalker));
227 function isRestParameter(node) {
228 return node.dotDotDotToken !== undefined;
230 function isDestructuringRest(node) {
231 return node.kind === ts.SyntaxKind.BindingElement && node.dotDotDotToken !== undefined;
233 function isObjectRest(node) {
234 return node.kind === ts.SyntaxKind.SpreadAssignment && tsutils_1.isReassignmentTarget(node.expression);
236 function isArrayRest(node) {
237 return node.kind === ts.SyntaxKind.SpreadElement && tsutils_1.isReassignmentTarget(node);
242 var templateObject_1, templateObject_2;