Skip to content

Latest commit

 

History

History
1120 lines (765 loc) · 26.4 KB

member-ordering.md

File metadata and controls

1120 lines (765 loc) · 26.4 KB

member-ordering

Require a consistent member declaration order.

A consistent ordering of fields, methods and constructors can make interfaces, type literals, classes and class expressions easier to read, navigate and edit.

Rule Details

This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured and ordered.

Grouping and sorting member groups

It allows to group members by their type (e.g. public-static-field, protected-static-field, private-static-field, public-instance-field, ...) and enforce a certain order for these groups. By default, their order is the same inside classes, classExpressions, interfaces and typeLiterals (note: not all member types apply to interfaces and typeLiterals). It is possible to define the order for any of those individually or to change the default order for all of them by setting the default option.

Sorting members

Besides grouping the members and sorting their groups, this rule also allows to sort the members themselves (e.g. a, b, c, ...). You have 2 options: Sort all of them while ignoring their type or sort them while respecting their types (e.g. sort all fields in an interface alphabetically).

Options

These options allow to specify how to group the members and sort their groups.

  • Sort groups, don't enforce member order: Use memberTypes
  • Sort members, don't enforce group order: Use order
  • Sort members within groups: Use memberTypes and order
type SortedOrderConfig = {
  memberTypes?: MemberType[] | 'never';
  order: 'alphabetically' | 'alphabetically-case-insensitive' | 'as-written';
};

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

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

See below for the possible definitions of MemberType.

Deprecated syntax

Note: There is a deprecated syntax to specify the member types as an array.

Member types (granular form)

There are multiple ways to specify the member types. The most explicit and granular form is the following:

[
  // Index signature
  "signature",

  // Fields
  "public-static-field",
  "protected-static-field",
  "private-static-field",
  "public-decorated-field",
  "protected-decorated-field",
  "private-decorated-field",
  "public-instance-field",
  "protected-instance-field",
  "private-instance-field",
  "public-abstract-field",
  "protected-abstract-field",
  "private-abstract-field",

  // Constructors
  "public-constructor",
  "protected-constructor",
  "private-constructor",

  // Getters
  "public-static-get",
  "protected-static-get",
  "private-static-get",

  "public-decorated-get",
  "protected-decorated-get",
  "private-decorated-get",

  "public-instance-get",
  "protected-instance-get",
  "private-instance-get",

  "public-abstract-get",
  "protected-abstract-get",
  "private-abstract-get",

  "public-get",
  "protected-get",
  "private-get",

  "static-get",
  "instance-get",
  "abstract-get",

  "decorated-get",

  "get",

  // Setters
  "public-static-set",
  "protected-static-set",
  "private-static-set",

  "public-decorated-set",
  "protected-decorated-set",
  "private-decorated-set",

  "public-instance-set",
  "protected-instance-set",
  "private-instance-set",

  "public-abstract-set",
  "protected-abstract-set",
  "private-abstract-set",

  "public-set",
  "protected-set",
  "private-set",

  "static-set",
  "instance-set",
  "abstract-set",

  "decorated-set",

  "set",

  // Methods
  "public-static-method",
  "protected-static-method",
  "private-static-method",
  "public-decorated-method",
  "protected-decorated-method",
  "private-decorated-method",
  "public-instance-method",
  "protected-instance-method",
  "private-instance-method",
  "public-abstract-method",
  "protected-abstract-method",
  "private-abstract-method"
]

Note: If you only specify some of the possible types, the non-specified ones can have any particular order. This means that they can be placed before, within or after the specified types and the linter won't complain about it.

Member group types (with accessibility, ignoring scope)

It is also possible to group member types by their accessibility (static, instance, abstract), ignoring their scope.

