UNPKG

prettier-plugin-sort-members

Version:
588 lines (564 loc) 16.1 kB
// 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 };