Skip to content

⬅️ Back to Table of Contents

📄 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

export default createRule<Options, MessageIds>({ ... })
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:

  • context any
  • [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:

  • getRank
  • getMemberName
  • context.report
  • getLowestRank
  • memberGroups[memberGroups.length - 1].push
  • previousRanks.push
  • memberGroups.push
  • members.forEach
  • naturalOutOfOrder
  • isBlockedByEarlierMemberReferences
  • name.toLowerCase
  • previousName.toLowerCase
  • naturalCompare (from natural-compare)
  • members.findIndex
  • isMemberOptional
  • report
  • Array.isArray
  • groupMembersByType(memberSet, memberTypes, supportsModifiers).forEach
  • checkAlphaSort
  • checkGroupSort
  • checkAlphaSortForAllMembers
  • grouped.map
  • checkOrder
  • checkRequiredOrder
  • members.slice
  • validateMembersOrder

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:

  • memberTypes string

Returns: JSONSchema.JSONSchema4

Code
(memberTypes: string): JSONSchema.JSONSchema4 => ({
  type: 'array',
  items: {
    oneOf: [
      {
        $ref: memberTypes,
      },
      {
        type: 'array',
        items: {
          $ref: memberTypes,
        },
      },
    ],
  },
})

objectConfig(memberTypes: string): JSONSchema.JSONSchema4

Parameters:

  • memberTypes string

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:

  • node any: the node to be evaluated.
Raw JSDoc
/**
 * Gets the node type.
 *
 * @param node 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

Raw JSDoc
/**
 * 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:

  • node any: the node to be evaluated.
Raw JSDoc
/**
 * Gets the member name based on the member type.
 *
 * @param node the node to be evaluated.
 */

Calls:

  • getMemberRawName
  • getNameFromIndexSignature (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:

  • node any: the node to be evaluated.

Returns: undefined Whether the member is optional, or false if it cannot be optional at all.

Raw JSDoc
/**
 * 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.
 */
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:

  • node Member

Returns: any

Code
function getMemberInitializer(node: Member) {
  switch (node.type) {
    case AST_NODE_TYPES.AccessorProperty:
    case AST_NODE_TYPES.PropertyDefinition:
      return node.value;
  }
  return undefined;
}

getThisPropertyName(node: TSESTree.MemberExpression): any

Parameters:

  • node TSESTree.MemberExpression

Returns: any

Calls:

  • getStaticStringValue (from ../util)
Code
function getThisPropertyName(node: TSESTree.MemberExpression) {
  return node.computed
    ? getStaticStringValue(node.property)
    : node.property.name;
}

isEvaluatedLater(node: TSESTree.Node, initializer: TSESTree.Expression): boolean

Parameters:

  • node TSESTree.Node
  • initializer TSESTree.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:

  • initializer TSESTree.Expression

Returns: Set<string>

Calls:

  • forEachChildESTree (from ../util)
  • isEvaluatedLater
  • getThisPropertyName
  • names.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:

  • member Member
  • members Member[]
  • fromIndex number
  • toIndex number
  • sourceCode TSESLint.SourceCode

Returns: boolean

Calls:

  • getMemberInitializer
  • collectImmediateThisPropertyNames
  • members.slice(fromIndex, toIndex).some
  • getMemberName
  • referencedNames.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:

  • memberGroups any: the valid names to be validated.
  • orderConfig any: 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.shift
  • orderConfig.findIndex
  • Array.isArray
  • memberType.includes

Internal Comments:

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)

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:

  • node Member

Returns: Accessibility

Code
function getAccessibility(node: Member): Accessibility {
  if ('accessibility' in node && node.accessibility) {
    return node.accessibility;
  }
  if ('key' in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) {
    return '#private';
  }
  return 'public';
}

getRank(node: Member, orderConfig: MemberType[], supportsModifiers: boolean): number

Gets the rank of the node given the order.

Parameters:

  • node any: the node to be evaluated.
  • orderConfig any: the current order to be validated.
  • supportsModifiers any: a flag indicating whether the type supports modifiers (scope or accessibility) or not.
Raw JSDoc
/**
 * 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.
 */

Calls:

  • getNodeType
  • getAccessibility
  • memberGroups.push
  • getRankOrder

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:

  • memberSet any: The members to be grouped.
  • memberType any: The configured order of member types.
  • supportsModifiers any: 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; }

