UNPKG

20 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22exports.defaultOrder = void 0;
23const utils_1 = require("@typescript-eslint/utils");
24const util = __importStar(require("../util"));
25const neverConfig = {
26 type: 'string',
27 enum: ['never'],
28};
29const arrayConfig = (memberTypes) => ({
30 type: 'array',
31 items: {
32 enum: memberTypes,
33 },
34});
35const objectConfig = (memberTypes) => ({
36 type: 'object',
37 properties: {
38 memberTypes: {
39 oneOf: [arrayConfig(memberTypes), neverConfig],
40 },
41 order: {
42 type: 'string',
43 enum: ['alphabetically', 'alphabetically-case-insensitive', 'as-written'],
44 },
45 },
46 additionalProperties: false,
47});
48exports.defaultOrder = [
49 // Index signature
50 'signature',
51 'call-signature',
52 // Fields
53 'public-static-field',
54 'protected-static-field',
55 'private-static-field',
56 'public-decorated-field',
57 'protected-decorated-field',
58 'private-decorated-field',
59 'public-instance-field',
60 'protected-instance-field',
61 'private-instance-field',
62 'public-abstract-field',
63 'protected-abstract-field',
64 'private-abstract-field',
65 'public-field',
66 'protected-field',
67 'private-field',
68 'static-field',
69 'instance-field',
70 'abstract-field',
71 'decorated-field',
72 'field',
73 // Constructors
74 'public-constructor',
75 'protected-constructor',
76 'private-constructor',
77 'constructor',
78 // Getters
79 'public-static-get',
80 'protected-static-get',
81 'private-static-get',
82 'public-decorated-get',
83 'protected-decorated-get',
84 'private-decorated-get',
85 'public-instance-get',
86 'protected-instance-get',
87 'private-instance-get',
88 'public-abstract-get',
89 'protected-abstract-get',
90 'private-abstract-get',
91 'public-get',
92 'protected-get',
93 'private-get',
94 'static-get',
95 'instance-get',
96 'abstract-get',
97 'decorated-get',
98 'get',
99 // Setters
100 'public-static-set',
101 'protected-static-set',
102 'private-static-set',
103 'public-decorated-set',
104 'protected-decorated-set',
105 'private-decorated-set',
106 'public-instance-set',
107 'protected-instance-set',
108 'private-instance-set',
109 'public-abstract-set',
110 'protected-abstract-set',
111 'private-abstract-set',
112 'public-set',
113 'protected-set',
114 'private-set',
115 'static-set',
116 'instance-set',
117 'abstract-set',
118 'decorated-set',
119 'set',
120 // Methods
121 'public-static-method',
122 'protected-static-method',
123 'private-static-method',
124 'public-decorated-method',
125 'protected-decorated-method',
126 'private-decorated-method',
127 'public-instance-method',
128 'protected-instance-method',
129 'private-instance-method',
130 'public-abstract-method',
131 'protected-abstract-method',
132 'private-abstract-method',
133 'public-method',
134 'protected-method',
135 'private-method',
136 'static-method',
137 'instance-method',
138 'abstract-method',
139 'decorated-method',
140 'method',
141];
142const allMemberTypes = [
143 'signature',
144 'field',
145 'method',
146 'call-signature',
147 'constructor',
148 'get',
149 'set',
150].reduce((all, type) => {
151 all.push(type);
152 ['public', 'protected', 'private'].forEach(accessibility => {
153 if (type !== 'signature') {
154 all.push(`${accessibility}-${type}`); // e.g. `public-field`
155 }
156 // Only class instance fields, methods, get and set can have decorators attached to them
157 if (type === 'field' ||
158 type === 'method' ||
159 type === 'get' ||
160 type === 'set') {
161 const decoratedMemberType = `${accessibility}-decorated-${type}`;
162 const decoratedMemberTypeNoAccessibility = `decorated-${type}`;
163 if (!all.includes(decoratedMemberType)) {
164 all.push(decoratedMemberType);
165 }
166 if (!all.includes(decoratedMemberTypeNoAccessibility)) {
167 all.push(decoratedMemberTypeNoAccessibility);
168 }
169 }
170 if (type !== 'constructor' && type !== 'signature') {
171 // There is no `static-constructor` or `instance-constructor` or `abstract-constructor`
172 ['static', 'instance', 'abstract'].forEach(scope => {
173 if (!all.includes(`${scope}-${type}`)) {
174 all.push(`${scope}-${type}`);
175 }
176 all.push(`${accessibility}-${scope}-${type}`);
177 });
178 }
179 });
180 return all;
181}, []);
182const functionExpressions = [
183 utils_1.AST_NODE_TYPES.FunctionExpression,
184 utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
185];
186/**
187 * Gets the node type.
188 *
189 * @param node the node to be evaluated.
190 */
191function getNodeType(node) {
192 switch (node.type) {
193 case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
194 case utils_1.AST_NODE_TYPES.MethodDefinition:
195 return node.kind;
196 case utils_1.AST_NODE_TYPES.TSMethodSignature:
197 return 'method';
198 case utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration:
199 return 'call-signature';
200 case utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration:
201 return 'constructor';
202 case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition:
203 return 'field';
204 case utils_1.AST_NODE_TYPES.PropertyDefinition:
205 return node.value && functionExpressions.includes(node.value.type)
206 ? 'method'
207 : 'field';
208 case utils_1.AST_NODE_TYPES.TSPropertySignature:
209 return 'field';
210 case utils_1.AST_NODE_TYPES.TSIndexSignature:
211 return 'signature';
212 default:
213 return null;
214 }
215}
216/**
217 * Gets the raw string value of a member's name
218 */
219function getMemberRawName(member, sourceCode) {
220 const { name, type } = util.getNameFromMember(member, sourceCode);
221 if (type === util.MemberNameType.Quoted) {
222 return name.substr(1, name.length - 2);
223 }
224 if (type === util.MemberNameType.Private) {
225 return name.substr(1);
226 }
227 return name;
228}
229/**
230 * Gets the member name based on the member type.
231 *
232 * @param node the node to be evaluated.
233 * @param sourceCode
234 */
235function getMemberName(node, sourceCode) {
236 switch (node.type) {
237 case utils_1.AST_NODE_TYPES.TSPropertySignature:
238 case utils_1.AST_NODE_TYPES.TSMethodSignature:
239 case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition:
240 case utils_1.AST_NODE_TYPES.PropertyDefinition:
241 return getMemberRawName(node, sourceCode);
242 case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
243 case utils_1.AST_NODE_TYPES.MethodDefinition:
244 return node.kind === 'constructor'
245 ? 'constructor'
246 : getMemberRawName(node, sourceCode);
247 case utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration:
248 return 'new';
249 case utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration:
250 return 'call';
251 case utils_1.AST_NODE_TYPES.TSIndexSignature:
252 return util.getNameFromIndexSignature(node);
253 default:
254 return null;
255 }
256}
257/**
258 * Gets the calculated rank using the provided method definition.
259 * The algorithm is as follows:
260 * - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field
261 * - If there is no order for accessibility-scope-type, then strip out the accessibility.
262 * - If there is no order for scope-type, then strip out the scope.
263 * - If there is no order for type, then return -1
264 * @param memberGroups the valid names to be validated.
265 * @param orderConfig the current order to be validated.
266 *
267 * @return Index of the matching member type in the order configuration.
268 */
269function getRankOrder(memberGroups, orderConfig) {
270 let rank = -1;
271 const stack = memberGroups.slice(); // Get a copy of the member groups
272 while (stack.length > 0 && rank === -1) {
273 rank = orderConfig.indexOf(stack.shift());
274 }
275 return rank;
276}
277/**
278 * Gets the rank of the node given the order.
279 * @param node the node to be evaluated.
280 * @param orderConfig the current order to be validated.
281 * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not.
282 */
283function getRank(node, orderConfig, supportsModifiers) {
284 const type = getNodeType(node);
285 if (type === null) {
286 // shouldn't happen but just in case, put it on the end
287 return orderConfig.length - 1;
288 }
289 const abstract = node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition ||
290 node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition;
291 const scope = 'static' in node && node.static
292 ? 'static'
293 : abstract
294 ? 'abstract'
295 : 'instance';
296 const accessibility = 'accessibility' in node && node.accessibility
297 ? node.accessibility
298 : 'public';
299 // Collect all existing member groups (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.)
300 const memberGroups = [];
301 if (supportsModifiers) {
302 const decorated = 'decorators' in node && node.decorators.length > 0;
303 if (decorated &&
304 (type === 'field' ||
305 type === 'method' ||
306 type === 'get' ||
307 type === 'set')) {
308 memberGroups.push(`${accessibility}-decorated-${type}`);
309 memberGroups.push(`decorated-${type}`);
310 }
311 if (type !== 'constructor') {
312 // Constructors have no scope
313 memberGroups.push(`${accessibility}-${scope}-${type}`);
314 memberGroups.push(`${scope}-${type}`);
315 }
316 memberGroups.push(`${accessibility}-${type}`);
317 }
318 memberGroups.push(type);
319 return getRankOrder(memberGroups, orderConfig);
320}
321/**
322 * Gets the lowest possible rank higher than target.
323 * e.g. given the following order:
324 * ...
325 * public-static-method
326 * protected-static-method
327 * private-static-method
328 * public-instance-method
329 * protected-instance-method
330 * private-instance-method
331 * ...
332 * and considering that a public-instance-method has already been declared, so ranks contains
333 * public-instance-method, then the lowest possible rank for public-static-method is
334 * public-instance-method.
335 * @param ranks the existing ranks in the object.
336 * @param target the target rank.
337 * @param order the current order to be validated.
338 * @returns the name of the lowest possible rank without dashes (-).
339 */
340function getLowestRank(ranks, target, order) {
341 let lowest = ranks[ranks.length - 1];
342 ranks.forEach(rank => {
343 if (rank > target) {
344 lowest = Math.min(lowest, rank);
345 }
346 });
347 return order[lowest].replace(/-/g, ' ');
348}
349exports.default = util.createRule({
350 name: 'member-ordering',
351 meta: {
352 type: 'suggestion',
353 docs: {
354 description: 'Require a consistent member declaration order',
355 recommended: false,
356 },
357 messages: {
358 incorrectOrder: 'Member {{member}} should be declared before member {{beforeMember}}.',
359 incorrectGroupOrder: 'Member {{name}} should be declared before all {{rank}} definitions.',
360 },
361 schema: [
362 {
363 type: 'object',
364 properties: {
365 default: {
366 oneOf: [
367 neverConfig,
368 arrayConfig(allMemberTypes),
369 objectConfig(allMemberTypes),
370 ],
371 },
372 classes: {
373 oneOf: [
374 neverConfig,
375 arrayConfig(allMemberTypes),
376 objectConfig(allMemberTypes),
377 ],
378 },
379 classExpressions: {
380 oneOf: [
381 neverConfig,
382 arrayConfig(allMemberTypes),
383 objectConfig(allMemberTypes),
384 ],
385 },
386 interfaces: {
387 oneOf: [
388 neverConfig,
389 arrayConfig(['signature', 'field', 'method', 'constructor']),
390 objectConfig(['signature', 'field', 'method', 'constructor']),
391 ],
392 },
393 typeLiterals: {
394 oneOf: [
395 neverConfig,
396 arrayConfig(['signature', 'field', 'method', 'constructor']),
397 objectConfig(['signature', 'field', 'method', 'constructor']),
398 ],
399 },
400 },
401 additionalProperties: false,
402 },
403 ],
404 },
405 defaultOptions: [
406 {
407 default: exports.defaultOrder,
408 },
409 ],
410 create(context, [options]) {
411 /**
412 * Checks if the member groups are correctly sorted.
413 *
414 * @param members Members to be validated.
415 * @param groupOrder Group order to be validated.
416 * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not.
417 *
418 * @return Array of member groups or null if one of the groups is not correctly sorted.
419 */
420 function checkGroupSort(members, groupOrder, supportsModifiers) {
421 const previousRanks = [];
422 const memberGroups = [];
423 let isCorrectlySorted = true;
424 // Find first member which isn't correctly sorted
425 members.forEach(member => {
426 const rank = getRank(member, groupOrder, supportsModifiers);
427 const name = getMemberName(member, context.getSourceCode());
428 const rankLastMember = previousRanks[previousRanks.length - 1];
429 if (rank === -1) {
430 return;
431 }
432 // Works for 1st item because x < undefined === false for any x (typeof string)
433 if (rank < rankLastMember) {
434 context.report({
435 node: member,
436 messageId: 'incorrectGroupOrder',
437 data: {
438 name,
439 rank: getLowestRank(previousRanks, rank, groupOrder),
440 },
441 });
442 isCorrectlySorted = false;
443 }
444 else if (rank === rankLastMember) {
445 // Same member group --> Push to existing member group array
446 memberGroups[memberGroups.length - 1].push(member);
447 }
448 else {
449 // New member group --> Create new member group array
450 previousRanks.push(rank);
451 memberGroups.push([member]);
452 }
453 });
454 return isCorrectlySorted ? memberGroups : null;
455 }
456 /**
457 * Checks if the members are alphabetically sorted.
458 *
459 * @param members Members to be validated.
460 * @param caseSensitive indicates if the alpha ordering is case sensitive or not.
461 *
462 * @return True if all members are correctly sorted.
463 */
464 function checkAlphaSort(members, caseSensitive) {
465 let previousName = '';
466 let isCorrectlySorted = true;
467 // Find first member which isn't correctly sorted
468 members.forEach(member => {
469 const name = getMemberName(member, context.getSourceCode());
470 // Note: Not all members have names
471 if (name) {
472 if (caseSensitive
473 ? name < previousName
474 : name.toLowerCase() < previousName.toLowerCase()) {
475 context.report({
476 node: member,
477 messageId: 'incorrectOrder',
478 data: {
479 member: name,
480 beforeMember: previousName,
481 },
482 });
483 isCorrectlySorted = false;
484 }
485 previousName = name;
486 }
487 });
488 return isCorrectlySorted;
489 }
490 /**
491 * Validates if all members are correctly sorted.
492 *
493 * @param members Members to be validated.
494 * @param orderConfig Order config to be validated.
495 * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not.
496 */
497 function validateMembersOrder(members, orderConfig, supportsModifiers) {
498 if (orderConfig === 'never') {
499 return;
500 }
501 // Standardize config
502 let order = null;
503 let memberTypes;
504 if (Array.isArray(orderConfig)) {
505 memberTypes = orderConfig;
506 }
507 else {
508 order = orderConfig.order;
509 memberTypes = orderConfig.memberTypes;
510 }
511 const hasAlphaSort = order === null || order === void 0 ? void 0 : order.startsWith('alphabetically');
512 const alphaSortIsCaseSensitive = order !== 'alphabetically-case-insensitive';
513 // Check order
514 if (Array.isArray(memberTypes)) {
515 const grouped = checkGroupSort(members, memberTypes, supportsModifiers);
516 if (grouped === null) {
517 return;
518 }
519 if (hasAlphaSort) {
520 grouped.some(groupMember => !checkAlphaSort(groupMember, alphaSortIsCaseSensitive));
521 }
522 }
523 else if (hasAlphaSort) {
524 checkAlphaSort(members, alphaSortIsCaseSensitive);
525 }
526 }
527 return {
528 ClassDeclaration(node) {
529 var _a;
530 validateMembersOrder(node.body.body, (_a = options.classes) !== null && _a !== void 0 ? _a : options.default, true);
531 },
532 ClassExpression(node) {
533 var _a;
534 validateMembersOrder(node.body.body, (_a = options.classExpressions) !== null && _a !== void 0 ? _a : options.default, true);
535 },
536 TSInterfaceDeclaration(node) {
537 var _a;
538 validateMembersOrder(node.body.body, (_a = options.interfaces) !== null && _a !== void 0 ? _a : options.default, false);
539 },
540 TSTypeLiteral(node) {
541 var _a;
542 validateMembersOrder(node.members, (_a = options.typeLiterals) !== null && _a !== void 0 ? _a : options.default, false);
543 },
544 };
545 },
546});
547//# sourceMappingURL=member-ordering.js.map
\No newline at end of file