[
  // Index signature
  // No accessibility for index signature. See above.

  // Fields
  "public-field", // = ["public-static-field", "public-instance-field"]
  "protected-field", // = ["protected-static-field", "protected-instance-field"]
  "private-field", // = ["private-static-field", "private-instance-field"]

  // Constructors
  // Only the accessibility of constructors is configurable. See below.

  // Getters
  "public-get", // = ["public-static-get", "public-instance-get"]
  "protected-get", // = ["protected-static-get", "protected-instance-get"]
  "private-get", // = ["private-static-get", "private-instance-get"]

  // Setters
  "public-set", // = ["public-static-set", "public-instance-set"]
  "protected-set", // = ["protected-static-set", "protected-instance-set"]
  "private-set", // = ["private-static-set", "private-instance-set"]

  // Methods
  "public-method", // = ["public-static-method", "public-instance-method"]
  "protected-method", // = ["protected-static-method", "protected-instance-method"]
  "private-method" // = ["private-static-method", "private-instance-method"]
]

Member group types (with accessibility and a decorator)

It is also possible to group methods or fields with a decorator separately, optionally specifying their accessibility.

[
  // Index signature
  // No decorators for index signature.

  // Fields
  "public-decorated-field",
  "protected-decorated-field",
  "private-decorated-field",

  "decorated-field", // = ["public-decorated-field", "protected-decorated-field", "private-decorated-field"]

  // Constructors
  // There are no decorators for constructors.

  // Getters
  "public-decorated-get",
  "protected-decorated-get",
  "private-decorated-get",

  "decorated-get" // = ["public-decorated-get", "protected-decorated-get", "private-decorated-get"]

  // Setters
  "public-decorated-set",
  "protected-decorated-set",
  "private-decorated-set",

  "decorated-set" // = ["public-decorated-set", "protected-decorated-set", "private-decorated-set"]

  // Methods
  "public-decorated-method",
  "protected-decorated-method",
  "private-decorated-method",

  "decorated-method" // = ["public-decorated-method", "protected-decorated-method", "private-decorated-method"]
]

Member group types (with scope, ignoring accessibility)

Another option is to group the member types by their scope (public, protected, private), ignoring their accessibility.

[
  // Index signature
  // No scope for index signature. See above.

  // Fields
  "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"]
  "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"]
  "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"]

  // Constructors
  "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"]

  // Getters
  "static-get", // = ["public-static-get", "protected-static-get", "private-static-get"]
  "instance-get", // = ["public-instance-get", "protected-instance-get", "private-instance-get"]
  "abstract-get" // = ["public-abstract-get", "protected-abstract-get", "private-abstract-get"]

  // Setters
  "static-set", // = ["public-static-set", "protected-static-set", "private-static-set"]
  "instance-set", // = ["public-instance-set", "protected-instance-set", "private-instance-set"]
  "abstract-set" // = ["public-abstract-set", "protected-abstract-set", "private-abstract-set"]

  // Methods
  "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"]
  "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"]
  "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"]
]

Member group types (with scope and accessibility)

The third grouping option is to ignore both scope and accessibility.

[
  // Index signature
  // No grouping for index signature. See above.

  // Fields
  "field", // = ["public-static-field", "protected-static-field", "private-static-field", "public-instance-field", "protected-instance-field", "private-instance-field",
  //              "public-abstract-field", "protected-abstract-field", private-abstract-field"]

  // Constructors
  // Only the accessibility of constructors is configurable. See above.

  // Getters
  "get" // = ["public-static-get", "protected-static-get", "private-static-get", "public-instance-get", "protected-instance-get", "private-instance-get",
  //                "public-abstract-get", "protected-abstract-get", "private-abstract-get"]

  // Setters
  "set" // = ["public-static-set", "protected-static-set", "private-static-set", "public-instance-set", "protected-instance-set", "private-instance-set",
  //                "public-abstract-set", "protected-abstract-set", "private-abstract-set"]

  // Methods
  "method" // = ["public-static-method", "protected-static-method", "private-static-method", "public-instance-method", "protected-instance-method", "private-instance-method",
  //                "public-abstract-method", "protected-abstract-method", "private-abstract-method"]
]

Grouping different member types at the same rank

It is also possible to group different member types at the same rank.

[
  // Index signature
  "signature",

  // Fields
  "field",

  // Constructors
  "constructor",

  // Getters and Setters at the same rank
  ["get", "set"],

  // Methods
  "method"
]

Default configuration

The default configuration looks as follows:

