UNPKG

sortier

Version:
320 lines (319 loc) 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sortContents = sortContents; const array_utils_js_1 = require("../../utilities/array-utils.js"); const sort_utils_js_1 = require("../../utilities/sort-utils.js"); var AccessibilityOption; (function (AccessibilityOption) { AccessibilityOption[AccessibilityOption["Public"] = 0] = "Public"; AccessibilityOption[AccessibilityOption["Protected"] = 1] = "Protected"; AccessibilityOption[AccessibilityOption["Private"] = 2] = "Private"; })(AccessibilityOption || (AccessibilityOption = {})); var KindOption; (function (KindOption) { KindOption[KindOption["Property"] = 0] = "Property"; KindOption[KindOption["Constructor"] = 1] = "Constructor"; KindOption[KindOption["Method"] = 2] = "Method"; })(KindOption || (KindOption = {})); function sortContents(className, classItems, comments, fileContents, options) { return new ClassContentsSorter(className || "", classItems, comments, fileContents, options).sort(); } class ClassContentsSorter { className; classItems; comments; fileContents; options; constructor(className, classItems, comments, fileContents, options) { this.className = className; this.classItems = classItems; this.comments = comments; this.fileContents = fileContents; this.options = this.getValidatedOptions(options); } sort() { const possibleSortableItems = this.classItems.map((value) => { switch (value.type) { case "ClassProperty": case "MethodDefinition": case "PropertyDefinition": { const key = value?.key?.name; if (key != null) { return { accessModifier: this.getAccessModifier(value.accessibility), isStatic: value.static || false, key: key, kind: this.getKindOption(value), overrideIndex: this.getOverrideIndex(value), range: value.range, }; } return null; } default: return null; } }); const sortableItems = possibleSortableItems.filter((value) => { return value != null; }); const newFileContents = this.sortItems(sortableItems, this.comments, this.fileContents); return newFileContents; } getValidatedOptions(partialOptions) { let overrides = ["*"]; if (partialOptions.overrides != null) { overrides = partialOptions.overrides; if (overrides.indexOf("*") === -1) { overrides = overrides.slice(); overrides.push("*"); } } return { isAscending: partialOptions.isAscending == null ? true : partialOptions.isAscending, order: partialOptions.order || "alpha", overrides: overrides, }; } getAccessModifier(accessibility) { switch (accessibility) { case "private": return AccessibilityOption.Private; case "protected": return AccessibilityOption.Protected; case "public": return AccessibilityOption.Public; default: return AccessibilityOption.Public; } } getOverrideIndex(node) { const itemsToSearch = [node.key.name]; let index = -1; for (const item of itemsToSearch) { index = this.options.overrides.indexOf(item); if (index !== -1) { return index; } } return this.options.overrides.indexOf("*"); } sortItems(classItems, comments, fileContents) { const isUsage = this.options.order === "usage"; const callOrder = this.getClassItemOrder(); const sortedTypes = classItems.slice(); sortedTypes.sort((a, b) => { const groupComparison = this.compareGroups(a, b); if (groupComparison !== 0) { return groupComparison; } const overrideComparison = this.compareOverrides(a, b); if (overrideComparison !== 0) { return overrideComparison; } let callComparison = 0; const isAStaticProperty = a.kind === KindOption.Property; const isBStaticProperty = b.kind === KindOption.Property; if (isUsage || (isAStaticProperty && isBStaticProperty)) { callComparison = this.compareMethodCallers(a, b, callOrder); if (callComparison !== 0) { return callComparison; } } const stringComparison = (0, sort_utils_js_1.compare)(a.key, b.key); if (isUsage || this.options.isAscending) { return stringComparison; } else { return -1 * stringComparison; } }); return (0, sort_utils_js_1.reorderValues)(fileContents, comments, classItems, sortedTypes); } getClassItemOrder() { // Split the list into static properties and everything else as static // properties cause build failures when depending on one another out of order const properties = []; const everythingElse = []; for (const classItem of this.classItems) { if (this.getKindOption(classItem) === KindOption.Property) { properties.push(classItem); } else { everythingElse.push(classItem); } } // Sort both arrays const comparisonFunction = (a, b) => { return (0, sort_utils_js_1.compare)(a.key.name, b.key.name); }; properties.sort(comparisonFunction); everythingElse.sort(comparisonFunction); // Determine the order of the items const staticOrder = this.orderItems(properties, true, true); const everythingElseOrder = this.orderItems(everythingElse, false, false); // Merge and dedupe const totalCallOrder = [...staticOrder, ...everythingElseOrder]; array_utils_js_1.ArrayUtils.dedupe(totalCallOrder); return totalCallOrder; } compareGroups(a, b) { // Static methods if (a.isStatic !== b.isStatic) { if (a.isStatic) { return -1; } else { return 1; } } // Kinds if (a.kind !== b.kind) { return a.kind - b.kind; } // Access modifiers if (a.accessModifier !== b.accessModifier) { return a.accessModifier - b.accessModifier; } return 0; } compareOverrides(a, b) { return a.overrideIndex - b.overrideIndex; } compareMethodCallers(a, b, methodToCallers) { return methodToCallers.indexOf(a.key) - methodToCallers.indexOf(b.key); } getKindOption(node) { if (node.kind === "constructor") { return KindOption.Constructor; } if (node.type === "ClassProperty" || node.type === "PropertyDefinition") { if (node.value != null && node.value.type === "ArrowFunctionExpression") { return KindOption.Method; } else { return KindOption.Property; } } return KindOption.Method; } orderItems(sortedClassItems, isSiblingSort, isProperties) { // Storage of the overall call order as read from top to bottom, left to right const overallCallOrder = []; // Map of method names to parent information const keyToNode = new Map(); // Figure out what parents which methods have and break any cycles for (const classItem of sortedClassItems) { const methodName = classItem.key.name; const calls = this.getCalleeOrder([classItem]); if (isSiblingSort) { calls.sort(); if (this.options.order === "alpha" && !this.options.isAscending) { calls.reverse(); } } overallCallOrder.push(...calls); for (const call of calls) { if (call === methodName) { continue; } const parents = keyToNode.get(call); if (parents == null) { keyToNode.set(call, new Set([methodName])); } else { let addToParentList = true; const ancestorStack = [methodName]; while (addToParentList && ancestorStack.length !== 0) { const parents = keyToNode.get(ancestorStack.pop()); if (parents == null) { continue; } for (const a of parents) { if (call === a) { addToParentList = false; break; } ancestorStack.push(a); } } if (addToParentList) { parents.add(methodName); } } } // Create the parent node const parentNode = keyToNode.get(methodName); if (parentNode == null) { keyToNode.set(methodName, new Set()); } } //dedupe the overallCallOrder array array_utils_js_1.ArrayUtils.dedupe(overallCallOrder); // Now go through all nodes, remove the root nodes and push them into the resulting // call order array in order based on overallCallOrder const resultingCallOrder = []; while (keyToNode.size !== 0) { const nextGroup = []; for (const keyNodePair of keyToNode) { if (keyNodePair[1].size === 0) { nextGroup.push(keyNodePair[0]); } } nextGroup.sort((a, b) => { const aIndex = overallCallOrder.indexOf(a); const bIndex = overallCallOrder.indexOf(b); return aIndex - bIndex; }); if (!isProperties && this.options.isAscending) { resultingCallOrder.push(...nextGroup); } else { resultingCallOrder.unshift(...nextGroup); } for (const key of nextGroup) { keyToNode.delete(key); for (const parents of keyToNode.values()) { parents.delete(key); } } } return resultingCallOrder; } getCalleeOrder(nodes) { const memberExpressionOrder = []; for (const node of nodes) { if (node == null) { continue; } // If it's a literal or some other calling type thing else if (node.object != null && (node.object.type === "ThisExpression" || node.object.name === this.className) && node.type === "MemberExpression" && node.property.name != null) { memberExpressionOrder.push(node.property.name); } // Check if it contains things to call into else { const nodeValues = Object.values(node).flat(); const nodesToInvestigate = nodeValues.filter((possibleChildNode) => { const isAstNode = possibleChildNode != null && typeof possibleChildNode === "object" && "type" in possibleChildNode; return isAstNode; }); if (nodesToInvestigate.length > 0) { nodesToInvestigate.sort((a, b) => { const lineDiff = a.loc.start.line - b.loc.start.line; if (lineDiff === 0) { return a.loc.start.column - b.loc.start.column; } else { return lineDiff; } }); memberExpressionOrder.push(...this.getCalleeOrder(nodesToInvestigate)); } } } return memberExpressionOrder; } }