UNPKG

5.43 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to require object keys to be sorted
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils"),
13 naturalCompare = require("natural-compare");
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19/**
20 * Gets the property name of the given `Property` node.
21 *
22 * - If the property's key is an `Identifier` node, this returns the key's name
23 * whether it's a computed property or not.
24 * - If the property has a static name, this returns the static name.
25 * - Otherwise, this returns null.
26 * @param {ASTNode} node The `Property` node to get.
27 * @returns {string|null} The property name or null.
28 * @private
29 */
30function getPropertyName(node) {
31 const staticName = astUtils.getStaticPropertyName(node);
32
33 if (staticName !== null) {
34 return staticName;
35 }
36
37 return node.key.name || null;
38}
39
40/**
41 * Functions which check that the given 2 names are in specific order.
42 *
43 * Postfix `I` is meant insensitive.
44 * Postfix `N` is meant natural.
45 * @private
46 */
47const isValidOrders = {
48 asc(a, b) {
49 return a <= b;
50 },
51 ascI(a, b) {
52 return a.toLowerCase() <= b.toLowerCase();
53 },
54 ascN(a, b) {
55 return naturalCompare(a, b) <= 0;
56 },
57 ascIN(a, b) {
58 return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0;
59 },
60 desc(a, b) {
61 return isValidOrders.asc(b, a);
62 },
63 descI(a, b) {
64 return isValidOrders.ascI(b, a);
65 },
66 descN(a, b) {
67 return isValidOrders.ascN(b, a);
68 },
69 descIN(a, b) {
70 return isValidOrders.ascIN(b, a);
71 }
72};
73
74//------------------------------------------------------------------------------
75// Rule Definition
76//------------------------------------------------------------------------------
77
78module.exports = {
79 meta: {
80 type: "suggestion",
81
82 docs: {
83 description: "require object keys to be sorted",
84 category: "Stylistic Issues",
85 recommended: false,
86 url: "https://eslint.org/docs/rules/sort-keys"
87 },
88
89 schema: [
90 {
91 enum: ["asc", "desc"]
92 },
93 {
94 type: "object",
95 properties: {
96 caseSensitive: {
97 type: "boolean",
98 default: true
99 },
100 natural: {
101 type: "boolean",
102 default: false
103 },
104 minKeys: {
105 type: "integer",
106 minimum: 2,
107 default: 2
108 }
109 },
110 additionalProperties: false
111 }
112 ],
113
114 messages: {
115 sortKeys: "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'."
116 }
117 },
118
119 create(context) {
120
121 // Parse options.
122 const order = context.options[0] || "asc";
123 const options = context.options[1];
124 const insensitive = options && options.caseSensitive === false;
125 const natural = options && options.natural;
126 const minKeys = options && options.minKeys;
127 const isValidOrder = isValidOrders[
128 order + (insensitive ? "I" : "") + (natural ? "N" : "")
129 ];
130
131 // The stack to save the previous property's name for each object literals.
132 let stack = null;
133
134 return {
135 ObjectExpression(node) {
136 stack = {
137 upper: stack,
138 prevName: null,
139 numKeys: node.properties.length
140 };
141 },
142
143 "ObjectExpression:exit"() {
144 stack = stack.upper;
145 },
146
147 SpreadElement(node) {
148 if (node.parent.type === "ObjectExpression") {
149 stack.prevName = null;
150 }
151 },
152
153 Property(node) {
154 if (node.parent.type === "ObjectPattern") {
155 return;
156 }
157
158 const prevName = stack.prevName;
159 const numKeys = stack.numKeys;
160 const thisName = getPropertyName(node);
161
162 if (thisName !== null) {
163 stack.prevName = thisName;
164 }
165
166 if (prevName === null || thisName === null || numKeys < minKeys) {
167 return;
168 }
169
170 if (!isValidOrder(prevName, thisName)) {
171 context.report({
172 node,
173 loc: node.key.loc,
174 messageId: "sortKeys",
175 data: {
176 thisName,
177 prevName,
178 order,
179 insensitive: insensitive ? "insensitive " : "",
180 natural: natural ? "natural " : ""
181 }
182 });
183 }
184 }
185 };
186 }
187};