{
  "default": [
    // Index signature
    "signature",

    // Fields
    "public-static-field",
    "protected-static-field",
    "private-static-field",

    "public-decorated-field",
    "protected-decorated-field",
    "private-decorated-field",

    "public-instance-field",
    "protected-instance-field",
    "private-instance-field",

    "public-abstract-field",
    "protected-abstract-field",
    "private-abstract-field",

    "public-field",
    "protected-field",
    "private-field",

    "static-field",
    "instance-field",
    "abstract-field",

    "decorated-field",

    "field",

    // Constructors
    "public-constructor",
    "protected-constructor",
    "private-constructor",

    "constructor",

    // Getters
    "public-static-get",
    "protected-static-get",
    "private-static-get",

    "public-decorated-get",
    "protected-decorated-get",
    "private-decorated-get",

    "public-instance-get",
    "protected-instance-get",
    "private-instance-get",

    "public-abstract-get",
    "protected-abstract-get",
    "private-abstract-get",

    "public-get",
    "protected-get",
    "private-get",

    "static-get",
    "instance-get",
    "abstract-get",

    "decorated-get",

    "get",

    // Setters
    "public-static-set",
    "protected-static-set",
    "private-static-set",

    "public-decorated-set",
    "protected-decorated-set",
    "private-decorated-set",

    "public-instance-set",
    "protected-instance-set",
    "private-instance-set",

    "public-abstract-set",
    "protected-abstract-set",
    "private-abstract-set",

    "public-set",
    "protected-set",
    "private-set",

    "static-set",
    "instance-set",
    "abstract-set",

    "decorated-set",

    "set",

    // Methods
    "public-static-method",
    "protected-static-method",
    "private-static-method",

    "public-decorated-method",
    "protected-decorated-method",
    "private-decorated-method",

    "public-instance-method",
    "protected-instance-method",
    "private-instance-method",

    "public-abstract-method",
    "protected-abstract-method",
    "private-abstract-method",

    "public-method",
    "protected-method",
    "private-method",

    "static-method",
    "instance-method",
    "abstract-method",

    "decorated-method",

    "method"
  ]
}

Note: The default configuration contains member group types which contain other member types (see above). This is intentional to provide better error messages.

Note: By default, the members are not sorted. If you want to sort them alphabetically, you have to provide a custom configuration.

Examples

Custom default configuration

Note: The default options are overwritten in these examples.

Configuration: { "default": ["signature", "method", "constructor", "field"] }

Incorrect examples
interface Foo {
  B: string; // -> field

  new (); // -> constructor

  A(): void; // -> method

  [Z: string]: any; // -> signature
}

Note: Wrong order.

type Foo = {
  B: string; // -> field

  // no constructor

  A(): void; // -> method

  // no signature
};

Note: Not all specified member types have to exist.

class Foo {
  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field

  constructor() {} // -> constructor

  public static A(): void {} // -> method
  public B(): void {} // -> method

  [Z: string]: any; // -> signature
}

Note: Accessibility or scope are ignored with this configuration.

const Foo = class {
  private C: string; // -> field
  public D: string; // -> field

  constructor() {} // -> constructor

  public static A(): void {} // -> method
  public B(): void {} // -> method

  [Z: string]: any; // -> signature

  protected static E: string; // -> field
};

Note: Not all members have to be grouped to find rule violations.

Correct examples
interface Foo {
  [Z: string]: any; // -> signature

  A(): void; // -> method

  new (); // -> constructor

  B: string; // -> field
}
type Foo = {
  // no signature

  A(): void; // -> method

  // no constructor

  B: string; // -> field
};
class Foo {
  [Z: string]: any; // -> signature

  public static A(): void {} // -> method
  public B(): void {} // -> method

  constructor() {} // -> constructor

  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field
}
const Foo = class {
  [Z: string]: any; // -> signature

  public static A(): void {} // -> method
  public B(): void {} // -> method

  constructor() {} // -> constructor

  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field
};

Configuration: { "default": ["public-instance-method", "public-static-field"] }

Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals.

Incorrect examples
class Foo {
  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public static E: string; // -> public static field

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  [Z: string]: any; // (irrelevant)

  public B(): void {} // -> public instance method
}

Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere.

