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 error_1 = require("../error");
23 var Lint = require("../index");
24 var objectLiteralSortKeys_examples_1 = require("./code-examples/objectLiteralSortKeys.examples");
25 var OPTION_IGNORE_BLANK_LINES = "ignore-blank-lines";
26 var OPTION_IGNORE_CASE = "ignore-case";
27 var OPTION_LOCALE_COMPARE = "locale-compare";
28 var OPTION_MATCH_DECLARATION_ORDER = "match-declaration-order";
29 var OPTION_MATCH_DECLARATION_ORDER_ONLY = "match-declaration-order-only";
30 var OPTION_SHORTHAND_FIRST = "shorthand-first";
31 var Rule = /** @class */ (function (_super) {
32 tslib_1.__extends(Rule, _super);
34 return _super !== null && _super.apply(this, arguments) || this;
36 /* tslint:enable:object-literal-sort-keys */
37 Rule.FAILURE_STRING_ALPHABETICAL = function (name) {
38 return "The key '" + name + "' is not sorted alphabetically";
40 Rule.FAILURE_STRING_USE_DECLARATION_ORDER = function (propName, typeName) {
41 var type = typeName === undefined ? "its type declaration" : "'" + typeName + "'";
42 return "The key '" + propName + "' is not in the same order as it is in " + type + ".";
44 Rule.FAILURE_STRING_SHORTHAND_FIRST = function (name) {
45 return "The shorthand property '" + name + "' should appear before normal properties";
47 Rule.prototype.apply = function (sourceFile) {
48 var options = parseOptions(this.ruleArguments);
49 if (options.matchDeclarationOrder || options.matchDeclarationOrderOnly) {
50 error_1.showWarningOnce(Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n ", " needs type info to use \"", "\" or\n \"", "\".\n See https://palantir.github.io/tslint/usage/type-checking/ for documentation on\n how to enable this feature.\n "], ["\n ", " needs type info to use \"", "\" or\n \"", "\".\n See https://palantir.github.io/tslint/usage/type-checking/ for documentation on\n how to enable this feature.\n "])), this.ruleName, OPTION_MATCH_DECLARATION_ORDER, OPTION_MATCH_DECLARATION_ORDER_ONLY));
53 return this.applyWithFunction(sourceFile, walk, options);
55 Rule.prototype.applyWithProgram = function (sourceFile, program) {
56 var options = parseOptions(this.ruleArguments);
57 if (options.matchDeclarationOrder && options.matchDeclarationOrderOnly) {
58 error_1.showWarningOnce("\"" + OPTION_MATCH_DECLARATION_ORDER + "\" will be ignored since " +
59 ("\"" + OPTION_MATCH_DECLARATION_ORDER_ONLY + "\" has been enabled for " + this.ruleName + "."));
62 return this.applyWithFunction(sourceFile, walk, parseOptions(this.ruleArguments), program.getTypeChecker());
64 /* tslint:disable:object-literal-sort-keys */
66 ruleName: "object-literal-sort-keys",
67 description: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n Checks ordering of keys in object literals.\n\n When using the default alphabetical ordering, additional blank lines may be used to group\n object properties together while keeping the elements within each group in alphabetical order.\n To opt out of this use ", " option.\n "], ["\n Checks ordering of keys in object literals.\n\n When using the default alphabetical ordering, additional blank lines may be used to group\n object properties together while keeping the elements within each group in alphabetical order.\n To opt out of this use ", " option.\n "])), OPTION_IGNORE_BLANK_LINES),
68 rationale: "Useful in preventing merge conflicts",
69 optionsDescription: Lint.Utils.dedent(templateObject_3 || (templateObject_3 = tslib_1.__makeTemplateObject(["\n By default, this rule checks that keys are in alphabetical order.\n The following may optionally be passed:\n\n * `", "` will enforce alphabetical ordering regardless of blank lines between each key-value pair.\n * `", "` will compare keys in a case insensitive way.\n * `", "` will compare keys using the expected sort order of special characters, such as accents.\n * `", "` will prefer to use the key ordering of the contextual type of the object literal, as in:\n\n ```\n interface I { foo: number; bar: number; }\n const obj: I = { foo: 1, bar: 2 };\n ```\n\n If a contextual type is not found, alphabetical ordering will be used instead.\n * \"", "\" exactly like \"", "\",\n but don't fall back to alphabetical if a contextual type is not found.\n\n Note: If both ", " and ", " options are present,\n ", " will take precedence and alphabetical fallback will not occur.\n\n * `", "` will enforce shorthand properties to appear first, as in:\n\n ```\n const obj = { a, c, b: true };\n ```\n "], ["\n By default, this rule checks that keys are in alphabetical order.\n The following may optionally be passed:\n\n * \\`", "\\` will enforce alphabetical ordering regardless of blank lines between each key-value pair.\n * \\`", "\\` will compare keys in a case insensitive way.\n * \\`", "\\` will compare keys using the expected sort order of special characters, such as accents.\n * \\`", "\\` will prefer to use the key ordering of the contextual type of the object literal, as in:\n\n \\`\\`\\`\n interface I { foo: number; bar: number; }\n const obj: I = { foo: 1, bar: 2 };\n \\`\\`\\`\n\n If a contextual type is not found, alphabetical ordering will be used instead.\n * \"", "\" exactly like \"", "\",\n but don't fall back to alphabetical if a contextual type is not found.\n\n Note: If both ", " and ", " options are present,\n ", " will take precedence and alphabetical fallback will not occur.\n\n * \\`", "\\` will enforce shorthand properties to appear first, as in:\n\n \\`\\`\\`\n const obj = { a, c, b: true };\n \\`\\`\\`\n "])), OPTION_IGNORE_BLANK_LINES, OPTION_IGNORE_CASE, OPTION_LOCALE_COMPARE, OPTION_MATCH_DECLARATION_ORDER, OPTION_MATCH_DECLARATION_ORDER_ONLY, OPTION_MATCH_DECLARATION_ORDER, OPTION_MATCH_DECLARATION_ORDER_ONLY, OPTION_MATCH_DECLARATION_ORDER, OPTION_MATCH_DECLARATION_ORDER_ONLY, OPTION_SHORTHAND_FIRST),
73 OPTION_IGNORE_BLANK_LINES,
75 OPTION_LOCALE_COMPARE,
76 OPTION_MATCH_DECLARATION_ORDER,
77 OPTION_MATCH_DECLARATION_ORDER_ONLY,
78 OPTION_SHORTHAND_FIRST,
85 OPTION_IGNORE_BLANK_LINES,
87 OPTION_LOCALE_COMPARE,
88 OPTION_MATCH_DECLARATION_ORDER,
89 OPTION_SHORTHAND_FIRST,
92 type: "maintainability",
93 typescriptOnly: false,
94 codeExamples: objectLiteralSortKeys_examples_1.codeExamples,
97 }(Lint.Rules.OptionallyTypedRule));
99 function parseOptions(ruleArguments) {
101 ignoreBlankLines: has(OPTION_IGNORE_BLANK_LINES),
102 ignoreCase: has(OPTION_IGNORE_CASE),
103 localeCompare: has(OPTION_LOCALE_COMPARE),
104 matchDeclarationOrder: has(OPTION_MATCH_DECLARATION_ORDER),
105 matchDeclarationOrderOnly: has(OPTION_MATCH_DECLARATION_ORDER_ONLY),
106 shorthandFirst: has(OPTION_SHORTHAND_FIRST),
109 return ruleArguments.indexOf(name) !== -1;
112 function walk(ctx, checker) {
113 var sourceFile = ctx.sourceFile, _a = ctx.options, ignoreBlankLines = _a.ignoreBlankLines, ignoreCase = _a.ignoreCase, localeCompare = _a.localeCompare, matchDeclarationOrder = _a.matchDeclarationOrder, matchDeclarationOrderOnly = _a.matchDeclarationOrderOnly, shorthandFirst = _a.shorthandFirst;
114 ts.forEachChild(sourceFile, function cb(node) {
115 if (tsutils_1.isObjectLiteralExpression(node) && node.properties.length > 1) {
118 ts.forEachChild(node, cb);
120 function check(node) {
121 if (matchDeclarationOrder || matchDeclarationOrderOnly) {
122 var type = getContextualType(node, checker);
123 // If type has an index signature, we can't check ordering.
124 // If type has call/construct signatures, it can't be satisfied by an object literal anyway.
125 if (type !== undefined &&
126 type.members.every(function (m) {
127 return m.kind === ts.SyntaxKind.PropertySignature ||
128 m.kind === ts.SyntaxKind.MethodSignature;
130 checkMatchesDeclarationOrder(node, type, type.members);
134 if (!matchDeclarationOrderOnly) {
135 checkAlphabetical(node);
138 function checkAlphabetical(node) {
139 if (tsutils_1.isSameLine(ctx.sourceFile, node.properties.pos, node.end)) {
143 var lastPropertyWasShorthand;
144 for (var _i = 0, _a = node.properties; _i < _a.length; _i++) {
145 var property = _a[_i];
146 switch (property.kind) {
147 case ts.SyntaxKind.SpreadAssignment:
148 lastKey = undefined; // reset at spread
149 lastPropertyWasShorthand = undefined; // reset at spread
151 case ts.SyntaxKind.ShorthandPropertyAssignment:
152 case ts.SyntaxKind.PropertyAssignment:
153 if (shorthandFirst) {
154 if (property.kind === ts.SyntaxKind.ShorthandPropertyAssignment) {
155 if (lastPropertyWasShorthand === false) {
156 ctx.addFailureAtNode(property.name, Rule.FAILURE_STRING_SHORTHAND_FIRST(property.name.text));
157 return; // only show warning on first out-of-order property
159 lastPropertyWasShorthand = true;
162 if (lastPropertyWasShorthand === true) {
163 lastKey = undefined; // reset on change from shorthand to normal
165 lastPropertyWasShorthand = false;
168 if (property.name.kind === ts.SyntaxKind.Identifier ||
169 property.name.kind === ts.SyntaxKind.StringLiteral) {
171 ? property.name.text.toLowerCase()
172 : property.name.text;
173 // comparison with undefined is expected
174 var keyOrderDescending = lastKey === undefined
177 ? lastKey.localeCompare(key) === 1
179 if (keyOrderDescending && ignoreBlankLines) {
180 ctx.addFailureAtNode(property.name, Rule.FAILURE_STRING_ALPHABETICAL(property.name.text));
183 if (keyOrderDescending && !hasBlankLineBefore(ctx.sourceFile, property)) {
184 ctx.addFailureAtNode(property.name, Rule.FAILURE_STRING_ALPHABETICAL(property.name.text));
185 return; // only show warning on first out-of-order property
192 function checkMatchesDeclarationOrder(_a, type, members) {
193 var properties = _a.properties;
195 outer: for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
196 var prop = properties_1[_i];
197 if (prop.kind === ts.SyntaxKind.SpreadAssignment) {
201 if (prop.name.kind === ts.SyntaxKind.ComputedPropertyName) {
204 var propName = prop.name.text;
205 for (; memberIndex !== members.length; memberIndex++) {
206 var memberName = members[memberIndex].name;
207 if (memberName.kind !== ts.SyntaxKind.ComputedPropertyName &&
208 propName === memberName.text) {
212 // This We didn't find the member we were looking for past the previous member,
213 // so it must have come before it and is therefore out of order.
214 ctx.addFailureAtNode(prop.name, Rule.FAILURE_STRING_USE_DECLARATION_ORDER(propName, getTypeName(type)));
215 // Don't bother with multiple errors.
220 function hasBlankLineBefore(sourceFile, element) {
221 var comments = ts.getLeadingCommentRanges(sourceFile.text, element.pos);
222 if (comments === undefined) {
223 comments = []; // it will be easier to work with an empty array down below...
225 var elementStart = comments.length > 0 ? comments[comments.length - 1].end : element.getFullStart();
226 // either the element itself, or one of its leading comments must have an extra new line before them
227 return (hasDoubleNewLine(sourceFile, elementStart) ||
228 comments.some(function (comment) {
229 var commentLine = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos).line;
230 var commentLineStartPosition = ts.getPositionOfLineAndCharacter(sourceFile, commentLine, 0);
231 return hasDoubleNewLine(sourceFile, commentLineStartPosition - 4);
234 function hasDoubleNewLine(sourceFile, position) {
235 return /(\r?\n){2}/.test(sourceFile.text.slice(position, position + 4));
237 function getTypeName(t) {
238 var parent = t.parent;
239 return t.kind === ts.SyntaxKind.InterfaceDeclaration
241 : tsutils_1.isTypeAliasDeclaration(parent)
245 function getContextualType(node, checker) {
246 var c = checker.getContextualType(node);
247 if (c === undefined || c.symbol === undefined) {
250 var declarations = c.symbol.declarations;
251 if (declarations === undefined || declarations.length !== 1) {
254 var decl = declarations[0];
255 return tsutils_1.isInterfaceDeclaration(decl) || tsutils_1.isTypeLiteralNode(decl) ? decl : undefined;
257 var templateObject_1, templateObject_2, templateObject_3;