prettier-plugin-sort-members
Version:
588 lines (564 loc) • 16.1 kB
JavaScript
// index.ts
import { printers as estreePrinters } from "prettier/plugins/estree";
// lib/visit.ts
import { visitorKeys as esVisitorKeys } from "@typescript-eslint/visitor-keys";
import { VISITOR_KEYS as babelVisitorKeys } from "@babel/types";
var stopModifying = Symbol("stopModifying");
function visit(node, modifier) {
if (node?.type == null)
return node;
if (node.type === "File")
return {
...cloneNode(node),
program: visit(node.program, modifier)
};
const modified = modifier(node);
if (modified === stopModifying)
return node;
const updatedNode = cloneNode(modified);
const keys = new Set((esVisitorKeys[updatedNode.type] ?? []).concat(babelVisitorKeys[updatedNode.type] ?? []));
for (const key of keys) {
const k = key;
const child = updatedNode[k];
if (Array.isArray(child)) {
updatedNode[k] = child.map((c) => visit(c, modifier));
continue;
}
updatedNode[k] = visit(updatedNode[k], modifier);
}
return updatedNode;
}
function cloneNode(node) {
return { ...node };
}
// lib/comparator/comparator.ts
var Order = {
Less: -1,
Equal: 0,
Greater: 1
};
var C = {
by,
chain,
nop,
string,
number,
maybe,
capture
};
function nop() {
return Order.Equal;
}
function by(fn, comp) {
return (a, b) => {
return comp(fn(a), fn(b));
};
}
function chain(...comps) {
return (a, b) => {
for (const comp of comps) {
const res = comp(a, b);
if (res !== Order.Equal)
return res;
}
return Order.Equal;
};
}
function string(a, b) {
if (a < b)
return Order.Less;
if (a > b)
return Order.Greater;
return Order.Equal;
}
function number(a, b) {
if (a < b)
return Order.Less;
if (a > b)
return Order.Greater;
return Order.Equal;
}
function maybe(comp) {
return (a, b) => {
if (a == null) {
if (b == null)
return Order.Equal;
return Order.Greater;
}
if (b == null)
return Order.Less;
return comp(a, b);
};
}
function capture(pred) {
function then(comp) {
return (a, b) => {
if (pred(a) && pred(b))
return comp(a, b);
if (pred(a))
return Order.Less;
if (pred(b))
return Order.Greater;
return Order.Equal;
};
}
const captured = then(nop);
captured.then = then;
return captured;
}
// lib/ast/name-of.ts
import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/types";
import { isNode, isPrivateName, isStringLiteral } from "@babel/types";
// lib/ast/member-like.ts
import { AST_NODE_TYPES } from "@typescript-eslint/types";
var MemberLikeNodeTypesArray = [
AST_NODE_TYPES.AccessorProperty,
AST_NODE_TYPES.PropertyDefinition,
AST_NODE_TYPES.MethodDefinition,
AST_NODE_TYPES.TSAbstractMethodDefinition,
AST_NODE_TYPES.TSAbstractPropertyDefinition,
AST_NODE_TYPES.TSConstructSignatureDeclaration,
AST_NODE_TYPES.TSIndexSignature,
AST_NODE_TYPES.TSMethodSignature,
AST_NODE_TYPES.TSPropertySignature,
"ClassAccessorProperty",
"ClassMethod",
"ClassPrivateMethod",
"ClassPrivateProperty",
"ClassProperty",
"TSDeclareMethod"
];
var MemberTypes = Object.fromEntries(MemberLikeNodeTypesArray.map((type) => [type, type]));
// lib/ast/name-of.ts
function nameOf(n) {
if (n.type === MemberTypes.TSIndexSignature) {
const p = n.parameters.at(0);
if (p == undefined)
return "";
if (p.type !== AST_NODE_TYPES2.Identifier)
return "";
return p.name;
}
if ("key" in n) {
switch (n.key.type) {
case AST_NODE_TYPES2.Identifier:
case AST_NODE_TYPES2.PrivateIdentifier:
return n.key.name;
case AST_NODE_TYPES2.Literal: {
const value = n.key.value;
if (typeof value !== "string")
return "";
return value;
}
}
if (isNode(n.key)) {
switch (true) {
case isPrivateName(n.key):
if (n.key.id.type !== AST_NODE_TYPES2.Identifier)
return "";
return n.key.id.name;
case isStringLiteral(n.key):
return n.key.value;
}
}
}
return "";
}
// lib/comparator/key-identifier-name.ts
var keyIdentifierName = () => C.by(nameOf, C.string);
// lib/comparator/accessibility.ts
import bt from "@babel/types";
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/types";
function accessibility() {
return C.by(($) => {
if ("accessibility" in $) {
switch ($.accessibility) {
case "public":
return 0;
case "protected":
return 1;
case "private":
return 2;
}
}
switch ($.type) {
case MemberTypes.PropertyDefinition:
case MemberTypes.MethodDefinition:
case MemberTypes.AccessorProperty:
if ($.key.type === AST_NODE_TYPES3.PrivateIdentifier)
return 3;
break;
case MemberTypes.ClassPrivateMethod:
case MemberTypes.ClassPrivateProperty:
return 3;
case MemberTypes.ClassAccessorProperty:
if (bt.isPrivateName($.key))
return 3;
break;
}
return 0;
}, C.number);
}
// lib/comparator/method-kind.ts
function methodKind(opt) {
return C.by(($) => {
switch ($.type) {
case MemberTypes.TSMethodSignature:
case MemberTypes.MethodDefinition:
case MemberTypes.TSAbstractMethodDefinition:
case MemberTypes.ClassMethod:
case MemberTypes.ClassPrivateMethod:
case MemberTypes.TSDeclareMethod:
switch ($.kind) {
case "constructor":
return 0;
case "get":
return 1;
case "set":
return opt.keepGettersAndSettersTogether ? 1 : 2;
case "method":
return 3;
}
}
return 3;
}, C.number);
}
// lib/ast/class-member.ts
function decorated(node) {
switch (node.type) {
case MemberTypes.AccessorProperty:
case MemberTypes.PropertyDefinition:
case MemberTypes.MethodDefinition:
case MemberTypes.ClassAccessorProperty:
case MemberTypes.ClassProperty:
case MemberTypes.ClassPrivateProperty:
case MemberTypes.ClassMethod:
case MemberTypes.ClassPrivateMethod:
return (node.decorators?.length ?? 0) > 0;
}
return false;
}
function abstracted(node) {
switch (node.type) {
case MemberTypes.TSAbstractPropertyDefinition:
case MemberTypes.TSAbstractMethodDefinition:
return true;
case MemberTypes.ClassProperty:
case MemberTypes.TSDeclareMethod:
return node.abstract === true;
}
return false;
}
function hasStaticModifier(node) {
return "static" in node && node.static === true;
}
// lib/comparator/class-member.ts
function classMember() {
return C.by(($) => {
if (decorated($))
return 2;
if (hasStaticModifier($))
return 1;
if (abstracted($))
return 4;
return 3;
}, C.number);
}
// lib/ast/index.ts
import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/types";
var functionExpressions = [
AST_NODE_TYPES4.FunctionExpression,
AST_NODE_TYPES4.ArrowFunctionExpression
];
// lib/comparator/kind/field.ts
function isField(node) {
switch (node.type) {
case MemberTypes.TSPropertySignature:
return true;
case MemberTypes.PropertyDefinition:
case MemberTypes.TSAbstractPropertyDefinition:
case MemberTypes.ClassProperty:
case MemberTypes.ClassPrivateProperty:
return !node.value || !functionExpressions.includes(node.value.type);
default:
return false;
}
}
// lib/comparator/kind/constructor.ts
import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/types";
function isConstructor(node) {
switch (node.type) {
case MemberTypes.TSConstructSignatureDeclaration:
return true;
case MemberTypes.MethodDefinition:
return node.key.type === AST_NODE_TYPES5.Identifier && node.key.name === "constructor";
case MemberTypes.ClassMethod:
case MemberTypes.ClassPrivateMethod:
return node.key.type === "Identifier" && node.key.name === "constructor";
}
return false;
}
// lib/comparator/kind/method.ts
function isMethod(node) {
switch (node.type) {
case MemberTypes.TSMethodSignature:
case MemberTypes.MethodDefinition:
case MemberTypes.TSAbstractMethodDefinition:
case MemberTypes.TSDeclareMethod:
return true;
case MemberTypes.ClassMethod:
case MemberTypes.ClassPrivateMethod:
return true;
case MemberTypes.PropertyDefinition:
case MemberTypes.ClassProperty:
case MemberTypes.ClassPrivateProperty:
return node.value != null && functionExpressions.includes(node.value.type);
case MemberTypes.TSPropertySignature:
return true;
default:
return false;
}
}
// lib/comparator/kind/accessor.ts
function isAccessor(node) {
switch (node.type) {
case MemberTypes.AccessorProperty:
case MemberTypes.ClassAccessorProperty:
return true;
default:
return false;
}
}
// lib/comparator/constructor-params.ts
function constructorParams() {
return C.by(($) => {
if ($.type !== MemberTypes.TSConstructSignatureDeclaration)
return 0;
return $.params?.length ?? $.parameters.length;
}, C.number);
}
// lib/comparator/index.ts
function comparator(options) {
const alpha = options.sortMembersAlphabetically === true;
const keepGettersAndSettersTogether = options.keepGettersAndSettersTogether ?? false;
return C.chain(C.capture(node(MemberTypes.TSIndexSignature)), C.capture(isField), C.capture(isConstructor).then(constructorParams()), C.capture(isAccessor), C.capture(isMethod).then(methodKind({ keepGettersAndSettersTogether })), classMember(), accessibility(), alpha ? keyIdentifierName() : C.nop);
}
function node(key) {
return function(node2) {
return node2.type === key;
};
}
// lib/preprocess.ts
import { AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/types";
// lib/subclass.ts
import { AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/types";
function isExcludedSubclass(node2, opts) {
const options = opts ?? {};
if (node2.type !== AST_NODE_TYPES6.ClassDeclaration)
return false;
if (node2.superClass == null)
return false;
const spr = name(node2.superClass);
if (!("skipSortForSubclassOf" in options))
return false;
if (!Array.isArray(options.skipSortForSubclassOf))
return false;
return options.skipSortForSubclassOf.includes(spr);
}
function name(node2) {
switch (node2.type) {
case AST_NODE_TYPES6.Identifier:
return node2.name;
case AST_NODE_TYPES6.MemberExpression: {
const obj = name(node2.object);
if (obj == null)
return null;
if (node2.property.type === AST_NODE_TYPES6.Identifier) {
return obj + "." + node2.property.name;
}
}
}
return null;
}
// lib/put-getters-and-setters-together.ts
function putGettersAndSettersTogether(members) {
const getterNames = new Set(members.flatMap((m) => {
const name2 = nameOf(m);
if (!("kind" in m) || m.kind !== "get" || name2 === null)
return [];
return [name2];
}));
const setters = new Map(members.flatMap((m) => {
const name2 = nameOf(m);
if (!("kind" in m) || m.kind !== "set" || name2 === null)
return [];
return [[name2, m]];
}));
return members.flatMap((m) => {
const name2 = nameOf(m);
if (!("kind" in m) || name2 === null)
return [m];
switch (m.kind) {
case "get": {
const danglingSetter = setters.get(name2);
if (danglingSetter !== undefined)
return [m, danglingSetter];
return [m];
}
case "set": {
if (getterNames.has(name2))
return [];
return [m];
}
}
return [m];
});
}
// lib/ast/overloading.ts
import { TSESTree as TSESTree3 } from "@typescript-eslint/types";
import { isClassMethod, isNode as isNode2 } from "@babel/types";
function splitOverloads(members) {
const overloads = [];
const nonOverloads = [];
const implementedNames = new Set(members.filter((m) => categoryOf(m) === "implementation").map(nameOf).filter((n) => n !== ""));
for (const member of members) {
if (categoryOf(member) !== "definition") {
nonOverloads.push(member);
continue;
}
if (!implementedNames.has(nameOf(member))) {
nonOverloads.push(member);
continue;
}
overloads.push(member);
}
return [overloads, nonOverloads];
}
function categoryOf(member) {
if (member.type === MemberTypes.TSDeclareMethod)
return "definition";
if (member.type === MemberTypes.MethodDefinition) {
if (member.value.type === TSESTree3.AST_NODE_TYPES.TSEmptyBodyFunctionExpression)
return "definition";
return "implementation";
}
if (isNode2(member) && isClassMethod(member)) {
return "implementation";
}
return "others";
}
function groupOverloads(members) {
const map = new Map;
for (const member of members) {
const name2 = nameOf(member);
let overloads = map.get(name2);
if (!overloads) {
overloads = [];
map.set(name2, overloads);
}
overloads.push(member);
}
return map;
}
function mergeOverloads(overloads, nonOverloads) {
const staticOverloads = groupOverloads(overloads.filter(hasStaticModifier));
const nonStaticOverloads = groupOverloads(overloads.filter((m) => !hasStaticModifier(m)));
const body = [];
for (const no of nonOverloads) {
if (no.type !== MemberTypes.MethodDefinition && !(isNode2(no) && isClassMethod(no))) {
body.push(no);
continue;
}
const name2 = nameOf(no);
if (!name2) {
body.push(no);
continue;
}
const o = hasStaticModifier(no) ? staticOverloads.get(name2) : nonStaticOverloads.get(name2);
if (!o) {
body.push(no);
continue;
}
while (o.length) {
body.push(o.shift());
}
body.push(no);
}
for (const s of staticOverloads.values()) {
while (s.length) {
body.push(s.shift());
}
}
for (const s of nonStaticOverloads.values()) {
while (s.length) {
body.push(s.shift());
}
}
return body;
}
// lib/preprocess.ts
function preprocess(ast, options) {
const opt = options;
const memcomp = comparator(opt);
const comp = C.capture(memberNodes).then(memcomp);
return visit(ast, (node2) => {
switch (node2.type) {
case AST_NODE_TYPES7.TSInterfaceBody:
return {
...node2,
body: node2.body.slice().sort(comp)
};
case AST_NODE_TYPES7.ClassBody: {
const [overloads, nonOverloads] = splitOverloads(node2.body);
const sorted = nonOverloads.slice().sort(comp);
const arrangedNonOverloads = opt.keepGettersAndSettersTogether ? putGettersAndSettersTogether(sorted) : sorted;
const body = mergeOverloads(overloads, arrangedNonOverloads);
return {
...node2,
body
};
}
case AST_NODE_TYPES7.TSTypeLiteral:
return {
...node2,
members: node2.members.slice().sort(comp)
};
case AST_NODE_TYPES7.ClassDeclaration:
if (isExcludedSubclass(node2, options))
return stopModifying;
return node2;
}
return node2;
});
}
function memberNodes(node2) {
return membersSet.has(node2.type);
}
var membersSet = new Set(MemberLikeNodeTypesArray);
// index.ts
var originalPreprocess = estreePrinters.estree.preprocess ?? ((x) => x);
estreePrinters.estree.preprocess = (x, options) => preprocess(originalPreprocess(x, options), options);
var options = {
sortMembersAlphabetically: {
type: "boolean",
category: "Global",
default: false,
description: "Sort members alphabetically. Other criteria such as visibility precedes."
},
skipSortForSubclassOf: {
type: "string",
array: true,
category: "Global",
description: "Do not sort members of classes that are subclass of this class.",
default: [{ value: [] }]
},
keepGettersAndSettersTogether: {
type: "boolean",
category: "Global",
default: false,
description: "TBW"
}
};
export {
options
};