const Foo = class {
  private C: string; // (irrelevant)

  [Z: string]: any; // (irrelevant)

  public static E: string; // -> public static field

  public D: string; // (irrelevant)

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  public B(): void {} // -> public instance method
};

Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere.

Correct examples
class Foo {
  public B(): void {} // -> public instance method

  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public static E: string; // -> public static field

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  [Z: string]: any; // (irrelevant)
}
const Foo = class {
  public B(): void {} // -> public instance method

  private C: string; // (irrelevant)

  [Z: string]: any; // (irrelevant)

  public D: string; // (irrelevant)

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  public static E: string; // -> public static field
};

Configuration: { "default": ["public-static-field", "static-field", "instance-field"] }

Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals.

Incorrect examples
class Foo {
  private E: string; // -> instance field

  private static B: string; // -> static field
  protected static C: string; // -> static field
  private static D: string; // -> static field

  public static A: string; // -> public static field

  [Z: string]: any; // (irrelevant)
}

Note: Public static fields should come first, followed by static fields and instance fields.

const foo = class {
  public T(): void {} // (irrelevant)

  private static B: string; // -> static field

  constructor() {} // (irrelevant)

  private E: string; // -> instance field

  protected static C: string; // -> static field
  private static D: string; // -> static field

  [Z: string]: any; // (irrelevant)

  public static A: string; // -> public static field
};

Note: Public static fields should come first, followed by static fields and instance fields.

Correct examples
class Foo {
  public static A: string; // -> public static field

  private static B: string; // -> static field
  protected static C: string; // -> static field
  private static D: string; // -> static field

  private E: string; // -> instance field
}
const foo = class {
  [Z: string]: any; // -> signature

  public static A: string; // -> public static field

  constructor() {} // -> constructor

  private static B: string; // -> static field
  protected static C: string; // -> static field
  private static D: string; // -> static field

  private E: string; // -> instance field

  public T(): void {} // -> method
};

Custom classes configuration

Note: If this is not set, the default will automatically be applied to classes as well. If a classes configuration is provided, only this configuration will be used for classes (i.e. nothing will be merged with default).

Note: The configuration for classes does not apply to class expressions (use classExpressions for them).

Configuration: { "classes": ["method", "constructor", "field"] }

Incorrect example
class Foo {
  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field

  constructor() {} // -> constructor

  public static A(): void {} // -> method
  public B(): void {} // -> method
}
Correct example
class Foo {
  public static A(): void {} // -> method
  public B(): void {} // -> method

  constructor() {} // -> constructor

  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field
}

Configuration: { "classes": ["public-instance-method", "public-static-field"] }

Incorrect example
class Foo {
  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public static E: string; // -> public static field

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  public B(): void {} // -> public instance method
}
Correct example
class Foo {
  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public B(): void {} // -> public instance method

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  public static E: string; // -> public static field
}

Custom classExpressions configuration

Note: If this is not set, the default will automatically be applied to classes expressions as well. If a classExpressions configuration is provided, only this configuration will be used for classExpressions (i.e. nothing will be merged with default).

Note: The configuration for classExpressions does not apply to classes (use classes for them).

Configuration: { "classExpressions": ["method", "constructor", "field"] }

Incorrect example
const foo = class {
  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field

  constructor() {} // -> constructor

  public static A(): void {} // -> method
  public B(): void {} // -> method
};
Correct example
const foo = class {
  public static A(): void {} // -> method
  public B(): void {} // -> method

  constructor() {} // -> constructor

  private C: string; // -> field
  public D: string; // -> field
  protected static E: string; // -> field
};

Configuration: { "classExpressions": ["public-instance-method", "public-static-field"] }

Incorrect example
const foo = class {
  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public static E: string; // -> public static field

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)

  public B(): void {} // -> public instance method
};
Correct example
const foo = class {
  private C: string; // (irrelevant)

  public D: string; // (irrelevant)

  public B(): void {} // -> public instance method

  public static E: string; // -> public static field

  constructor() {} // (irrelevant)

  public static A(): void {} // (irrelevant)
};

Custom interfaces configuration

Note: If this is not set, the default will automatically be applied to classes expressions as well. If a interfaces configuration is provided, only this configuration will be used for interfaces (i.e. nothing will be merged with default).