...the resulting array will look like: [[a, B, c], [c, B, a]].

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.map
  • getRank
  • members.forEach
  • groupedMembers.at(-1)?.push
  • groupedMembers.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:

  • ranks any: the existing ranks in the object.
  • target any: the minimum target rank to filter on.
  • order any: 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.forEach
  • Math.min
  • Array.isArray
  • lowestRanks.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:

  • members any: Members to be validated.
  • groupOrder any: Group order to be validated.
  • supportsModifiers any: 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:

  • getRank
  • getMemberName
  • context.report
  • getLowestRank
  • memberGroups[memberGroups.length - 1].push
  • previousRanks.push
  • memberGroups.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:

  • members any: Members to be validated.
  • order any: What order the members should be sorted in.

Returns: undefined True if all members are correctly sorted.

Raw JSDoc
/**
     * 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.
     */

Calls:

  • members.forEach
  • getMemberName
  • naturalOutOfOrder
  • isBlockedByEarlierMemberReferences
  • context.report

Internal Comments:

// Find first member which isn't correctly sorted (x4)
// Note: Not all members have names

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:

  • name string
  • previousName string
  • order AlphabeticalOrder

Returns: boolean

Calls:

  • name.toLowerCase
  • previousName.toLowerCase
  • naturalCompare (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:

  • members any: Members to be validated.
  • optionalityOrder any: 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.findIndex
  • isMemberOptional
  • context.report
  • getMemberName
  • report

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:

  • member Member

Returns: void

Calls:

  • context.report
Code
(member: Member): void =>
        context.report({
          loc: member.loc,
          messageId: 'incorrectRequiredMembersOrder',
          data: {
            member: getMemberName(member, context.sourceCode),
            optionalOrRequired:
              optionalityOrder === 'required-first' ? 'required' : 'optional',
          },
        })

validateMembersOrder(members: Member[], orderConfig: OrderConfig, supportsModifiers: boolean): void

Validates if all members are correctly sorted.

Parameters:

  • members any: Members to be validated.
  • orderConfig any: Order config to be validated.
  • supportsModifiers any: A flag indicating whether the type supports modifiers (scope or accessibility) or not.
Raw JSDoc
/**
     * 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.
     */

Calls:

  • Array.isArray
  • groupMembersByType(memberSet, memberTypes, supportsModifiers).forEach
  • checkAlphaSort
  • checkGroupSort
  • checkAlphaSortForAllMembers
  • grouped.map
  • checkOrder
  • members.findIndex
  • isMemberOptional
  • checkRequiredOrder
  • members.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:

  • memberSet Member[]

Returns: undefined

Calls:

  • Array.isArray
  • groupMembersByType(memberSet, memberTypes, supportsModifiers).forEach
  • checkAlphaSort
Code
(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);
            },
          );
        }
      }

checkOrder(memberSet: Member[]): boolean

Parameters:

  • memberSet Member[]

Returns: boolean

Calls:

  • Array.isArray
  • checkGroupSort
  • checkAlphaSortForAllMembers
  • grouped.map
  • checkAlphaSort

Internal Comments:

// Check order

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
interface SortedOrderConfig {
  memberTypes?: 'never' | MemberType[];
  optionalityOrder?: OptionalityOrder;
  order?: Order;
}

Properties

Name Type Optional Description
memberTypes 'never' \| MemberType[] not shown
optionalityOrder OptionalityOrder not shown
order Order not shown

Type Aliases

MessageIds

type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder' | 'incorrectRequiredMembersOrder';

ReadonlyType

type ReadonlyType = 'readonly-field' | 'readonly-signature';

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

type MemberScope = 'abstract' | 'instance' | 'static';

Accessibility

type Accessibility = '#private' | TSESTree.Accessibility;

BaseMemberType

type BaseMemberType = | `${Accessibility}-${Exclude<
      MemberKind,
      'readonly-signature' | 'signature' | 'static-initialization'
    >}`
  | `${Accessibility}-${MemberScope}-${NonCallableMemberKind}`
  | `${Accessibility}-decorated-${DecoratedMemberKind}`
  | `${MemberScope}-${NonCallableMemberKind}`
  | `decorated-${DecoratedMemberKind}`
  | MemberKind;

MemberType

type MemberType = BaseMemberType | BaseMemberType[];

AlphabeticalOrder

type AlphabeticalOrder = | 'alphabetically'
  | 'alphabetically-case-insensitive'
  | 'natural'
  | 'natural-case-insensitive';

Order

type Order = 'as-written' | AlphabeticalOrder;

OrderConfig

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

Member

type Member = TSESTree.ClassElement | TSESTree.TypeElement;

OptionalityOrder

type OptionalityOrder = 'optional-first' | 'required-first';

Options

type Options = [
  {
    classes?: OrderConfig;
    classExpressions?: OrderConfig;
    default?: OrderConfig;
    interfaces?: OrderConfig;
    typeLiterals?: OrderConfig;
  },
];

Generated by Syntax Scribe