astro-ghostcms/.pnpm-store/v3/files/55/17aa93dc4bb0a9f9d90ca0c247c...

747 lines
27 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
"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