tsfmt 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/build-plugins/transformGenericsPlugin.js +86 -0
  3. package/dist/cli.js +173 -0
  4. package/dist/core/ast/ASTAnalyzer.js +136 -0
  5. package/dist/core/ast/ASTTransformer.js +85 -0
  6. package/dist/core/ast/DependencyResolver.js +144 -0
  7. package/dist/core/config/ConfigDefaults.js +178 -0
  8. package/dist/core/config/ConfigLoader.js +251 -0
  9. package/dist/core/config/ConfigMerger.js +45 -0
  10. package/dist/core/config/ConfigTypes.js +70 -0
  11. package/dist/core/config/ConfigValidator.js +74 -0
  12. package/dist/core/di/Container.js +175 -0
  13. package/dist/core/di/ServiceRegistration.js +38 -0
  14. package/dist/core/formatters/BaseFormattingRule.js +37 -0
  15. package/dist/core/formatters/rules/ast/ClassMemberSortingRule.js +171 -0
  16. package/dist/core/formatters/rules/ast/FileDeclarationSortingRule.js +162 -0
  17. package/dist/core/formatters/rules/imports/ImportOrganizationRule.js +196 -0
  18. package/dist/core/formatters/rules/index-generation/IndexGenerationRule.js +208 -0
  19. package/dist/core/formatters/rules/spacing/BlankLineBeforeReturnsRule.js +32 -0
  20. package/dist/core/formatters/rules/spacing/BlankLineBetweenDeclarationsRule.js +93 -0
  21. package/dist/core/formatters/rules/spacing/BlankLineBetweenStatementTypesRule.js +57 -0
  22. package/dist/core/formatters/rules/spacing/BlockSpacingRule.js +17 -0
  23. package/dist/core/formatters/rules/spacing/BracketSpacingRule.js +108 -0
  24. package/dist/core/formatters/rules/style/DocBlockCommentRule.js +19 -0
  25. package/dist/core/formatters/rules/style/IndentationRule.js +45 -0
  26. package/dist/core/formatters/rules/style/QuoteStyleRule.js +63 -0
  27. package/dist/core/formatters/rules/style/SemicolonRule.js +68 -0
  28. package/dist/core/formatters/rules/style/StructuralIndentationRule.js +250 -0
  29. package/dist/core/pipeline/FormatterPipeline.js +252 -0
  30. package/dist/formatters/package.js +29 -0
  31. package/dist/index.js +66 -0
  32. package/dist/shared/types.js +28 -0
  33. package/dist/sortPackage.js +34 -0
  34. package/dist/sortTSConfig.js +34 -0
  35. package/package.json +11 -8
  36. package/bin/ci_build.sh +0 -50
  37. package/bin/cli.js +0 -20
package/README.md CHANGED
@@ -84,7 +84,7 @@ tsfmt uses a sophisticated pipeline-based architecture:
84
84
  **Configuration System**
85
85
 
86
86
  - Zero-configuration by default with sensible opinions
87
- - Optional `core.config.ts` file for project-specific customization
87
+ - Optional `tsfmt.config.ts` file for project-specific customization
88
88
  - Deep merging of user configuration with defaults
89
89
 
