1 | /**
|
2 | * @fileoverview Rule to require object keys to be sorted
|
3 | * @author Toru Nagashima
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | const astUtils = require("../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 | *
|
27 | * @param {ASTNode} node - The `Property` node to get.
|
28 | * @returns {string|null} The property name or null.
|
29 | * @private
|
30 | */
|
31 | function getPropertyName(node) {
|
32 | return astUtils.getStaticPropertyName(node) || node.key.name || null;
|
33 | }
|
34 |
|
35 | /**
|
36 | * Functions which check that the given 2 names are in specific order.
|
37 | *
|
38 | * Postfix `I` is meant insensitive.
|
39 | * Postfix `N` is meant natual.
|
40 | *
|
41 | * @private
|
42 | */
|
43 | const isValidOrders = {
|
44 | asc(a, b) {
|
45 | return a <= b;
|
46 | },
|
47 | ascI(a, b) {
|
48 | return a.toLowerCase() <= b.toLowerCase();
|
49 | },
|
50 | ascN(a, b) {
|
51 | return naturalCompare(a, b) <= 0;
|
52 | },
|
53 | ascIN(a, b) {
|
54 | return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0;
|
55 | },
|
56 | desc(a, b) {
|
57 | return isValidOrders.asc(b, a);
|
58 | },
|
59 | descI(a, b) {
|
60 | return isValidOrders.ascI(b, a);
|
61 | },
|
62 | descN(a, b) {
|
63 | return isValidOrders.ascN(b, a);
|
64 | },
|
65 | descIN(a, b) {
|
66 | return isValidOrders.ascIN(b, a);
|
67 | }
|
68 | };
|
69 |
|
70 | //------------------------------------------------------------------------------
|
71 | // Rule Definition
|
72 | //------------------------------------------------------------------------------
|
73 |
|
74 | module.exports = {
|
75 | meta: {
|
76 | docs: {
|
77 | description: "require object keys to be sorted",
|
78 | category: "Stylistic Issues",
|
79 | recommended: false,
|
80 | url: "https://eslint.org/docs/rules/sort-keys"
|
81 | },
|
82 | schema: [
|
83 | {
|
84 | enum: ["asc", "desc"]
|
85 | },
|
86 | {
|
87 | type: "object",
|
88 | properties: {
|
89 | caseSensitive: {
|
90 | type: "boolean"
|
91 | },
|
92 | natural: {
|
93 | type: "boolean"
|
94 | }
|
95 | },
|
96 | additionalProperties: false
|
97 | }
|
98 | ]
|
99 | },
|
100 |
|
101 | create(context) {
|
102 |
|
103 | // Parse options.
|
104 | const order = context.options[0] || "asc";
|
105 | const options = context.options[1];
|
106 | const insensitive = (options && options.caseSensitive) === false;
|
107 | const natual = Boolean(options && options.natural);
|
108 | const isValidOrder = isValidOrders[
|
109 | order + (insensitive ? "I" : "") + (natual ? "N" : "")
|
110 | ];
|
111 |
|
112 | // The stack to save the previous property's name for each object literals.
|
113 | let stack = null;
|
114 |
|
115 | return {
|
116 | ObjectExpression() {
|
117 | stack = {
|
118 | upper: stack,
|
119 | prevName: null
|
120 | };
|
121 | },
|
122 |
|
123 | "ObjectExpression:exit"() {
|
124 | stack = stack.upper;
|
125 | },
|
126 |
|
127 | Property(node) {
|
128 | if (node.parent.type === "ObjectPattern") {
|
129 | return;
|
130 | }
|
131 |
|
132 | const prevName = stack.prevName;
|
133 | const thisName = getPropertyName(node);
|
134 |
|
135 | stack.prevName = thisName || prevName;
|
136 |
|
137 | if (!prevName || !thisName) {
|
138 | return;
|
139 | }
|
140 |
|
141 | if (!isValidOrder(prevName, thisName)) {
|
142 | context.report({
|
143 | node,
|
144 | loc: node.key.loc,
|
145 | message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
|
146 | data: {
|
147 | thisName,
|
148 | prevName,
|
149 | order,
|
150 | insensitive: insensitive ? "insensitive " : "",
|
151 | natual: natual ? "natural " : ""
|
152 | }
|
153 | });
|
154 | }
|
155 | }
|
156 | };
|
157 | }
|
158 | };
|