"use strict"; /** * @license * Copyright 2013 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var tsutils_1 = require("tsutils"); var ts = require("typescript"); var Lint = require("../index"); var defaultOptions = fillOptions("ignore"); // tslint:disable-line no-unnecessary-type-assertion function fillOptions(value) { return { arrays: value, exports: value, functions: value, imports: value, objects: value, typeLiterals: value, }; } function normalizeOptions(options) { return { multiline: normalize(options.multiline), singleline: normalize(options.singleline) }; function normalize(value) { return typeof value === "string" ? fillOptions(value) : tslib_1.__assign({}, defaultOptions, value); } } /* tslint:disable:object-literal-sort-keys */ var metadataOptionShape = { anyOf: [ { type: "string", enum: ["always", "never"], }, { type: "object", properties: fillOptions({ type: "string", enum: ["always", "never", "ignore"], }), }, ], }; /* tslint:enable:object-literal-sort-keys */ var Rule = (function (_super) { tslib_1.__extends(Rule, _super); function Rule() { return _super !== null && _super.apply(this, arguments) || this; } Rule.prototype.apply = function (sourceFile) { var options = normalizeOptions(this.ruleArguments[0]); return this.applyWithWalker(new TrailingCommaWalker(sourceFile, this.ruleName, options)); }; Rule.prototype.isEnabled = function () { return _super.prototype.isEnabled.call(this) && this.ruleArguments.length !== 0; }; /* tslint:disable:object-literal-sort-keys */ Rule.metadata = { ruleName: "trailing-comma", description: (_a = ["\n Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings,\n named imports and exports and function parameters."], _a.raw = ["\n Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings,\n named imports and exports and function parameters."], Lint.Utils.dedent(_a)), hasFix: true, optionsDescription: (_b = ["\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."], _b.raw = ["\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."], Lint.Utils.dedent(_b)), options: { type: "object", properties: { multiline: metadataOptionShape, singleline: metadataOptionShape, }, additionalProperties: false, }, optionExamples: [ [true, { multiline: "always", singleline: "never" }], [ true, { multiline: { objects: "always", arrays: "always", functions: "never", typeLiterals: "ignore", }, }, ], ], type: "maintainability", typescriptOnly: false, }; /* tslint:enable:object-literal-sort-keys */ Rule.FAILURE_STRING_NEVER = "Unnecessary trailing comma"; Rule.FAILURE_STRING_ALWAYS = "Missing trailing comma"; return Rule; }(Lint.Rules.AbstractRule)); exports.Rule = Rule; var TrailingCommaWalker = (function (_super) { tslib_1.__extends(TrailingCommaWalker, _super); function TrailingCommaWalker() { return _super !== null && _super.apply(this, arguments) || this; } TrailingCommaWalker.prototype.walk = function (sourceFile) { var _this = this; var cb = function (node) { switch (node.kind) { case ts.SyntaxKind.ArrayLiteralExpression: case ts.SyntaxKind.ArrayBindingPattern: _this.checkList(node.elements, node.end, "arrays"); break; case ts.SyntaxKind.ObjectBindingPattern: _this.checkList(node.elements, node.end, "objects"); break; case ts.SyntaxKind.NamedImports: _this.checkList(node.elements, node.end, "imports"); break; case ts.SyntaxKind.NamedExports: _this.checkList(node.elements, node.end, "exports"); break; case ts.SyntaxKind.ObjectLiteralExpression: _this.checkList(node.properties, node.end, "objects"); break; case ts.SyntaxKind.EnumDeclaration: _this.checkList(node.members, node.end, "objects"); break; case ts.SyntaxKind.NewExpression: if (node.arguments === undefined) { break; } // falls through case ts.SyntaxKind.CallExpression: _this.checkList(node.arguments, node.end, "functions"); break; case ts.SyntaxKind.ArrowFunction: case ts.SyntaxKind.Constructor: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionExpression: case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.SetAccessor: case ts.SyntaxKind.MethodSignature: case ts.SyntaxKind.ConstructSignature: case ts.SyntaxKind.ConstructorType: case ts.SyntaxKind.FunctionType: case ts.SyntaxKind.CallSignature: _this.checkListWithEndToken(node, node.parameters, ts.SyntaxKind.CloseParenToken, "functions"); break; case ts.SyntaxKind.TypeLiteral: _this.checkTypeLiteral(node); break; default: } return ts.forEachChild(node, cb); }; return ts.forEachChild(sourceFile, cb); }; TrailingCommaWalker.prototype.checkTypeLiteral = function (node) { var members = node.members; if (members.length === 0) { return; } var sourceText = this.sourceFile.text; for (var _i = 0, members_1 = members; _i < members_1.length; _i++) { var member = members_1[_i]; // PropertySignature in TypeLiteral can end with semicolon or comma. If one ends with a semicolon don't check for trailing comma if (sourceText[member.end - 1] === ";") { return; } } // The trailing comma is part of the last member and therefore not present as hasTrailingComma on the NodeArray var hasTrailingComma = sourceText[members.end - 1] === ","; return this.checkComma(hasTrailingComma, members, node.end, "typeLiterals"); }; TrailingCommaWalker.prototype.checkListWithEndToken = function (node, list, closeTokenKind, optionKey) { if (list.length === 0) { return; } var token = tsutils_1.getChildOfKind(node, closeTokenKind, this.sourceFile); if (token !== undefined) { return this.checkComma(list.hasTrailingComma, list, token.end, optionKey); } }; TrailingCommaWalker.prototype.checkList = function (list, closeElementPos, optionKey) { if (list.length === 0) { return; } return this.checkComma(list.hasTrailingComma, list, closeElementPos, optionKey); }; /* Expects `list.length !== 0` */ TrailingCommaWalker.prototype.checkComma = function (hasTrailingComma, list, closeTokenPos, optionKey) { var options = tsutils_1.isSameLine(this.sourceFile, list[list.length - 1].end, closeTokenPos) ? this.options.singleline : this.options.multiline; var 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) { this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_NEVER, Lint.Replacement.deleteText(list.end - 1, 1)); } }; return TrailingCommaWalker; }(Lint.AbstractWalker)); var _a, _b;