90
90
  ## Key Features
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ function transformGenericsPlugin() {
6
+ function discoverRuleMappings() {
7
+ try {
8
+ let sourceCode;
9
+ const possiblePaths = [
10
+ path.resolve(__dirname, "../core/pipeline/FormatterPipeline.ts"),
11
+ path.resolve(__dirname, "../../src/core/pipeline/FormatterPipeline.ts"),
12
+ path.resolve(process.cwd(), "src/core/pipeline/FormatterPipeline.ts")
13
+ ];
14
+ for (const path2 of possiblePaths) {
15
+ try {
16
+ sourceCode = fs.readFileSync(path2, "utf8");
17
+ break;
18
+ } catch {
19
+ }
20
+ }
21
+ if (!sourceCode) {
22
+ throw new Error("Could not find FormatterPipeline.ts");
23
+ }
24
+ const methodStart = sourceCode.match(/private\s+initializeRules\(\):\s*void\s*\{/);
25
+ if (!methodStart || methodStart.index === void 0) {
26
+ throw new Error("Could not find initializeRules method");
27
+ }
28
+ const startIndex = methodStart.index + methodStart[0].length;
29
+ let braceCount = 1;
30
+ let endIndex = startIndex;
31
+ for (let i = startIndex; i < sourceCode.length && braceCount > 0; i++) {
32
+ if (sourceCode[i] === "{") {
33
+ braceCount++;
34
+ } else if (sourceCode[i] === "}") {
35
+ braceCount--;
36
+ }
37
+ endIndex = i;
38
+ }
39
+ const initRulesBody = sourceCode.substring(startIndex, endIndex);
40
+ const orderToRuleMap = {};
41
+ const addRuleRegex = /this\.addRule<([^>]+)>\(FormatterOrder\.([^)]+)\)/g;
42
+ let match;
43
+ while ((match = addRuleRegex.exec(initRulesBody)) !== null) {
44
+ const ruleName = match[1].trim();
45
+ const orderName = match[2].trim();
46
+ const orderKey = `ConfigTypes.FormatterOrder.${orderName}`;
47
+ if (!orderToRuleMap[orderKey]) {
48
+ orderToRuleMap[orderKey] = [];
49
+ }
50
+ orderToRuleMap[orderKey].push(ruleName);
51
+ }
52
+ return orderToRuleMap;
53
+ } catch (error) {
54
+ throw new Error(`Failed to auto-discover rule mappings from FormatterPipeline.ts: ${error}`);
55
+ }
56
+ }
57
+ return {
58
+ name: "transform-generics",
59
+ generateBundle(options, bundle) {
60
+ for (const fileName in bundle) {
61
+ const chunk = bundle[fileName];
62
+ if (chunk.type === "chunk" && fileName.includes("FormatterPipeline.js")) {
63
+ const orderToRuleMap = discoverRuleMappings();
64
+ const orderIndices = {};
65
+ for (const orderType of Object.keys(orderToRuleMap)) {
66
+ orderIndices[orderType] = 0;
67
+ }
68
+ chunk.code = chunk.code.replace(/this\.addRule\s*\(\s*([^)]+)\s*\)/g, (match, orderParam) => {
69
+ const orderKey = orderParam.trim();
70
+ if (orderToRuleMap[orderKey]) {
71
+ const rules = orderToRuleMap[orderKey];
72
+ const currentIndex = orderIndices[orderKey] || 0;
73
+ if (currentIndex < rules.length) {
74
+ const ruleName = rules[currentIndex];
75
+ orderIndices[orderKey] = currentIndex + 1;
76
+ return `this.addRuleByName("${ruleName}", ${orderParam})`;
77
+ }
78
+ }
79
+ return match;
80
+ });
81
+ }
82
+ }
83
+ }
84
+ };
85
+ }
86
+ exports.transformGenericsPlugin = transformGenericsPlugin;
package/dist/cli.js ADDED
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ const fs = require("fs");
3
+ const glob = require("glob");
4
+ const path = require("path");
5
+ require("reflect-metadata");
6
+ require("typescript");
7
+ require("./core/config/ConfigDefaults.js");
8
+ const ConfigLoader = require("./core/config/ConfigLoader.js");
9
+ const Container = require("./core/di/Container.js");
10
+ const ServiceRegistration = require("./core/di/ServiceRegistration.js");
11
+ require("fs/promises");
12
+ const sortPackage = require("./sortPackage.js");
13
+ const sortTSConfig = require("./sortTSConfig.js");
14
+ function _interopNamespaceDefault(e) {
15
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
16
+ if (e) {
17
+ for (const k in e) {
18
+ if (k !== "default") {
19
+ const d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: () => e[k]
23
+ });
24
+ }
25
+ }
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+ const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
31
+ const glob__namespace = /* @__PURE__ */ _interopNamespaceDefault(glob);
32
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
33
+ async function formatDirectory(targetDir, config, dryRun) {
34
+ const container = new Container.Container();
35
+ ServiceRegistration.ServiceRegistration.registerServices(container, config);
36
+ const include = config.sorting?.include || ["**/*.{ts,tsx,js,jsx}"];
37
+ const exclude = config.sorting?.exclude || [];
38
+ const criticalExcludes = ["node_modules/**", "dist/**", "build/**", "vendor/**", "bin/**"];
39
+ const finalExclude = [.../* @__PURE__ */ new Set([...exclude, ...criticalExcludes])];
40
+ const files = include.flatMap((pattern) => glob__namespace.sync(pattern, {
41
+ cwd: targetDir,
42
+ ignore: finalExclude,
43
+ absolute: true
44
+ }));
45
+ if (files.length === 0) {
46
+ console.info("No files found to format.");
47
+ return;
48
+ }
49
+ console.info(`Formatting ${files.length} files...`);
50
+ const pipeline = container.resolve("FormatterPipeline");
51
+ let formattedCount = 0;
52
+ for (const file of files) {
53
+ try {
54
+ const context = await pipeline.formatFile(file, dryRun);
55
+ if (context.changed) {
56
+ formattedCount++;
57
+ if (!dryRun) {
58
+ console.log(`📊 Formatted: ${path__namespace.relative(targetDir, file)}`);
59
+ }
60
+ }
61
+ } catch (error) {
62
+ console.error(`Error formatting file ${file}:`, error.message);
63
+ }
64
+ }
65
+ if (dryRun) {
66
+ console.info(`Would format ${formattedCount} of ${files.length} files.`);
67
+ } else {
68
+ console.info(`Formatted ${formattedCount} of ${files.length} files.`);
69
+ }
70
+ }
71
+ async function formatSingleFile(filePath, config, dryRun) {
72
+ const container = new Container.Container();
73
+ ServiceRegistration.ServiceRegistration.registerServices(container, config);
74
+ const pipeline = container.resolve("FormatterPipeline");
75
+ try {
76
+ const context = await pipeline.formatFile(filePath, dryRun);
77
+ if (context.changed) {
78
+ if (dryRun) {
79
+ console.info(`Would format: ${filePath}`);
80
+ } else {
81
+ console.log(`📊 Formatted: ${filePath}`);
82
+ }
83
+ } else {
84
+ console.info(`No changes needed: ${filePath}`);
85
+ }
86
+ } catch (error) {
87
+ console.error(`Error formatting file ${filePath}:`, error.message);
88
+ }
89
+ }
90
+ function isSupportedFile(filePath) {
91
+ const supportedExtensions = [".ts", ".tsx", ".js", ".jsx"];
92
+ return supportedExtensions.some((ext) => filePath.endsWith(ext));
93
+ }
94
+ async function main() {
95
+ const args = process.argv.slice(2);
96
+ let target = process.cwd();
97
+ let dryRun = false;
98
+ for (let i = 0; i < args.length; i++) {
99
+ const arg = args[i];
100
+ if (arg === "--dry") {
101
+ dryRun = true;
102
+ } else if (!arg.startsWith("-")) {
103
+ target = path__namespace.resolve(arg);
104
+ } else {
105
+ console.error(`Error: Unsupported option "${arg}". Only --dry is supported.`);
106
+ process.exit(1);
107
+ }
108
+ }
109
+ try {
110
+ const targetStat = fs__namespace.existsSync(target) ? fs__namespace.statSync(target) : null;
111
+ const isFile = targetStat?.isFile() ?? false;
112
+ const isDirectory = targetStat?.isDirectory() ?? false;
113
+ if (!targetStat) {
114
+ console.error(`Error: Target "${target}" does not exist.`);
115
+ process.exit(1);
116
+ }
117
+ const configDir = isFile ? path__namespace.dirname(target) : target;
118
+ const config = ConfigLoader.ConfigLoader.loadConfig(configDir);
119
+ if (ConfigLoader.ConfigLoader.hasConfigFile(configDir)) {
120
+ console.log("Using custom configuration from tsfmt.config.ts");
121
+ }
122
+ if (isFile) {
123
+ if (!isSupportedFile(target)) {
124
+ console.error(`Error: Unsupported file type. Supported: .ts, .tsx, .js, .jsx`);
125
+ process.exit(1);
126
+ }
127
+ if (config.codeStyle?.enabled || config.imports?.enabled || config.sorting?.enabled || config.spacing?.enabled) {
128
+ await formatSingleFile(target, config, dryRun);
129
+ }
130
+ if (dryRun) {
131
+ console.info("Dry run completed. No files were modified.");
132
+ } else {
133
+ console.info("Formatting completed successfully.");
134
+ }
135
+ return;
136
+ }
137
+ if (isDirectory) {
138
+ if (config.packageJson?.enabled) {
139
+ const packagePath = path__namespace.join(target, "package.json");
140
+ if (fs__namespace.existsSync(packagePath)) {
141
+ console.log(`📦 Processing ${packagePath}...`);
142
+ sortPackage.sortPackageFile(packagePath, {
143
+ customSortOrder: config.packageJson.customSortOrder,
144
+ indentation: config.packageJson.indentation,
145
+ dryRun
146
+ });
147
+ }
148
+ }
149
+ if (config.tsConfig?.enabled) {
150
+ const tsconfigPath = path__namespace.join(target, "tsconfig.json");
151
+ if (fs__namespace.existsSync(tsconfigPath)) {
152
+ console.log(`🔧 Processing ${tsconfigPath}...`);
153
+ sortTSConfig.sortTsConfigFile(tsconfigPath, {
154
+ indentation: config.tsConfig.indentation,
155
+ dryRun
156
+ });
157
+ }
158
+ }
159
+ if (config.codeStyle?.enabled || config.imports?.enabled || config.sorting?.enabled || config.spacing?.enabled) {
160
+ await formatDirectory(target, config, dryRun);
161
+ }
162
+ if (dryRun) {
163
+ console.info("Dry run completed. No files were modified.");
164
+ } else {
165
+ console.info("Formatting completed successfully.");
166
+ }
167
+ }
168
+ } catch (error) {
169
+ console.error("Error during formatting:", error.message);
170
+ process.exit(1);
171
+ }
172
+ }
173
+ main();
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const ts = require("typescript");
4
+ function _interopNamespaceDefault(e) {
5
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
6
+ if (e) {
7
+ for (const k in e) {
8
+ if (k !== "default") {
9
+ const d = Object.getOwnPropertyDescriptor(e, k);
10
+ Object.defineProperty(n, k, d.get ? d : {
11
+ enumerable: true,
12
+ get: () => e[k]
13
+ });
14
+ }
15
+ }
16
+ }
17
+ n.default = e;
18
+ return Object.freeze(n);
19
+ }
20
+ const ts__namespace = /* @__PURE__ */ _interopNamespaceDefault(ts);
21
+ class ASTAnalyzer {
22
+ /**
23
+ * Extracts all identifier references from a node
24
+ * This is the core traversal function that recursively visits the AST
25
+ */
26
+ static extractReferences(node, scopeFilter) {
27
+ const identifiers = /* @__PURE__ */ new Set();
28
+ const thisReferences = /* @__PURE__ */ new Set();
29
+ const directCalls = /* @__PURE__ */ new Set();
30
+ function visit(currentNode) {
31
+ if (ts__namespace.isPropertyAccessExpression(currentNode)) {
32
+ if (currentNode.expression.kind === ts__namespace.SyntaxKind.ThisKeyword) {
33
+ const propName = currentNode.name.text;
34
+ if (!scopeFilter || scopeFilter(propName)) {
35
+ thisReferences.add(propName);
36
+ identifiers.add(propName);
37
+ }
38
+ }
39
+ }
40
+ if (ts__namespace.isIdentifier(currentNode)) {
41
+ const name = currentNode.text;
42
+ if (!scopeFilter || scopeFilter(name)) {
43
+ identifiers.add(name);
44
+ const parent = currentNode.parent;
45
+ if (parent && ts__namespace.isCallExpression(parent) && parent.expression === currentNode) {
46
+ directCalls.add(name);
47
+ }
48
+ }
49
+ }
50
+ if (ts__namespace.isElementAccessExpression(currentNode)) {
51
+ if (currentNode.expression.kind === ts__namespace.SyntaxKind.ThisKeyword) {
52
+ if (ts__namespace.isStringLiteral(currentNode.argumentExpression)) {
53
+ const propName = currentNode.argumentExpression.text;
54
+ if (!scopeFilter || scopeFilter(propName)) {
55
+ thisReferences.add(propName);
56
+ identifiers.add(propName);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ts__namespace.forEachChild(currentNode, visit);
62
+ }
63
+ visit(node);
64
+ return { identifiers, thisReferences, directCalls };
65
+ }
66
+ /**
67
+ * Extract references from a class member
68
+ * Only considers references to members in availableMembers set
69
+ */
70
+ static extractClassMemberReferences(member, availableMembers) {
71
+ if (ts__namespace.isConstructorDeclaration(member)) {
72
+ return /* @__PURE__ */ new Set();
73
+ }
74
+ const refs = this.extractReferences(member, (name) => availableMembers.has(name));
75
+ return /* @__PURE__ */ new Set([...refs.thisReferences, ...refs.identifiers]);
76
+ }
77
+ /**
78
+ * Extract references from a file-level declaration
79
+ * Only considers references to declarations in availableDeclarations set
80
+ */
81
+ static extractFileDeclarationReferences(declaration, availableDeclarations) {
82
+ if (ts__namespace.isImportDeclaration(declaration) || ts__namespace.isImportEqualsDeclaration(declaration) || ts__namespace.isExportDeclaration(declaration)) {
83
+ return /* @__PURE__ */ new Set();
84
+ }
85
+ const refs = this.extractReferences(declaration, (name) => availableDeclarations.has(name));
86
+ return refs.identifiers;
87
+ }
88
+ /** Get the name of a class member */
89
+ static getClassMemberName(member) {
90
+ if (ts__namespace.isConstructorDeclaration(member)) {
91
+ return "constructor";
92
+ }
93
+ if (ts__namespace.isPropertyDeclaration(member) || ts__namespace.isMethodDeclaration(member) || ts__namespace.isGetAccessorDeclaration(member) || ts__namespace.isSetAccessorDeclaration(member)) {
94
+ if (ts__namespace.isIdentifier(member.name)) {
95
+ return member.name.text;
96
+ }
97
+ if (ts__namespace.isStringLiteral(member.name)) {
98
+ return member.name.text;
99
+ }
100
+ }
101
+ return "";
102
+ }
103
+ /** Get the name of a file-level declaration */
104
+ static getDeclarationName(declaration) {
105
+ if (ts__namespace.isInterfaceDeclaration(declaration) || ts__namespace.isTypeAliasDeclaration(declaration) || ts__namespace.isEnumDeclaration(declaration) || ts__namespace.isFunctionDeclaration(declaration) || ts__namespace.isClassDeclaration(declaration)) {
106
+ return declaration.name?.text || "";
107
+ }
108
+ if (ts__namespace.isVariableStatement(declaration)) {
109
+ const firstDecl = declaration.declarationList.declarations[0];
110
+ if (ts__namespace.isIdentifier(firstDecl.name)) {
111
+ return firstDecl.name.text;
112
+ }
113
+ }
114
+ return "";
115
+ }
116
+ /** Check if a node has a specific modifier */
117
+ static hasModifier(node, kind) {
118
+ const modifiers = ts__namespace.canHaveModifiers(node) ? ts__namespace.getModifiers(node) : void 0;
119
+ return modifiers?.some((m) => m.kind === kind) || false;
120
+ }
121
+ /** Check if a statement is a default export */
122
+ static isDefaultExport(node) {
123
+ if (ts__namespace.isExportAssignment(node)) {
124
+ return true;
125
+ }
126
+ return this.hasModifier(node, ts__namespace.SyntaxKind.DefaultKeyword);
127
+ }
128
+ /** Check if a statement has an export modifier */
129
+ static isExported(node) {
130
+ if (ts__namespace.isExportAssignment(node)) {
131
+ return true;
132
+ }
133
+ return this.hasModifier(node, ts__namespace.SyntaxKind.ExportKeyword);
134
+ }
135
+ }
136
+ exports.ASTAnalyzer = ASTAnalyzer;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const ts = require("typescript");
4
+ function _interopNamespaceDefault(e) {
5
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
6
+ if (e) {
7
+ for (const k in e) {
8
+ if (k !== "default") {
9
+ const d = Object.getOwnPropertyDescriptor(e, k);
10
+ Object.defineProperty(n, k, d.get ? d : {
11
+ enumerable: true,
12
+ get: () => e[k]
13
+ });
14
+ }
15
+ }
16
+ }
17
+ n.default = e;
18
+ return Object.freeze(n);
19
+ }
20
+ const ts__namespace = /* @__PURE__ */ _interopNamespaceDefault(ts);
21
+ class ASTTransformer {
22
+ /** Create a source file from source code */
23
+ static createSourceFile(source, filePath) {
24
+ const scriptKind = filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts__namespace.ScriptKind.TSX : ts__namespace.ScriptKind.TS;
25
+ return ts__namespace.createSourceFile(filePath, source, ts__namespace.ScriptTarget.Latest, true, scriptKind);
26
+ }
27
+ /** Print a node to string using TypeScript printer */
28
+ static printNode(node, sourceFile, removeComments = false) {
29
+ const printer = ts__namespace.createPrinter({
30
+ newLine: ts__namespace.NewLineKind.LineFeed,
31
+ removeComments
32
+ });
33
+ return printer.printNode(ts__namespace.EmitHint.Unspecified, node, sourceFile);
34
+ }
35
+ /**
36
+ * Print a source file to string
37
+ * Extracts and deduplicates leading comments to prevent duplication during formatting
38
+ */
39
+ static printSourceFile(sourceFile) {
40
+ const fullText = sourceFile.getFullText();
41
+ const leadingCommentsMatch = fullText.match(/^((?:\/\*[\s\S]*?\*\/\s*)+)/);
42
+ let leadingComments = leadingCommentsMatch ? leadingCommentsMatch[1].trim() : "";
43
+ if (leadingComments) {
44
+ const commentBlocks = leadingComments.match(/\/\*[\s\S]*?\*\//g) || [];
45
+ const uniqueBlocks = new Set(commentBlocks.map((block) => block.trim()));
46
+ leadingComments = Array.from(uniqueBlocks).join("\n");
47
+ }
48
+ const printer = ts__namespace.createPrinter({
49
+ newLine: ts__namespace.NewLineKind.LineFeed,
50
+ removeComments: false
51
+ });
52
+ let printed = printer.printFile(sourceFile);
53
+ printed = printed.replace(/(\n;)+\s*$/, "\n");
54
+ if (leadingComments) {
55
+ const printedWithoutLeadingComments = printed.replace(/^((?:\/\*[\s\S]*?\*\/\s*)+)/, "").trimStart();
56
+ return leadingComments + "\n\n" + printedWithoutLeadingComments;
57
+ }
58
+ return printed;
59
+ }
60
+ /** Create a new class declaration with reordered members */
61
+ static reorderClassMembers(classNode, orderedMembers) {
62
+ return ts__namespace.factory.updateClassDeclaration(classNode, classNode.modifiers, classNode.name, classNode.typeParameters, classNode.heritageClauses, orderedMembers);
63
+ }
64
+ /** Create a new source file with reordered statements */
65
+ static reorderSourceFileStatements(sourceFile, orderedStatements) {
66
+ return ts__namespace.factory.updateSourceFile(sourceFile, orderedStatements, sourceFile.isDeclarationFile, sourceFile.referencedFiles, sourceFile.typeReferenceDirectives, sourceFile.hasNoDefaultLib, sourceFile.libReferenceDirectives);
67
+ }
68
+ /** Transform a source file by visiting all nodes */
69
+ static transformSourceFile(sourceFile, visitor) {
70
+ const transformer = (context) => {
71
+ const visit = (node) => {
72
+ const result2 = visitor(node);
73
+ if (result2)
74
+ return result2;
75
+ return ts__namespace.visitEachChild(node, visit, context);
76
+ };
77
+ return (node) => ts__namespace.visitNode(node, visit);
78
+ };
79
+ const result = ts__namespace.transform(sourceFile, [transformer]);
80
+ const transformedSourceFile = result.transformed[0];
81
+ result.dispose();
82
+ return transformedSourceFile;
83
+ }
84
+ }
85
+ exports.ASTTransformer = ASTTransformer;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class DependencyResolver {
4
+ /**
5
+ * Find strongly connected components (circular dependency groups)
6
+ * Using Tarjan's algorithm
7
+ */
8
+ static findStronglyConnectedComponents(nodes) {
9
+ const index = /* @__PURE__ */ new Map();
10
+ const lowLink = /* @__PURE__ */ new Map();
11
+ const onStack = /* @__PURE__ */ new Set();
12
+ const stack = [];
13
+ const sccs = [];
14
+ let currentIndex = 0;
15
+ function strongConnect(nodeName) {
16
+ index.set(nodeName, currentIndex);
17
+ lowLink.set(nodeName, currentIndex);
18
+ currentIndex++;
19
+ stack.push(nodeName);
20
+ onStack.add(nodeName);
21
+ const node = nodes.get(nodeName);
22
+ for (const dep of node.dependencies) {
23
+ if (!index.has(dep)) {
24
+ strongConnect(dep);
25
+ lowLink.set(nodeName, Math.min(lowLink.get(nodeName), lowLink.get(dep)));
26
+ } else if (onStack.has(dep)) {
27
+ lowLink.set(nodeName, Math.min(lowLink.get(nodeName), index.get(dep)));
28
+ }
29
+ }
30
+ if (lowLink.get(nodeName) === index.get(nodeName)) {
31
+ const scc = /* @__PURE__ */ new Set();
32
+ let w;
33
+ do {
34
+ w = stack.pop();
35
+ onStack.delete(w);
36
+ scc.add(w);
37
+ } while (w !== nodeName);
38
+ if (scc.size > 1 || node.dependencies.has(nodeName)) {
39
+ sccs.push(scc);
40
+ }
41
+ }
42
+ }
43
+ for (const nodeName of nodes.keys()) {
44
+ if (!index.has(nodeName)) {
45
+ strongConnect(nodeName);
46
+ }
47
+ }
48
+ return sccs;
49
+ }
50
+ /** Builds a dependency graph from a list of items */
51
+ static buildGraph(items, getName, getDependencies) {
52
+ const nodes = /* @__PURE__ */ new Map();
53
+ items.forEach((item, index) => {
54
+ const name = getName(item);
55
+ if (!name)
56
+ return;
57
+ nodes.set(name, {
58
+ name,
59
+ dependencies: getDependencies(item),
60
+ originalIndex: index,
61
+ sortedIndex: index
62
+ });
63
+ });
64
+ const allNames = new Set(nodes.keys());
65
+ nodes.forEach((node) => {
66
+ node.dependencies = new Set(Array.from(node.dependencies).filter((dep) => allNames.has(dep)));
67
+ });
68
+ const circularGroups = this.findStronglyConnectedComponents(nodes);
69
+ return { nodes, circularGroups };
70
+ }
71
+ /**
72
+ * Performs topological sort respecting the ideal sorted order
73
+ * Returns array of names in dependency-aware order
74
+ */
75
+ static topologicalSort(graph, sortedNames) {
76
+ const result = [];
77
+ const visited = /* @__PURE__ */ new Set();
78
+ const visiting = /* @__PURE__ */ new Set();
79
+ const circularNodes = /* @__PURE__ */ new Set();
80
+ graph.circularGroups.forEach((group) => {
81
+ group.forEach((name) => circularNodes.add(name));
82
+ });
83
+ function visit(name) {
84
+ if (visited.has(name))
85
+ return true;
86
+ if (visiting.has(name)) {
87
+ return false;
88
+ }
89
+ const node = graph.nodes.get(name);
90
+ if (!node)
91
+ return true;
92
+ visiting.add(name);
93
+ for (const dep of node.dependencies) {
94
+ if (circularNodes.has(dep) && circularNodes.has(name)) {
95
+ continue;
96
+ }
97
+ if (!visit(dep)) {
98
+ return false;
99
+ }
100
+ }
101
+ visiting.delete(name);
102
+ visited.add(name);
103
+ result.push(name);
104
+ return true;
105
+ }
106
+ for (const name of sortedNames) {
107
+ visit(name);
108
+ }
109
+ return result;
110
+ }
111
+ /**
112
+ * Reorders items to respect dependencies while minimizing changes
113
+ * from the ideal sorted order
114
+ */
115
+ static reorderWithDependencies(items, getName) {
116
+ const getDeps = (item) => {
117
+ const member = item;
118
+ return member.dependencies || /* @__PURE__ */ new Set();
119
+ };
120
+ const graph = this.buildGraph(items, getName, getDeps);
121
+ const sortedNames = items.map(getName);
122
+ const dependencyOrder = this.topologicalSort(graph, sortedNames);
123
+ const nameToItem = /* @__PURE__ */ new Map();
124
+ items.forEach((item) => {
125
+ const name = getName(item);
126
+ if (name)
127
+ nameToItem.set(name, item);
128
+ });
129
+ const result = [];
130
+ for (const name of dependencyOrder) {
131
+ const item = nameToItem.get(name);
132
+ if (item) {
133
+ result.push(item);
134
+ }
135
+ }
136
+ items.forEach((item) => {
137
+ if (!result.includes(item)) {
138
+ result.push(item);
139
+ }
140
+ });
141
+ return result;
142
+ }
143
+ }
144
+ exports.DependencyResolver = DependencyResolver;