747 lines
27 KiB
Plaintext
747 lines
27 KiB
Plaintext
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.defaultOrder = void 0;
|
|
const utils_1 = require("@typescript-eslint/utils");
|
|
const eslint_utils_1 = require("@typescript-eslint/utils/eslint-utils");
|
|
const natural_compare_1 = __importDefault(require("natural-compare"));
|
|
const util_1 = require("../util");
|
|
const neverConfig = {
|
|
type: 'string',
|
|
enum: ['never'],
|
|
};
|
|
const arrayConfig = (memberTypes) => ({
|
|
type: 'array',
|
|
items: {
|
|
oneOf: [
|
|
{
|
|
$ref: memberTypes,
|
|
},
|
|
{
|
|
type: 'array',
|
|
items: {
|
|
$ref: memberTypes,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
const objectConfig = (memberTypes) => ({
|
|
type: 'object',
|
|
properties: {
|
|
memberTypes: {
|
|
oneOf: [arrayConfig(memberTypes), neverConfig],
|
|
},
|
|
order: {
|
|
$ref: '#/items/0/$defs/orderOptions',
|
|
},
|
|
optionalityOrder: {
|
|
$ref: '#/items/0/$defs/optionalityOrderOptions',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
});
|
|
exports.defaultOrder = [
|
|
// Index signature
|
|
'signature',
|
|
'call-signature',
|
|
// Fields
|
|
'public-static-field',
|
|
'protected-static-field',
|
|
'private-static-field',
|
|
'#private-static-field',
|
|
'public-decorated-field',
|
|
'protected-decorated-field',
|
|
'private-decorated-field',
|
|
'public-instance-field',
|
|
'protected-instance-field',
|
|
'private-instance-field',
|
|
'#private-instance-field',
|
|
'public-abstract-field',
|
|
'protected-abstract-field',
|
|
'public-field',
|
|
'protected-field',
|
|
'private-field',
|
|
'#private-field',
|
|
'static-field',
|
|
'instance-field',
|
|
'abstract-field',
|
|
'decorated-field',
|
|
'field',
|
|
// Static initialization
|
|
'static-initialization',
|
|
// Constructors
|
|
'public-constructor',
|
|
'protected-constructor',
|
|
'private-constructor',
|
|
'constructor',
|
|
// Accessors
|
|
'public-static-accessor',
|
|
'protected-static-accessor',
|
|
'private-static-accessor',
|
|
'#private-static-accessor',
|
|
'public-decorated-accessor',
|
|
'protected-decorated-accessor',
|
|
'private-decorated-accessor',
|
|
'public-instance-accessor',
|
|
'protected-instance-accessor',
|
|
'private-instance-accessor',
|
|
'#private-instance-accessor',
|
|
'public-abstract-accessor',
|
|
'protected-abstract-accessor',
|
|
'public-accessor',
|
|
'protected-accessor',
|
|
'private-accessor',
|
|
'#private-accessor',
|
|
'static-accessor',
|
|
'instance-accessor',
|
|
'abstract-accessor',
|
|
'decorated-accessor',
|
|
'accessor',
|
|
// Getters
|
|
'public-static-get',
|
|
'protected-static-get',
|
|
'private-static-get',
|
|
'#private-static-get',
|
|
'public-decorated-get',
|
|
'protected-decorated-get',
|
|
'private-decorated-get',
|
|
'public-instance-get',
|
|
'protected-instance-get',
|
|
'private-instance-get',
|
|
'#private-instance-get',
|
|
'public-abstract-get',
|
|
'protected-abstract-get',
|
|
'public-get',
|
|
'protected-get',
|
|
'private-get',
|
|
'#private-get',
|
|
'static-get',
|
|
'instance-get',
|
|
'abstract-get',
|
|
'decorated-get',
|
|
'get',
|
|
// Setters
|
|
'public-static-set',
|
|
'protected-static-set',
|
|
'private-static-set',
|
|
'#private-static-set',
|
|
'public-decorated-set',
|
|
'protected-decorated-set',
|
|
'private-decorated-set',
|
|
'public-instance-set',
|
|
'protected-instance-set',
|
|
'private-instance-set',
|
|
'#private-instance-set',
|
|
'public-abstract-set',
|
|
'protected-abstract-set',
|
|
'public-set',
|
|
'protected-set',
|
|
'private-set',
|
|
'#private-set',
|
|
'static-set',
|
|
'instance-set',
|
|
'abstract-set',
|
|
'decorated-set',
|
|
'set',
|
|
// Methods
|
|
'public-static-method',
|
|
'protected-static-method',
|
|
'private-static-method',
|
|
'#private-static-method',
|
|
'public-decorated-method',
|
|
'protected-decorated-method',
|
|
'private-decorated-method',
|
|
'public-instance-method',
|
|
'protected-instance-method',
|
|
'private-instance-method',
|
|
'#private-instance-method',
|
|
'public-abstract-method',
|
|
'protected-abstract-method',
|
|
'public-method',
|
|
'protected-method',
|
|
'private-method',
|
|
'#private-method',
|
|
'static-method',
|
|
'instance-method',
|
|
'abstract-method',
|
|
'decorated-method',
|
|
'method',
|
|
];
|
|
const allMemberTypes = Array.from([
|
|
'readonly-signature',
|
|
'signature',
|
|
'readonly-field',
|
|
'field',
|
|
'method',
|
|
'call-signature',
|
|
'constructor',
|
|
'accessor',
|
|
'get',
|
|
'set',
|
|
'static-initialization',
|
|
].reduce((all, type) => {
|
|
all.add(type);
|
|
['public', 'protected', 'private', '#private'].forEach(accessibility => {
|
|
if (type !== 'readonly-signature' &&
|
|
type !== 'signature' &&
|
|
type !== 'static-initialization' &&
|
|
type !== 'call-signature' &&
|
|
!(type === 'constructor' && accessibility === '#private')) {
|
|
all.add(`${accessibility}-${type}`); // e.g. `public-field`
|
|
}
|
|
// Only class instance fields, methods, accessors, get and set can have decorators attached to them
|
|
if (accessibility !== '#private' &&
|
|
(type === 'readonly-field' ||
|
|
type === 'field' ||
|
|
type === 'method' ||
|
|
type === 'accessor' ||
|
|
type === 'get' ||
|
|
type === 'set')) {
|
|
all.add(`${accessibility}-decorated-${type}`);
|
|
all.add(`decorated-${type}`);
|
|
}
|
|
if (type !== 'constructor' &&
|
|
type !== 'readonly-signature' &&
|
|
type !== 'signature' &&
|
|
type !== 'call-signature') {
|
|
// There is no `static-constructor` or `instance-constructor` or `abstract-constructor`
|
|
if (accessibility === '#private' || accessibility === 'private') {
|
|
['static', 'instance'].forEach(scope => {
|
|
all.add(`${scope}-${type}`);
|
|
all.add(`${accessibility}-${scope}-${type}`);
|
|
});
|
|
}
|
|
else {
|
|
['static', 'instance', 'abstract'].forEach(scope => {
|
|
all.add(`${scope}-${type}`);
|
|
all.add(`${accessibility}-${scope}-${type}`);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return all;
|
|
}, new Set()));
|
|
const functionExpressions = [
|
|
utils_1.AST_NODE_TYPES.FunctionExpression,
|
|
utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
|
|
];
|
|
/**
|
|
* Gets the node type.
|
|
*
|
|
* @param node the node to be evaluated.
|
|
*/
|
|
function getNodeType(node) {
|
|
switch (node.type) {
|
|
case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
|
|
case utils_1.AST_NODE_TYPES.MethodDefinition:
|
|
return node.kind;
|
|
case utils_1.AST_NODE_TYPES.TSMethodSignature:
|
|
return 'method';
|
|
case utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration:
|
|
return 'call-signature';
|
|
case utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration:
|
|
return 'constructor';
|
|
case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition:
|
|
return node.readonly ? 'readonly-field' : 'field';
|
|
case utils_1.AST_NODE_TYPES.AccessorProperty:
|
|
return 'accessor';
|
|
case utils_1.AST_NODE_TYPES.PropertyDefinition:
|
|
return node.value && functionExpressions.includes(node.value.type)
|
|
? 'method'
|
|
: node.readonly
|
|
? 'readonly-field'
|
|
: 'field';
|
|
case utils_1.AST_NODE_TYPES.TSPropertySignature:
|
|
return node.readonly ? 'readonly-field' : 'field';
|
|
case utils_1.AST_NODE_TYPES.TSIndexSignature:
|
|
return node.readonly ? 'readonly-signature' : 'signature';
|
|
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
return 'static-initialization';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Gets the raw string value of a member's name
|
|
*/
|
|
function getMemberRawName(member, sourceCode) {
|
|
const { name, type } = (0, util_1.getNameFromMember)(member, sourceCode);
|
|
if (type === util_1.MemberNameType.Quoted) {
|
|
return name.slice(1, -1);
|
|
}
|
|
if (type === util_1.MemberNameType.Private) {
|
|
return name.slice(1);
|
|
}
|
|
return name;
|
|
}
|
|
/**
|
|
* Gets the member name based on the member type.
|
|
*
|
|
* @param node the node to be evaluated.
|
|
*/
|
|
function getMemberName(node, sourceCode) {
|
|
switch (node.type) {
|
|
case utils_1.AST_NODE_TYPES.TSPropertySignature:
|
|
case utils_1.AST_NODE_TYPES.TSMethodSignature:
|
|
case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition:
|
|
case utils_1.AST_NODE_TYPES.PropertyDefinition:
|
|
return getMemberRawName(node, sourceCode);
|
|
case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
|
|
case utils_1.AST_NODE_TYPES.MethodDefinition:
|
|
return node.kind === 'constructor'
|
|
? 'constructor'
|
|
: getMemberRawName(node, sourceCode);
|
|
case utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration:
|
|
return 'new';
|
|
case utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration:
|
|
return 'call';
|
|
case utils_1.AST_NODE_TYPES.TSIndexSignature:
|
|
return (0, util_1.getNameFromIndexSignature)(node);
|
|
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
return 'static block';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Returns true if the member is optional based on the member type.
|
|
*
|
|
* @param node the node to be evaluated.
|
|
*
|
|
* @returns Whether the member is optional, or false if it cannot be optional at all.
|
|
*/
|
|
function isMemberOptional(node) {
|
|
switch (node.type) {
|
|
case utils_1.AST_NODE_TYPES.TSPropertySignature:
|
|
case utils_1.AST_NODE_TYPES.TSMethodSignature:
|
|
case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition:
|
|
case utils_1.AST_NODE_TYPES.PropertyDefinition:
|
|
case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
|
|
case utils_1.AST_NODE_TYPES.MethodDefinition:
|
|
return !!node.optional;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Gets the calculated rank using the provided method definition.
|
|
* The algorithm is as follows:
|
|
* - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field
|
|
* - If there is no order for accessibility-scope-type, then strip out the accessibility.
|
|
* - If there is no order for scope-type, then strip out the scope.
|
|
* - If there is no order for type, then return -1
|
|
* @param memberGroups the valid names to be validated.
|
|
* @param orderConfig the current order to be validated.
|
|
*
|
|
* @return Index of the matching member type in the order configuration.
|
|
*/
|
|
function getRankOrder(memberGroups, orderConfig) {
|
|
let rank = -1;
|
|
const stack = memberGroups.slice(); // Get a copy of the member groups
|
|
while (stack.length > 0 && rank === -1) {
|
|
const memberGroup = stack.shift();
|
|
rank = orderConfig.findIndex(memberType => Array.isArray(memberType)
|
|
? memberType.includes(memberGroup)
|
|
: memberType === memberGroup);
|
|
}
|
|
return rank;
|
|
}
|
|
function getAccessibility(node) {
|
|
if ('accessibility' in node && node.accessibility) {
|
|
return node.accessibility;
|
|
}
|
|
if ('key' in node && node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
return '#private';
|
|
}
|
|
return 'public';
|
|
}
|
|
/**
|
|
* Gets the rank of the node given the order.
|
|
* @param node the node to be evaluated.
|
|
* @param orderConfig the current order to be validated.
|
|
* @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not.
|
|
*/
|
|
function getRank(node, orderConfig, supportsModifiers) {
|
|
const type = getNodeType(node);
|
|
if (type == null) {
|
|
// shouldn't happen but just in case, put it on the end
|
|
return orderConfig.length - 1;
|
|
}
|
|
const abstract = node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition ||
|
|
node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition;
|
|
const scope = 'static' in node && node.static
|
|
? 'static'
|
|
: abstract
|
|
? 'abstract'
|
|
: 'instance';
|
|
const accessibility = getAccessibility(node);
|
|
// Collect all existing member groups that apply to this node...
|
|
// (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.)
|
|
const memberGroups = [];
|
|
if (supportsModifiers) {
|
|
const decorated = 'decorators' in node && node.decorators.length > 0;
|
|
if (decorated &&
|
|
(type === 'readonly-field' ||
|
|
type === 'field' ||
|
|
type === 'method' ||
|
|
type === 'get' ||
|
|
type === 'set')) {
|
|
memberGroups.push(`${accessibility}-decorated-${type}`);
|
|
memberGroups.push(`decorated-${type}`);
|
|
if (type === 'readonly-field') {
|
|
memberGroups.push(`${accessibility}-decorated-field`);
|
|
memberGroups.push(`decorated-field`);
|
|
}
|
|
}
|
|
if (type !== 'readonly-signature' &&
|
|
type !== 'signature' &&
|
|
type !== 'static-initialization') {
|
|
if (type !== 'constructor') {
|
|
// Constructors have no scope
|
|
memberGroups.push(`${accessibility}-${scope}-${type}`);
|
|
memberGroups.push(`${scope}-${type}`);
|
|
if (type === 'readonly-field') {
|
|
memberGroups.push(`${accessibility}-${scope}-field`);
|
|
memberGroups.push(`${scope}-field`);
|
|
}
|
|
}
|
|
memberGroups.push(`${accessibility}-${type}`);
|
|
if (type === 'readonly-field') {
|
|
memberGroups.push(`${accessibility}-field`);
|
|
}
|
|
}
|
|
}
|
|
memberGroups.push(type);
|
|
if (type === 'readonly-signature') {
|
|
memberGroups.push('signature');
|
|
}
|
|
else if (type === 'readonly-field') {
|
|
memberGroups.push('field');
|
|
}
|
|
// ...then get the rank order for those member groups based on the node
|
|
return getRankOrder(memberGroups, orderConfig);
|
|
}
|
|
/**
|
|
* Gets the lowest possible rank(s) higher than target.
|
|
* e.g. given the following order:
|
|
* ...
|
|
* public-static-method
|
|
* protected-static-method
|
|
* private-static-method
|
|
* public-instance-method
|
|
* protected-instance-method
|
|
* private-instance-method
|
|
* ...
|
|
* and considering that a public-instance-method has already been declared, so ranks contains
|
|
* public-instance-method, then the lowest possible rank for public-static-method is
|
|
* public-instance-method.
|
|
* If a lowest possible rank is a member group, a comma separated list of ranks is returned.
|
|
* @param ranks the existing ranks in the object.
|
|
* @param target the target rank.
|
|
* @param order the current order to be validated.
|
|
* @returns the name(s) of the lowest possible rank without dashes (-).
|
|
*/
|
|
function getLowestRank(ranks, target, order) {
|
|
let lowest = ranks[ranks.length - 1];
|
|
ranks.forEach(rank => {
|
|
if (rank > target) {
|
|
lowest = Math.min(lowest, rank);
|
|
}
|
|
});
|
|
const lowestRank = order[lowest];
|
|
const lowestRanks = Array.isArray(lowestRank) ? lowestRank : [lowestRank];
|
|
return lowestRanks.map(rank => rank.replace(/-/g, ' ')).join(', ');
|
|
}
|
|
exports.default = (0, util_1.createRule)({
|
|
name: 'member-ordering',
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Require a consistent member declaration order',
|
|
},
|
|
messages: {
|
|
incorrectOrder: 'Member {{member}} should be declared before member {{beforeMember}}.',
|
|
incorrectGroupOrder: 'Member {{name}} should be declared before all {{rank}} definitions.',
|
|
incorrectRequiredMembersOrder: `Member {{member}} should be declared after all {{optionalOrRequired}} members.`,
|
|
},
|
|
schema: [
|
|
{
|
|
$defs: {
|
|
orderOptions: {
|
|
type: 'string',
|
|
enum: [
|
|
'alphabetically',
|
|
'alphabetically-case-insensitive',
|
|
'as-written',
|
|
'natural',
|
|
'natural-case-insensitive',
|
|
],
|
|
},
|
|
optionalityOrderOptions: {
|
|
type: 'string',
|
|
enum: ['optional-first', 'required-first'],
|
|
},
|
|
allItems: {
|
|
type: 'string',
|
|
enum: allMemberTypes,
|
|
},
|
|
typeItems: {
|
|
type: 'string',
|
|
enum: [
|
|
'readonly-signature',
|
|
'signature',
|
|
'readonly-field',
|
|
'field',
|
|
'method',
|
|
'constructor',
|
|
],
|
|
},
|
|
baseConfig: {
|
|
oneOf: [
|
|
neverConfig,
|
|
arrayConfig('#/items/0/$defs/allItems'),
|
|
objectConfig('#/items/0/$defs/allItems'),
|
|
],
|
|
},
|
|
typesConfig: {
|
|
oneOf: [
|
|
neverConfig,
|
|
arrayConfig('#/items/0/$defs/typeItems'),
|
|
objectConfig('#/items/0/$defs/typeItems'),
|
|
],
|
|
},
|
|
},
|
|
type: 'object',
|
|
properties: {
|
|
default: {
|
|
$ref: '#/items/0/$defs/baseConfig',
|
|
},
|
|
classes: {
|
|
$ref: '#/items/0/$defs/baseConfig',
|
|
},
|
|
classExpressions: {
|
|
$ref: '#/items/0/$defs/baseConfig',
|
|
},
|
|
interfaces: {
|
|
$ref: '#/items/0/$defs/typesConfig',
|
|
},
|
|
typeLiterals: {
|
|
$ref: '#/items/0/$defs/typesConfig',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
},
|
|
defaultOptions: [
|
|
{
|
|
default: {
|
|
memberTypes: exports.defaultOrder,
|
|
},
|
|
},
|
|
],
|
|
create(context, [options]) {
|
|
/**
|
|
* Checks if the member groups are correctly sorted.
|
|
*
|
|
* @param members Members to be validated.
|
|
* @param groupOrder Group order to be validated.
|
|
* @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not.
|
|
*
|
|
* @return Array of member groups or null if one of the groups is not correctly sorted.
|
|
*/
|
|
function checkGroupSort(members, groupOrder, supportsModifiers) {
|
|
const previousRanks = [];
|
|
const memberGroups = [];
|
|
let isCorrectlySorted = true;
|
|
// Find first member which isn't correctly sorted
|
|
for (const member of members) {
|
|
const rank = getRank(member, groupOrder, supportsModifiers);
|
|
const name = getMemberName(member, (0, eslint_utils_1.getSourceCode)(context));
|
|
const rankLastMember = previousRanks[previousRanks.length - 1];
|
|
if (rank === -1) {
|
|
continue;
|
|
}
|
|
// Works for 1st item because x < undefined === false for any x (typeof string)
|
|
if (rank < rankLastMember) {
|
|
context.report({
|
|
node: member,
|
|
messageId: 'incorrectGroupOrder',
|
|
data: {
|
|
name,
|
|
rank: getLowestRank(previousRanks, rank, groupOrder),
|
|
},
|
|
});
|
|
isCorrectlySorted = false;
|
|
}
|
|
else if (rank === rankLastMember) {
|
|
// Same member group --> Push to existing member group array
|
|
memberGroups[memberGroups.length - 1].push(member);
|
|
}
|
|
else {
|
|
// New member group --> Create new member group array
|
|
previousRanks.push(rank);
|
|
memberGroups.push([member]);
|
|
}
|
|
}
|
|
return isCorrectlySorted ? memberGroups : null;
|
|
}
|
|
/**
|
|
* Checks if the members are alphabetically sorted.
|
|
*
|
|
* @param members Members to be validated.
|
|
* @param order What order the members should be sorted in.
|
|
*
|
|
* @return True if all members are correctly sorted.
|
|
*/
|
|
function checkAlphaSort(members, order) {
|
|
let previousName = '';
|
|
let isCorrectlySorted = true;
|
|
// Find first member which isn't correctly sorted
|
|
members.forEach(member => {
|
|
const name = getMemberName(member, (0, eslint_utils_1.getSourceCode)(context));
|
|
// Note: Not all members have names
|
|
if (name) {
|
|
if (naturalOutOfOrder(name, previousName, order)) {
|
|
context.report({
|
|
node: member,
|
|
messageId: 'incorrectOrder',
|
|
data: {
|
|
member: name,
|
|
beforeMember: previousName,
|
|
},
|
|
});
|
|
isCorrectlySorted = false;
|
|
}
|
|
previousName = name;
|
|
}
|
|
});
|
|
return isCorrectlySorted;
|
|
}
|
|
function naturalOutOfOrder(name, previousName, order) {
|
|
if (name === previousName) {
|
|
return false;
|
|
}
|
|
switch (order) {
|
|
case 'alphabetically':
|
|
return name < previousName;
|
|
case 'alphabetically-case-insensitive':
|
|
return name.toLowerCase() < previousName.toLowerCase();
|
|
case 'natural':
|
|
return (0, natural_compare_1.default)(name, previousName) !== 1;
|
|
case 'natural-case-insensitive':
|
|
return ((0, natural_compare_1.default)(name.toLowerCase(), previousName.toLowerCase()) !== 1);
|
|
}
|
|
}
|
|
/**
|
|
* Checks if the order of optional and required members is correct based
|
|
* on the given 'required' parameter.
|
|
*
|
|
* @param members Members to be validated.
|
|
* @param optionalityOrder Where to place optional members, if not intermixed.
|
|
*
|
|
* @return True if all required and optional members are correctly sorted.
|
|
*/
|
|
function checkRequiredOrder(members, optionalityOrder) {
|
|
const switchIndex = members.findIndex((member, i) => i && isMemberOptional(member) !== isMemberOptional(members[i - 1]));
|
|
const report = (member) => context.report({
|
|
messageId: 'incorrectRequiredMembersOrder',
|
|
loc: member.loc,
|
|
data: {
|
|
member: getMemberName(member, (0, eslint_utils_1.getSourceCode)(context)),
|
|
optionalOrRequired: optionalityOrder === 'required-first' ? 'required' : 'optional',
|
|
},
|
|
});
|
|
// if the optionality of the first item is correct (based on optionalityOrder)
|
|
// then the first 0 inclusive to switchIndex exclusive members all
|
|
// have the correct optionality
|
|
if (isMemberOptional(members[0]) !==
|
|
(optionalityOrder === 'optional-first')) {
|
|
report(members[0]);
|
|
return false;
|
|
}
|
|
for (let i = switchIndex + 1; i < members.length; i++) {
|
|
if (isMemberOptional(members[i]) !==
|
|
isMemberOptional(members[switchIndex])) {
|
|
report(members[switchIndex]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Validates if all members are correctly sorted.
|
|
*
|
|
* @param members Members to be validated.
|
|
* @param orderConfig Order config to be validated.
|
|
* @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not.
|
|
*/
|
|
function validateMembersOrder(members, orderConfig, supportsModifiers) {
|
|
if (orderConfig === 'never') {
|
|
return;
|
|
}
|
|
// Standardize config
|
|
let order;
|
|
let memberTypes;
|
|
let optionalityOrder;
|
|
// returns true if everything is good and false if an error was reported
|
|
const checkOrder = (memberSet) => {
|
|
const hasAlphaSort = !!(order && order !== 'as-written');
|
|
// Check order
|
|
if (Array.isArray(memberTypes)) {
|
|
const grouped = checkGroupSort(memberSet, memberTypes, supportsModifiers);
|
|
if (grouped == null) {
|
|
return false;
|
|
}
|
|
if (hasAlphaSort) {
|
|
return !grouped.some(groupMember => !checkAlphaSort(groupMember, order));
|
|
}
|
|
}
|
|
else if (hasAlphaSort) {
|
|
return checkAlphaSort(memberSet, order);
|
|
}
|
|
return true;
|
|
};
|
|
if (Array.isArray(orderConfig)) {
|
|
memberTypes = orderConfig;
|
|
}
|
|
else {
|
|
order = orderConfig.order;
|
|
memberTypes = orderConfig.memberTypes;
|
|
optionalityOrder = orderConfig.optionalityOrder;
|
|
}
|
|
if (!optionalityOrder) {
|
|
checkOrder(members);
|
|
return;
|
|
}
|
|
const switchIndex = members.findIndex((member, i) => i && isMemberOptional(member) !== isMemberOptional(members[i - 1]));
|
|
if (switchIndex !== -1) {
|
|
if (!checkRequiredOrder(members, optionalityOrder)) {
|
|
return;
|
|
}
|
|
checkOrder(members.slice(0, switchIndex));
|
|
checkOrder(members.slice(switchIndex));
|
|
}
|
|
else {
|
|
checkOrder(members);
|
|
}
|
|
}
|
|
return {
|
|
ClassDeclaration(node) {
|
|
validateMembersOrder(node.body.body, options.classes ?? options.default, true);
|
|
},
|
|
ClassExpression(node) {
|
|
validateMembersOrder(node.body.body, options.classExpressions ?? options.default, true);
|
|
},
|
|
TSInterfaceDeclaration(node) {
|
|
validateMembersOrder(node.body.body, options.interfaces ?? options.default, false);
|
|
},
|
|
TSTypeLiteral(node) {
|
|
validateMembersOrder(node.members, options.typeLiterals ?? options.default, false);
|
|
},
|
|
};
|
|
},
|
|
});
|
|
//# sourceMappingURL=member-ordering.js.map |