Note: The configuration for interfaces only allows a limited set of member types: signature, field, constructor and method.

Note: The configuration for interfaces does not apply to type literals (use typeLiterals for them).

Configuration: { "interfaces": ["signature", "method", "constructor", "field"] }

Incorrect example
interface Foo {
  B: string; // -> field

  new (); // -> constructor

  A(): void; // -> method

  [Z: string]: any; // -> signature
}
Correct example
interface Foo {
  [Z: string]: any; // -> signature

  A(): void; // -> method

  new (); // -> constructor

  B: string; // -> field
}

Custom typeLiterals configuration

Note: If this is not set, the default will automatically be applied to classes expressions as well. If a typeLiterals configuration is provided, only this configuration will be used for typeLiterals (i.e. nothing will be merged with default).

Note: The configuration for typeLiterals only allows a limited set of member types: signature, field, constructor and method.

Note: The configuration for typeLiterals does not apply to interfaces (use interfaces for them).

Configuration: { "typeLiterals": ["signature", "method", "constructor", "field"] }

Incorrect example
type Foo = {
  B: string; // -> field

  A(): void; // -> method

  new (); // -> constructor

  [Z: string]: any; // -> signature
};
Correct example
type Foo = {
  [Z: string]: any; // -> signature

  A(): void; // -> method

  new (); // -> constructor

  B: string; // -> field
};

Sorting alphabetically within member groups

It is possible to sort all members within a group alphabetically.

Configuration: { "default": { "memberTypes": <Default Order>, "order": "alphabetically" } }

This will apply the default order (see above) and enforce an alphabetic case-sensitive order within each group.

Incorrect examples
interface Foo {
  B: x;
  a: x;
  c: x;

  new (): Bar;
  (): Baz;

  B(): void;
  a(): void;
  c(): void;

  // Wrong group order, should be placed before all field definitions
  [a: string]: number;
}
interface Foo {
  [a: string]: number;

  B: x;
  a: x;
  c: x;

  new (): Bar;
  (): Baz;

  // Wrong alphabetic order within group
  c(): void;
  B(): void;
  a(): void;
}

Sorting alphabetically while ignoring member groups

It is also possible to sort all members and ignore the member groups completely.

Configuration: { "default": { "memberTypes": "never", "order": "alphabetically" } }

Incorrect example
interface Foo {
  b(): void;
  a: b;

  [a: string]: number; // Order doesn't matter (no sortable identifier)
  new (): Bar; // Order doesn't matter (no sortable identifier)
  (): Baz; // Order doesn't matter (no sortable identifier)
}

Note: Wrong alphabetic order b(): void should come after a: b.

Sorting alphabetically case-insensitive within member groups

It is possible to sort all members within a group alphabetically with case insensitivity.

Configuration: { "default": { "memberTypes": <Default Order>, "order": "alphabetically-case-insensitive" } }

This will apply the default order (see above) and enforce an alphabetic case-insensitive order within each group.

Incorrect examples
interface Foo {
  a: x;
  B: x;
  c: x;

  new (): Bar;
  (): Baz;

  a(): void;
  b(): void;
  C(): void;

  // Wrong group order, should be placed before all field definitions
  [a: string]: number;
}
interface Foo {
  [a: string]: number;

  a: x;
  B: x;
  c: x;

  new (): Bar;
  (): Baz;

  // Wrong alphabetic order within group
  C(): void;
  b(): void;
  a(): void;
}

Sorting alphabetically case-insensitive while ignoring member groups

It is also possible to sort all members with case insensitivity and ignore the member groups completely.

Configuration: { "default": { "memberTypes": "never", "order": "alphabetically-case-insensitive" } }

Incorrect example
interface Foo {
  B(): void;
  a: number;

  [a: string]: number; // Order doesn't matter (no sortable identifier)
  new (): Bar; // Order doesn't matter (no sortable identifier)
  (): Baz; // Order doesn't matter (no sortable identifier)
}

Note: Wrong alphabetic order B(): void should come after a: number.

When Not To Use It

If you don't care about the general structure of your classes and interfaces, then you will not need this rule.

Related To

Attributes

  • ✅ Recommended
  • 🔧 Fixable
  • 💭 Requires type information