Skip to content

Commit

Permalink
feat(eslint-plugin): sort members alphabetically
Browse files Browse the repository at this point in the history
  • Loading branch information
timkraut committed Mar 22, 2019
1 parent 00571e9 commit c3d7b81
Show file tree
Hide file tree
Showing 4 changed files with 1,137 additions and 182 deletions.
181 changes: 127 additions & 54 deletions packages/eslint-plugin/src/rules/member-ordering.ts
@@ -1,8 +1,15 @@
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree';
import * as util from '../util';

type MessageIds = 'incorrectOrder';
type OrderConfig = string[] | 'never';
type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder';

interface SortedOrderConfig {
memberTypes: string[];
order: string;
}

type OrderConfig = string[] | SortedOrderConfig | 'never';

type Options = [
{
default?: OrderConfig;
Expand Down Expand Up @@ -37,6 +44,32 @@ const allMemberTypes = ['field', 'method', 'constructor'].reduce<string[]>(
[],
);

const neverConfig = {
enum: ['never'],
};

const allMemberTypesArrayConfig = {
type: 'array',
items: {
enum: allMemberTypes,
},
};

const allMemberTypesObjectConfig = {
type: 'object',
properties: {
memberTypes: {
type: 'array',
items: {
enum: allMemberTypes,
},
},
order: {
enum: ['alphabetically'],
},
},
};

export default util.createRule<Options, MessageIds>({
name: 'member-ordering',
meta: {
Expand All @@ -49,6 +82,8 @@ export default util.createRule<Options, MessageIds>({
},
messages: {
incorrectOrder:
'Member "{{member}}" should be declared before member "{{beforeMember}}".',
incorrectGroupOrder:
'Member {{name}} should be declared before all {{rank}} definitions.',
},
schema: [
Expand All @@ -57,67 +92,37 @@ export default util.createRule<Options, MessageIds>({
properties: {
default: {
oneOf: [
{
enum: ['never'],
},
{
type: 'array',
items: {
enum: allMemberTypes,
},
},
neverConfig,
allMemberTypesArrayConfig,
allMemberTypesObjectConfig,
],
},
classes: {
oneOf: [
{
enum: ['never'],
},
{
type: 'array',
items: {
enum: allMemberTypes,
},
},
neverConfig,
allMemberTypesArrayConfig,
allMemberTypesObjectConfig,
],
},
classExpressions: {
oneOf: [
{
enum: ['never'],
},
{
type: 'array',
items: {
enum: allMemberTypes,
},
},
neverConfig,
allMemberTypesArrayConfig,
allMemberTypesObjectConfig,
],
},
interfaces: {
oneOf: [
{
enum: ['never'],
},
{
type: 'array',
items: {
enum: ['field', 'method', 'constructor'],
},
},
neverConfig,
allMemberTypesArrayConfig,
allMemberTypesObjectConfig,
],
},
typeLiterals: {
oneOf: [
{
enum: ['never'],
},
{
type: 'array',
items: {
enum: ['field', 'method', 'constructor'],
},
},
neverConfig,
allMemberTypesArrayConfig,
allMemberTypesObjectConfig,
],
},
},
Expand Down Expand Up @@ -328,29 +333,97 @@ export default util.createRule<Options, MessageIds>({
* Validates if all members are correctly sorted.
*
* @param members Members to be validated.
* @param order Current order 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: (TSESTree.ClassElement | TSESTree.TypeElement)[],
order: OrderConfig,
orderConfig: OrderConfig,
supportsModifiers: boolean,
): void {
if (members && order !== 'never') {
if (members && orderConfig !== 'never') {
if (!Array.isArray(orderConfig)) {
// = ObjectConfig --> Members should be sorted
if (!orderConfig.order) {
return; // order is required for sorting
} else if (orderConfig.order !== 'alphabetically') {
// TODO Verify if this is the right way to communicate error (might be already done by ESLint itself)
throw new Error(
'Invalid configuration for order. Only "alphabetically" is allowed as value currently.',
);
}

if (
!orderConfig.memberTypes &&
orderConfig.order === 'alphabetically'
) {
// = Sort without grouping
// Find first member which isn't correctly sorted
let previousNames: string[] = [];

members.forEach(member => {
const name = getMemberName(member);

if (name && name !== 'constructor' && name !== 'new') {
// Don't compare null
// Works for 1st item because x < undefined === false for any x (typeof string)
if (name < previousNames[previousNames.length - 1]) {
context.report({
node: member,
messageId: 'incorrectOrder',
data: {
member: name,
beforeMember: previousNames.find(
previousName => name < previousName,
),
},
});
} else {
previousNames.push(name);
}
}
});
} else if (Array.isArray(orderConfig.memberTypes)) {
// Sort with grouping
// TODO Sort all members with grouping
} else {
// TODO Verify if this is the right way to communicate error (might be already done by ESLint itself)
throw new Error(
'Invalid configuration for memberTypes. It should be an array.',
);
}
}

let memberTypeGroupsOrder: string[] = [];

if (Array.isArray(orderConfig)) {
memberTypeGroupsOrder = orderConfig;
} else {
memberTypeGroupsOrder = orderConfig.memberTypes || [];
}

const previousRanks: number[] = [];

// Find first member which isn't correctly sorted
members.forEach(member => {
const rank = getRank(member, order, supportsModifiers);
const rank = getRank(
member,
memberTypeGroupsOrder,
supportsModifiers,
);

if (rank !== -1) {
if (rank < previousRanks[previousRanks.length - 1]) {
context.report({
node: member,
messageId: 'incorrectOrder',
messageId: 'incorrectGroupOrder',
data: {
name: getMemberName(member),
rank: getLowestRank(previousRanks, rank, order),
rank: getLowestRank(
previousRanks,
rank,
memberTypeGroupsOrder,
),
},
});
} else {
Expand Down

0 comments on commit c3d7b81

Please sign in to comment.