📄 member-ordering¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 25 |
| 📦 Imports | 13 |
| 📊 Variables & Constants | 4 |
| 📐 Interfaces | 1 |
| 📑 Type Aliases | 15 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/member-ordering.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'member-ordering' |
meta.type |
'suggestion' |
meta.docs.description |
'Require a consistent member declaration order' |
meta.docs.frozen |
true |
meta.messages.incorrectGroupOrder |
'Member {{name}} should be declared before all {{rank}} definitions.' |
meta.messages.incorrectOrder |
'Member {{member}} should be declared before member {{beforeMember}}.' |
meta.messages.incorrectRequiredMembersOrder |
Member {{member}} should be declared after all {{optionalOrRequired}} members. |
meta.schema |
[ { type: 'object', $defs: { allItems: { type: 'string', enum: allMemberTypes, }, optionalityOrderOptions: { type: 's... |
defaultOptions |
[ { default: { memberTypes: defaultOrder, }, }, ] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
JSONSchema |
@typescript-eslint/utils |
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
naturalCompare |
natural-compare |
createRule |
../util |
forEachChildESTree |
../util |
getNameFromIndexSignature |
../util |
getNameFromMember |
../util |
getStaticStringValue |
../util |
MemberNameType |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
neverConfig |
JSONSchema.JSONSchema4 |
const | { type: 'string', enum: ['never'], } |
✗ |
defaultOrder |
MemberType[] |
const | [ // Index signature 'signature', 'call-signature', // Fields 'public-static-... |
✓ |
allMemberTypes |
BaseMemberType[] |
const | [ ...new Set( ( [ 'readonly-signature', 'signature', 'readonly-field', 'field... |
✗ |
functionExpressions |
any[] |
const | [ AST_NODE_TYPES.FunctionExpression, AST_NODE_TYPES.ArrowFunctionExpression, ] |
✗ |
Functions¶
create(context: any, [options]: any): { ClassDeclaration(node: any): void; 'ClassDeclaration, Fun…¶
Parameters:
contextany[options]any
Returns: { ClassDeclaration(node: any): void; 'ClassDeclaration, FunctionDeclaration'(node: any): void; ClassExpression(node: any): void; TSInterfaceDeclaration(node: any): void; TSTypeLiteral(node: any): void; }
Calls:
getRankgetMemberNamecontext.reportgetLowestRankmemberGroups[memberGroups.length - 1].pushpreviousRanks.pushmemberGroups.pushmembers.forEachnaturalOutOfOrderisBlockedByEarlierMemberReferencesname.toLowerCasepreviousName.toLowerCasenaturalCompare (from natural-compare)members.findIndexisMemberOptionalreportArray.isArraygroupMembersByType(memberSet, memberTypes, supportsModifiers).forEachcheckAlphaSortcheckGroupSortcheckAlphaSortForAllMembersgrouped.mapcheckOrdercheckRequiredOrdermembers.slicevalidateMembersOrder
Internal Comments:
/**
* 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.
*/
// Find first member which isn't correctly sorted (x5)
// Works for 1st item because x < undefined === false for any x (typeof string)
// Same member group --> Push to existing member group array (x5)
// New member group --> Create new member group array (x4)
/**
* 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.
*/
// Note: Not all members have names
/**
* 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.
*/
// 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
/**
* 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.
*/
// Standardize config (x2)
/**
* It runs an alphabetic sort on the groups of the members of the class in the source code.
* @param memberSet The members in the class of the source code on which the grouping operation will be performed.
*/ (x2)
// returns true if everything is good and false if an error was reported (x2)
// Check order
// https://github.com/typescript-eslint/typescript-eslint/issues/5439
/* eslint-disable @typescript-eslint/no-non-null-assertion */
Code
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: Member[],
groupOrder: MemberType[],
supportsModifiers: boolean,
): Member[][] | null {
const previousRanks: number[] = [];
const memberGroups: Member[][] = [];
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, context.sourceCode);
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: Member[],
order: AlphabeticalOrder,
): boolean {
let previousName = '';
let previousIndex = 0;
let isCorrectlySorted = true;
// Find first member which isn't correctly sorted
members.forEach((member, index) => {
const name = getMemberName(member, context.sourceCode);
// Note: Not all members have names
if (name) {
if (naturalOutOfOrder(name, previousName, order)) {
if (
isBlockedByEarlierMemberReferences(
member,
members,
previousIndex,
index,
context.sourceCode,
)
) {
return;
}
context.report({
node: member,
messageId: 'incorrectOrder',
data: {
beforeMember: previousName,
member: name,
},
});
isCorrectlySorted = false;
}
previousName = name;
previousIndex = index;
}
});
return isCorrectlySorted;
}
function naturalOutOfOrder(
name: string,
previousName: string,
order: AlphabeticalOrder,
): boolean {
if (name === previousName) {
return false;
}
switch (order) {
case 'alphabetically':
return name < previousName;
case 'alphabetically-case-insensitive':
return name.toLowerCase() < previousName.toLowerCase();
case 'natural':
return naturalCompare(name, previousName) !== 1;
case 'natural-case-insensitive':
return (
naturalCompare(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: Member[],
optionalityOrder: OptionalityOrder | undefined,
): boolean {
const switchIndex = members.findIndex(
(member, i) =>
i && isMemberOptional(member) !== isMemberOptional(members[i - 1]),
);
const report = (member: Member): void =>
context.report({
loc: member.loc,
messageId: 'incorrectRequiredMembersOrder',
data: {
member: getMemberName(member, context.sourceCode),
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: Member[],
orderConfig: OrderConfig,
supportsModifiers: boolean,
): void {
if (orderConfig === 'never') {
return;
}
// Standardize config
let order: Order | undefined;
let memberTypes: string | MemberType[] | undefined;
let optionalityOrder: OptionalityOrder | undefined;
/**
* It runs an alphabetic sort on the groups of the members of the class in the source code.
* @param memberSet The members in the class of the source code on which the grouping operation will be performed.
*/
const checkAlphaSortForAllMembers = (memberSet: Member[]): undefined => {
const hasAlphaSort = !!(order && order !== 'as-written');
if (hasAlphaSort && Array.isArray(memberTypes)) {
groupMembersByType(memberSet, memberTypes, supportsModifiers).forEach(
members => {
checkAlphaSort(members, order as AlphabeticalOrder);
},
);
}
};
// returns true if everything is good and false if an error was reported
const checkOrder = (memberSet: Member[]): boolean => {
const hasAlphaSort = !!(order && order !== 'as-written');
// Check order
if (Array.isArray(memberTypes)) {
const grouped = checkGroupSort(
memberSet,
memberTypes,
supportsModifiers,
);
if (grouped == null) {
checkAlphaSortForAllMembers(members);
return false;
}
if (hasAlphaSort) {
grouped.map(groupMember =>
checkAlphaSort(groupMember, order as AlphabeticalOrder),
);
}
} else if (hasAlphaSort) {
return checkAlphaSort(memberSet, order as AlphabeticalOrder);
}
return false;
};
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);
}
}
// https://github.com/typescript-eslint/typescript-eslint/issues/5439
/* eslint-disable @typescript-eslint/no-non-null-assertion */
return {
ClassDeclaration(node): void {
validateMembersOrder(
node.body.body,
options.classes ?? options.default!,
true,
);
},
'ClassDeclaration, FunctionDeclaration'(node): void {
if ('superClass' in node) {
// ...
}
},
ClassExpression(node): void {
validateMembersOrder(
node.body.body,
options.classExpressions ?? options.default!,
true,
);
},
TSInterfaceDeclaration(node): void {
validateMembersOrder(
node.body.body,
options.interfaces ?? options.default!,
false,
);
},
TSTypeLiteral(node): void {
validateMembersOrder(
node.members,
options.typeLiterals ?? options.default!,
false,
);
},
};
/* eslint-enable @typescript-eslint/no-non-null-assertion */
}
arrayConfig(memberTypes: string): JSONSchema.JSONSchema4¶
Parameters:
memberTypesstring
Returns: JSONSchema.JSONSchema4
Code
objectConfig(memberTypes: string): JSONSchema.JSONSchema4¶
Parameters:
memberTypesstring
Returns: JSONSchema.JSONSchema4
Code
(memberTypes: string): JSONSchema.JSONSchema4 => ({
type: 'object',
additionalProperties: false,
properties: {
memberTypes: {
oneOf: [arrayConfig(memberTypes), neverConfig],
},
optionalityOrder: {
$ref: '#/items/0/$defs/optionalityOrderOptions',
},
order: {
$ref: '#/items/0/$defs/orderOptions',
},
},
})
getNodeType(node: Member): MemberKind | null¶
Gets the node type.
Parameters:
nodeany: the node to be evaluated.
Calls:
functionExpressions.includes
Code
function getNodeType(node: Member): MemberKind | null {
switch (node.type) {
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
case AST_NODE_TYPES.TSMethodSignature:
return node.kind;
case AST_NODE_TYPES.TSCallSignatureDeclaration:
return 'call-signature';
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
return 'constructor';
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.TSPropertySignature:
return node.readonly ? 'readonly-field' : 'field';
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.AccessorProperty:
return 'accessor';
case AST_NODE_TYPES.PropertyDefinition:
return node.value && functionExpressions.includes(node.value.type)
? 'method'
: node.readonly
? 'readonly-field'
: 'field';
case AST_NODE_TYPES.TSIndexSignature:
return node.readonly ? 'readonly-signature' : 'signature';
case AST_NODE_TYPES.StaticBlock:
return 'static-initialization';
default:
return null;
}
}
getMemberRawName(member: | TSESTree.AccessorProperty | TSESTree.…, sourceCode: TSESLint.SourceCode): string¶
Gets the raw string value of a member's name
Calls:
getNameFromMember (from ../util)name.slice
Code
function getMemberRawName(
member:
| TSESTree.AccessorProperty
| TSESTree.MethodDefinition
| TSESTree.Property
| TSESTree.PropertyDefinition
| TSESTree.TSAbstractAccessorProperty
| TSESTree.TSAbstractMethodDefinition
| TSESTree.TSAbstractPropertyDefinition
| TSESTree.TSMethodSignature
| TSESTree.TSPropertySignature,
sourceCode: TSESLint.SourceCode,
): string {
const { name, type } = getNameFromMember(member, sourceCode);
if (type === MemberNameType.Quoted) {
return name.slice(1, -1);
}
if (type === MemberNameType.Private) {
return name.slice(1);
}
return name;
}
getMemberName(node: Member, sourceCode: TSESLint.SourceCode): string | null¶
Gets the member name based on the member type.
Parameters:
nodeany: the node to be evaluated.
Raw JSDoc
Calls:
getMemberRawNamegetNameFromIndexSignature (from ../util)
Code
function getMemberName(
node: Member,
sourceCode: TSESLint.SourceCode,
): string | null {
switch (node.type) {
case AST_NODE_TYPES.TSPropertySignature:
case AST_NODE_TYPES.TSMethodSignature:
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.AccessorProperty:
case AST_NODE_TYPES.PropertyDefinition:
return getMemberRawName(node, sourceCode);
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
return node.kind === 'constructor'
? 'constructor'
: getMemberRawName(node, sourceCode);
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
return 'new';
case AST_NODE_TYPES.TSCallSignatureDeclaration:
return 'call';
case AST_NODE_TYPES.TSIndexSignature:
return getNameFromIndexSignature(node);
case AST_NODE_TYPES.StaticBlock:
return 'static block';
default:
return null;
}
}
isMemberOptional(node: Member): boolean¶
Returns true if the member is optional based on the member type.
Parameters:
nodeany: the node to be evaluated.
Returns: undefined
Whether the member is optional, or false if it cannot be optional at all.
Raw JSDoc
Code
function isMemberOptional(node: Member): boolean {
switch (node.type) {
case AST_NODE_TYPES.TSPropertySignature:
case AST_NODE_TYPES.TSMethodSignature:
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.AccessorProperty:
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
return node.optional;
}
return false;
}
getMemberInitializer(node: Member): any¶
Parameters:
nodeMember
Returns: any
Code
getThisPropertyName(node: TSESTree.MemberExpression): any¶
Parameters:
nodeTSESTree.MemberExpression
Returns: any
Calls:
getStaticStringValue (from ../util)
Code
isEvaluatedLater(node: TSESTree.Node, initializer: TSESTree.Expression): boolean¶
Parameters:
nodeTSESTree.NodeinitializerTSESTree.Expression
Returns: boolean
Calls:
nullThrows (from ../util)
Code
function isEvaluatedLater(
node: TSESTree.Node,
initializer: TSESTree.Expression,
) {
for (
let current = node;
current !== initializer.parent;
current = nullThrows(current.parent, NullThrowsReasons.MissingParent)
) {
if (
current.type === AST_NODE_TYPES.ArrowFunctionExpression ||
current.type === AST_NODE_TYPES.ClassBody ||
current.type === AST_NODE_TYPES.FunctionExpression
) {
return true;
}
}
return false;
}
collectImmediateThisPropertyNames(initializer: TSESTree.Expression): Set<string>¶
Parameters:
initializerTSESTree.Expression
Returns: Set<string>
Calls:
forEachChildESTree (from ../util)isEvaluatedLatergetThisPropertyNamenames.add
Code
function collectImmediateThisPropertyNames(initializer: TSESTree.Expression) {
const names = new Set<string>();
forEachChildESTree(initializer, node => {
if (
node.type === AST_NODE_TYPES.MemberExpression &&
node.object.type === AST_NODE_TYPES.ThisExpression &&
!isEvaluatedLater(node, initializer)
) {
const name = getThisPropertyName(node);
if (name != null) {
names.add(name);
}
}
return null;
});
return names;
}
isBlockedByEarlierMemberReferences(member: Member, members: Member[], fromIndex: number, toIndex: number, sourceCode: TSESLint.SourceCode): boolean¶
Parameters:
memberMembermembersMember[]fromIndexnumbertoIndexnumbersourceCodeTSESLint.SourceCode
Returns: boolean
Calls:
getMemberInitializercollectImmediateThisPropertyNamesmembers.slice(fromIndex, toIndex).somegetMemberNamereferencedNames.has
Code
function isBlockedByEarlierMemberReferences(
member: Member,
members: Member[],
fromIndex: number,
toIndex: number,
sourceCode: TSESLint.SourceCode,
) {
const initializer = getMemberInitializer(member);
if (!initializer) {
return false;
}
const referencedNames = collectImmediateThisPropertyNames(initializer);
return members.slice(fromIndex, toIndex).some(other => {
const otherName = getMemberName(other, sourceCode);
return (
otherName != null &&
getMemberInitializer(other) != null &&
referencedNames.has(otherName)
);
});
}
getRankOrder(memberGroups: BaseMemberType[], orderConfig: MemberType[]): number¶
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
Parameters:
memberGroupsany: the valid names to be validated.orderConfigany: the current order to be validated.
Returns: undefined
Index of the matching member type in the order configuration.
Raw JSDoc
/**
* 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.
*/
Calls:
stack.shiftorderConfig.findIndexArray.isArraymemberType.includes
Internal Comments:
Code
function getRankOrder(
memberGroups: BaseMemberType[],
orderConfig: MemberType[],
): number {
let rank = -1;
const stack = [...memberGroups]; // Get a copy of the member groups
while (stack.length > 0 && rank === -1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const memberGroup = stack.shift()!;
rank = orderConfig.findIndex(memberType =>
Array.isArray(memberType)
? memberType.includes(memberGroup)
: memberType === memberGroup,
);
}
return rank;
}
getAccessibility(node: Member): Accessibility¶
Parameters:
nodeMember
Returns: Accessibility
Code
getRank(node: Member, orderConfig: MemberType[], supportsModifiers: boolean): number¶
Gets the rank of the node given the order.
Parameters:
nodeany: the node to be evaluated.orderConfigany: the current order to be validated.supportsModifiersany: a flag indicating whether the type supports modifiers (scope or accessibility) or not.
Raw JSDoc
Calls:
getNodeTypegetAccessibilitymemberGroups.pushgetRankOrder
Internal Comments:
// shouldn't happen but just in case, put it on the end
// Collect all existing member groups that apply to this node... (x2)
// (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) (x2)
// Constructors have no scope (x4)
// ...then get the rank order for those member groups based on the node
Code
function getRank(
node: Member,
orderConfig: MemberType[],
supportsModifiers: boolean,
): number {
const type = getNodeType(node);
if (
node.type === AST_NODE_TYPES.MethodDefinition &&
node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression
) {
return -1;
}
if (type == null) {
// shouldn't happen but just in case, put it on the end
return orderConfig.length - 1;
}
const abstract =
node.type === AST_NODE_TYPES.TSAbstractAccessorProperty ||
node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition ||
node.type === 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: BaseMemberType[] = [];
if (supportsModifiers) {
const decorated = 'decorators' in node && node.decorators.length > 0;
if (
decorated &&
(type === 'readonly-field' ||
type === 'field' ||
type === 'method' ||
type === 'accessor' ||
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);
}
groupMembersByType(members: Member[], memberTypes: MemberType[], supportsModifiers: boolean): Member[][]¶
Groups members into arrays of consecutive members with the same rank. If, for example, the memberSet parameter looks like the following...
Parameters:
memberSetany: The members to be grouped.memberTypeany: The configured order of member types.supportsModifiersany: It'll get passed to getRank().
Returns: undefined
The array of groups of members.
Examples:
interface Foo {a: x; B: x; c: x;
c(): void; B(): void; a(): void;
(): Baz;
new (): Bar; }
Raw JSDoc
/**
* Groups members into arrays of consecutive members with the same rank.
* If, for example, the memberSet parameter looks like the following...
* @example
* ```
* interface Foo {
* [a: string]: number;
*
* a: x;
* B: x;
* c: x;
*
* c(): void;
* B(): void;
* a(): void;
*
* (): Baz;
*
* new (): Bar;
* }
* ```
* ...the resulting array will look like: [[a, B, c], [c, B, a]].
* @param memberSet The members to be grouped.
* @param memberType The configured order of member types.
* @param supportsModifiers It'll get passed to getRank().
* @returns The array of groups of members.
*/
Calls:
members.mapgetRankmembers.forEachgroupedMembers.at(-1)?.pushgroupedMembers.push
Code
function groupMembersByType(
members: Member[],
memberTypes: MemberType[],
supportsModifiers: boolean,
): Member[][] {
const groupedMembers: Member[][] = [];
const memberRanks = members.map(member =>
getRank(member, memberTypes, supportsModifiers),
);
let previousRank: number | undefined = undefined;
members.forEach((member, index) => {
if (index === members.length - 1) {
return;
}
const rankOfCurrentMember = memberRanks[index];
const rankOfNextMember = memberRanks[index + 1];
if (rankOfCurrentMember === previousRank) {
groupedMembers.at(-1)?.push(member);
} else if (rankOfCurrentMember === rankOfNextMember) {
groupedMembers.push([member]);
previousRank = rankOfCurrentMember;
}
});
return groupedMembers;
}
getLowestRank(ranks: number[], target: number, order: MemberType[]): string¶
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.
Parameters:
ranksany: the existing ranks in the object.targetany: the minimum target rank to filter on.orderany: the current order to be validated.
Returns: undefined
the name(s) of the lowest possible rank without dashes (-).
Raw JSDoc
/**
* 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 minimum target rank to filter on.
* @param order the current order to be validated.
* @returns the name(s) of the lowest possible rank without dashes (-).
*/
Calls:
ranks.forEachMath.minArray.isArraylowestRanks.map(rank => rank.replaceAll('-', ' ')).join
Code
function getLowestRank(
ranks: number[],
target: number,
order: MemberType[],
): string {
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.replaceAll('-', ' ')).join(', ');
}
Internal helpers¶
Declared inside another function in this file.
checkGroupSort(members: Member[], groupOrder: MemberType[], supportsModifiers: boolean): Member[][] | null¶
Checks if the member groups are correctly sorted.
Parameters:
membersany: Members to be validated.groupOrderany: Group order to be validated.supportsModifiersany: A flag indicating whether the type supports modifiers (scope or accessibility) or not.
Returns: undefined
Array of member groups or null if one of the groups is not correctly sorted.
Raw JSDoc
/**
* 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.
*/
Calls:
getRankgetMemberNamecontext.reportgetLowestRankmemberGroups[memberGroups.length - 1].pushpreviousRanks.pushmemberGroups.push
Internal Comments:
// Find first member which isn't correctly sorted
// Works for 1st item because x < undefined === false for any x (typeof string)
// Same member group --> Push to existing member group array (x5)
// New member group --> Create new member group array (x4)
Code
function checkGroupSort(
members: Member[],
groupOrder: MemberType[],
supportsModifiers: boolean,
): Member[][] | null {
const previousRanks: number[] = [];
const memberGroups: Member[][] = [];
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, context.sourceCode);
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;
}
checkAlphaSort(members: Member[], order: AlphabeticalOrder): boolean¶
Checks if the members are alphabetically sorted.
Parameters:
membersany: Members to be validated.orderany: What order the members should be sorted in.
Returns: undefined
True if all members are correctly sorted.
Raw JSDoc
Calls:
members.forEachgetMemberNamenaturalOutOfOrderisBlockedByEarlierMemberReferencescontext.report
Internal Comments:
Code
function checkAlphaSort(
members: Member[],
order: AlphabeticalOrder,
): boolean {
let previousName = '';
let previousIndex = 0;
let isCorrectlySorted = true;
// Find first member which isn't correctly sorted
members.forEach((member, index) => {
const name = getMemberName(member, context.sourceCode);
// Note: Not all members have names
if (name) {
if (naturalOutOfOrder(name, previousName, order)) {
if (
isBlockedByEarlierMemberReferences(
member,
members,
previousIndex,
index,
context.sourceCode,
)
) {
return;
}
context.report({
node: member,
messageId: 'incorrectOrder',
data: {
beforeMember: previousName,
member: name,
},
});
isCorrectlySorted = false;
}
previousName = name;
previousIndex = index;
}
});
return isCorrectlySorted;
}
naturalOutOfOrder(name: string, previousName: string, order: AlphabeticalOrder): boolean¶
Parameters:
namestringpreviousNamestringorderAlphabeticalOrder
Returns: boolean
Calls:
name.toLowerCasepreviousName.toLowerCasenaturalCompare (from natural-compare)
Code
function naturalOutOfOrder(
name: string,
previousName: string,
order: AlphabeticalOrder,
): boolean {
if (name === previousName) {
return false;
}
switch (order) {
case 'alphabetically':
return name < previousName;
case 'alphabetically-case-insensitive':
return name.toLowerCase() < previousName.toLowerCase();
case 'natural':
return naturalCompare(name, previousName) !== 1;
case 'natural-case-insensitive':
return (
naturalCompare(name.toLowerCase(), previousName.toLowerCase()) !== 1
);
}
}
checkRequiredOrder(members: Member[], optionalityOrder: OptionalityOrder | undefined): boolean¶
Checks if the order of optional and required members is correct based on the given 'required' parameter.
Parameters:
membersany: Members to be validated.optionalityOrderany: Where to place optional members, if not intermixed.
Returns: undefined
True if all required and optional members are correctly sorted.
Raw JSDoc
/**
* 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.
*/
Calls:
members.findIndexisMemberOptionalcontext.reportgetMemberNamereport
Internal Comments:
// 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
Code
function checkRequiredOrder(
members: Member[],
optionalityOrder: OptionalityOrder | undefined,
): boolean {
const switchIndex = members.findIndex(
(member, i) =>
i && isMemberOptional(member) !== isMemberOptional(members[i - 1]),
);
const report = (member: Member): void =>
context.report({
loc: member.loc,
messageId: 'incorrectRequiredMembersOrder',
data: {
member: getMemberName(member, context.sourceCode),
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;
}
report(member: Member): void¶
Parameters:
memberMember
Returns: void
Calls:
context.report
Code
validateMembersOrder(members: Member[], orderConfig: OrderConfig, supportsModifiers: boolean): void¶
Validates if all members are correctly sorted.
Parameters:
membersany: Members to be validated.orderConfigany: Order config to be validated.supportsModifiersany: A flag indicating whether the type supports modifiers (scope or accessibility) or not.
Raw JSDoc
Calls:
Array.isArraygroupMembersByType(memberSet, memberTypes, supportsModifiers).forEachcheckAlphaSortcheckGroupSortcheckAlphaSortForAllMembersgrouped.mapcheckOrdermembers.findIndexisMemberOptionalcheckRequiredOrdermembers.slice
Internal Comments:
// Standardize config (x2)
/**
* It runs an alphabetic sort on the groups of the members of the class in the source code.
* @param memberSet The members in the class of the source code on which the grouping operation will be performed.
*/ (x2)
// returns true if everything is good and false if an error was reported (x2)
// Check order
Code
function validateMembersOrder(
members: Member[],
orderConfig: OrderConfig,
supportsModifiers: boolean,
): void {
if (orderConfig === 'never') {
return;
}
// Standardize config
let order: Order | undefined;
let memberTypes: string | MemberType[] | undefined;
let optionalityOrder: OptionalityOrder | undefined;
/**
* It runs an alphabetic sort on the groups of the members of the class in the source code.
* @param memberSet The members in the class of the source code on which the grouping operation will be performed.
*/
const checkAlphaSortForAllMembers = (memberSet: Member[]): undefined => {
const hasAlphaSort = !!(order && order !== 'as-written');
if (hasAlphaSort && Array.isArray(memberTypes)) {
groupMembersByType(memberSet, memberTypes, supportsModifiers).forEach(
members => {
checkAlphaSort(members, order as AlphabeticalOrder);
},
);
}
};
// returns true if everything is good and false if an error was reported
const checkOrder = (memberSet: Member[]): boolean => {
const hasAlphaSort = !!(order && order !== 'as-written');
// Check order
if (Array.isArray(memberTypes)) {
const grouped = checkGroupSort(
memberSet,
memberTypes,
supportsModifiers,
);
if (grouped == null) {
checkAlphaSortForAllMembers(members);
return false;
}
if (hasAlphaSort) {
grouped.map(groupMember =>
checkAlphaSort(groupMember, order as AlphabeticalOrder),
);
}
} else if (hasAlphaSort) {
return checkAlphaSort(memberSet, order as AlphabeticalOrder);
}
return false;
};
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);
}
}
checkAlphaSortForAllMembers(memberSet: Member[]): undefined¶
Parameters:
memberSetMember[]
Returns: undefined
Calls:
Array.isArraygroupMembersByType(memberSet, memberTypes, supportsModifiers).forEachcheckAlphaSort
Code
checkOrder(memberSet: Member[]): boolean¶
Parameters:
memberSetMember[]
Returns: boolean
Calls:
Array.isArraycheckGroupSortcheckAlphaSortForAllMembersgrouped.mapcheckAlphaSort
Internal Comments:
Code
(memberSet: Member[]): boolean => {
const hasAlphaSort = !!(order && order !== 'as-written');
// Check order
if (Array.isArray(memberTypes)) {
const grouped = checkGroupSort(
memberSet,
memberTypes,
supportsModifiers,
);
if (grouped == null) {
checkAlphaSortForAllMembers(members);
return false;
}
if (hasAlphaSort) {
grouped.map(groupMember =>
checkAlphaSort(groupMember, order as AlphabeticalOrder),
);
}
} else if (hasAlphaSort) {
return checkAlphaSort(memberSet, order as AlphabeticalOrder);
}
return false;
}
Interfaces¶
SortedOrderConfig¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
memberTypes |
'never' \| MemberType[] |
✓ | not shown |
optionalityOrder |
OptionalityOrder |
✓ | not shown |
order |
Order |
✓ | not shown |
Type Aliases¶
MessageIds¶
ReadonlyType¶
MemberKind¶
type MemberKind = | 'accessor'
| 'call-signature'
| 'constructor'
| 'field'
| 'get'
| 'method'
| 'set'
| 'signature'
| 'static-initialization'
| ReadonlyType;
DecoratedMemberKind¶
type DecoratedMemberKind = | 'accessor'
| 'field'
| 'get'
| 'method'
| 'set'
| Exclude<ReadonlyType, 'readonly-signature'>;
NonCallableMemberKind¶
type NonCallableMemberKind = Exclude<
MemberKind,
'constructor' | 'readonly-signature' | 'signature'
>;
MemberScope¶
Accessibility¶
BaseMemberType¶
type BaseMemberType = | `${Accessibility}-${Exclude<
MemberKind,
'readonly-signature' | 'signature' | 'static-initialization'
>}`
| `${Accessibility}-${MemberScope}-${NonCallableMemberKind}`
| `${Accessibility}-decorated-${DecoratedMemberKind}`
| `${MemberScope}-${NonCallableMemberKind}`
| `decorated-${DecoratedMemberKind}`
| MemberKind;
MemberType¶
AlphabeticalOrder¶
type AlphabeticalOrder = | 'alphabetically'
| 'alphabetically-case-insensitive'
| 'natural'
| 'natural-case-insensitive';
Order¶
OrderConfig¶
Member¶
OptionalityOrder¶
Options¶
type Options = [
{
classes?: OrderConfig;
classExpressions?: OrderConfig;
default?: OrderConfig;
interfaces?: OrderConfig;
typeLiterals?: OrderConfig;
},
];
Generated by Syntax Scribe