@tsonic/frontend 0.0.1
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.
- package/package.json +53 -0
- package/src/dependency-graph.ts +18 -0
- package/src/dotnet-metadata.ts +121 -0
- package/src/graph/builder.ts +81 -0
- package/src/graph/circular.ts +58 -0
- package/src/graph/extraction/exports.ts +55 -0
- package/src/graph/extraction/imports.ts +81 -0
- package/src/graph/extraction/index.ts +7 -0
- package/src/graph/extraction/orchestrator.ts +99 -0
- package/src/graph/extraction.ts +10 -0
- package/src/graph/helpers.ts +51 -0
- package/src/graph/index.ts +17 -0
- package/src/graph/types.ts +13 -0
- package/src/index.ts +80 -0
- package/src/ir/binding-resolution.test.ts +585 -0
- package/src/ir/builder/exports.ts +78 -0
- package/src/ir/builder/helpers.ts +27 -0
- package/src/ir/builder/imports.ts +153 -0
- package/src/ir/builder/index.ts +10 -0
- package/src/ir/builder/orchestrator.ts +178 -0
- package/src/ir/builder/statements.ts +55 -0
- package/src/ir/builder/types.ts +8 -0
- package/src/ir/builder/validation.ts +129 -0
- package/src/ir/builder.test.ts +581 -0
- package/src/ir/builder.ts +14 -0
- package/src/ir/converters/expressions/access.ts +99 -0
- package/src/ir/converters/expressions/calls.ts +137 -0
- package/src/ir/converters/expressions/collections.ts +84 -0
- package/src/ir/converters/expressions/functions.ts +62 -0
- package/src/ir/converters/expressions/helpers.ts +264 -0
- package/src/ir/converters/expressions/index.ts +43 -0
- package/src/ir/converters/expressions/literals.ts +22 -0
- package/src/ir/converters/expressions/operators.ts +147 -0
- package/src/ir/converters/expressions/other.ts +60 -0
- package/src/ir/converters/statements/control/blocks.ts +22 -0
- package/src/ir/converters/statements/control/conditionals.ts +67 -0
- package/src/ir/converters/statements/control/exceptions.ts +43 -0
- package/src/ir/converters/statements/control/index.ts +17 -0
- package/src/ir/converters/statements/control/loops.ts +99 -0
- package/src/ir/converters/statements/control.ts +17 -0
- package/src/ir/converters/statements/declarations/classes/constructors.ts +120 -0
- package/src/ir/converters/statements/declarations/classes/index.ts +12 -0
- package/src/ir/converters/statements/declarations/classes/methods.ts +61 -0
- package/src/ir/converters/statements/declarations/classes/orchestrator.ts +166 -0
- package/src/ir/converters/statements/declarations/classes/override-detection.ts +116 -0
- package/src/ir/converters/statements/declarations/classes/properties.ts +63 -0
- package/src/ir/converters/statements/declarations/classes.ts +6 -0
- package/src/ir/converters/statements/declarations/enums.ts +29 -0
- package/src/ir/converters/statements/declarations/functions.ts +39 -0
- package/src/ir/converters/statements/declarations/index.ts +14 -0
- package/src/ir/converters/statements/declarations/interfaces.ts +131 -0
- package/src/ir/converters/statements/declarations/registry.ts +45 -0
- package/src/ir/converters/statements/declarations/type-aliases.ts +25 -0
- package/src/ir/converters/statements/declarations/variables.ts +60 -0
- package/src/ir/converters/statements/declarations.ts +16 -0
- package/src/ir/converters/statements/helpers.ts +174 -0
- package/src/ir/converters/statements/index.ts +40 -0
- package/src/ir/expression-converter.ts +207 -0
- package/src/ir/generic-validator.ts +100 -0
- package/src/ir/hierarchical-bindings-e2e.test.ts +163 -0
- package/src/ir/index.ts +6 -0
- package/src/ir/statement-converter.ts +128 -0
- package/src/ir/type-converter/arrays.ts +20 -0
- package/src/ir/type-converter/converter.ts +10 -0
- package/src/ir/type-converter/functions.ts +22 -0
- package/src/ir/type-converter/index.ts +11 -0
- package/src/ir/type-converter/inference.ts +122 -0
- package/src/ir/type-converter/literals.ts +40 -0
- package/src/ir/type-converter/objects.ts +107 -0
- package/src/ir/type-converter/orchestrator.ts +85 -0
- package/src/ir/type-converter/patterns.ts +73 -0
- package/src/ir/type-converter/primitives.ts +57 -0
- package/src/ir/type-converter/references.ts +64 -0
- package/src/ir/type-converter/unions-intersections.ts +34 -0
- package/src/ir/type-converter.ts +13 -0
- package/src/ir/types/expressions.ts +215 -0
- package/src/ir/types/guards.ts +39 -0
- package/src/ir/types/helpers.ts +135 -0
- package/src/ir/types/index.ts +108 -0
- package/src/ir/types/ir-types.ts +96 -0
- package/src/ir/types/module.ts +57 -0
- package/src/ir/types/statements.ts +238 -0
- package/src/ir/types.ts +97 -0
- package/src/metadata/bindings-loader.test.ts +144 -0
- package/src/metadata/bindings-loader.ts +357 -0
- package/src/metadata/index.ts +15 -0
- package/src/metadata/library-loader.ts +153 -0
- package/src/metadata/loader.test.ts +156 -0
- package/src/metadata/loader.ts +382 -0
- package/src/program/bindings.test.ts +512 -0
- package/src/program/bindings.ts +253 -0
- package/src/program/config.ts +30 -0
- package/src/program/creation.ts +249 -0
- package/src/program/dependency-graph.ts +245 -0
- package/src/program/diagnostics.ts +103 -0
- package/src/program/index.ts +19 -0
- package/src/program/metadata.ts +68 -0
- package/src/program/queries.ts +18 -0
- package/src/program/types.ts +38 -0
- package/src/program.ts +13 -0
- package/src/resolver/dotnet-import-resolver.ts +226 -0
- package/src/resolver/import-resolution.ts +177 -0
- package/src/resolver/index.ts +18 -0
- package/src/resolver/namespace.test.ts +86 -0
- package/src/resolver/namespace.ts +42 -0
- package/src/resolver/naming.ts +38 -0
- package/src/resolver/path-resolution.ts +22 -0
- package/src/resolver/types.ts +15 -0
- package/src/resolver.test.ts +155 -0
- package/src/resolver.ts +14 -0
- package/src/symbol-table/builder.ts +114 -0
- package/src/symbol-table/creation.ts +42 -0
- package/src/symbol-table/helpers.ts +18 -0
- package/src/symbol-table/index.ts +13 -0
- package/src/symbol-table/queries.ts +42 -0
- package/src/symbol-table/types.ts +28 -0
- package/src/symbol-table.ts +14 -0
- package/src/types/bindings.ts +172 -0
- package/src/types/diagnostic.test.ts +164 -0
- package/src/types/diagnostic.ts +153 -0
- package/src/types/explicit-views.test.ts +113 -0
- package/src/types/explicit-views.ts +218 -0
- package/src/types/metadata.ts +229 -0
- package/src/types/module.ts +99 -0
- package/src/types/nested-types.test.ts +194 -0
- package/src/types/nested-types.ts +215 -0
- package/src/types/parameter-modifiers.ts +173 -0
- package/src/types/ref-parameters.test.ts +192 -0
- package/src/types/ref-parameters.ts +268 -0
- package/src/types/result.test.ts +157 -0
- package/src/types/result.ts +48 -0
- package/src/types/support-types.test.ts +81 -0
- package/src/types/support-types.ts +288 -0
- package/src/types/test-harness.ts +180 -0
- package/src/validation/exports.ts +98 -0
- package/src/validation/features.ts +89 -0
- package/src/validation/generics.ts +40 -0
- package/src/validation/helpers.ts +31 -0
- package/src/validation/imports.ts +97 -0
- package/src/validation/index.ts +11 -0
- package/src/validation/orchestrator.ts +51 -0
- package/src/validation/static-safety.ts +267 -0
- package/src/validator.test.ts +468 -0
- package/src/validator.ts +15 -0
- package/tsconfig.json +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tsonic/frontend",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TypeScript parser and IR builder for Tsonic compiler",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./types/metadata.js": {
|
|
14
|
+
"types": "./dist/types/metadata.d.ts",
|
|
15
|
+
"default": "./dist/types/metadata.js"
|
|
16
|
+
},
|
|
17
|
+
"./types/bindings.js": {
|
|
18
|
+
"types": "./dist/types/bindings.d.ts",
|
|
19
|
+
"default": "./dist/types/bindings.js"
|
|
20
|
+
},
|
|
21
|
+
"./types/nested-types.js": {
|
|
22
|
+
"types": "./dist/types/nested-types.d.ts",
|
|
23
|
+
"default": "./dist/types/nested-types.js"
|
|
24
|
+
},
|
|
25
|
+
"./types/explicit-views.js": {
|
|
26
|
+
"types": "./dist/types/explicit-views.d.ts",
|
|
27
|
+
"default": "./dist/types/explicit-views.js"
|
|
28
|
+
},
|
|
29
|
+
"./metadata/index.js": {
|
|
30
|
+
"types": "./dist/metadata/index.d.ts",
|
|
31
|
+
"default": "./dist/metadata/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -b",
|
|
36
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
37
|
+
"test": "mocha 'dist/**/*.test.js'",
|
|
38
|
+
"test:watch": "mocha 'dist/**/*.test.js' --watch"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"typescript": "5.9.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/chai": "5.2.2",
|
|
45
|
+
"@types/mocha": "10.0.10",
|
|
46
|
+
"@types/node": "20.14.0",
|
|
47
|
+
"chai": "5.3.3",
|
|
48
|
+
"mocha": "11.7.2"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module dependency graph builder
|
|
3
|
+
* Main dispatcher - re-exports from graph/ subdirectory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type { DependencyAnalysis } from "./graph/types.js";
|
|
7
|
+
export { buildDependencyGraph } from "./graph/builder.js";
|
|
8
|
+
export {
|
|
9
|
+
extractModuleInfo,
|
|
10
|
+
extractImport,
|
|
11
|
+
extractExport,
|
|
12
|
+
} from "./graph/extraction.js";
|
|
13
|
+
export { checkCircularDependencies } from "./graph/circular.js";
|
|
14
|
+
export {
|
|
15
|
+
isTopLevelCode,
|
|
16
|
+
hasExecutableInitializer,
|
|
17
|
+
hasExportModifier,
|
|
18
|
+
} from "./graph/helpers.js";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .NET Metadata Loader - Reads .metadata.json files to determine override behavior
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Metadata for a single .NET type member (method, property, etc.)
|
|
7
|
+
*/
|
|
8
|
+
export type DotnetMemberMetadata = {
|
|
9
|
+
readonly kind: "method" | "property" | "field" | "event";
|
|
10
|
+
readonly virtual?: boolean; // True if method is virtual/abstract
|
|
11
|
+
readonly sealed?: boolean; // True if method is sealed (cannot override)
|
|
12
|
+
readonly abstract?: boolean; // True if method is abstract
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Metadata for a complete .NET type (class, interface, struct, etc.)
|
|
17
|
+
*/
|
|
18
|
+
export type DotnetTypeMetadata = {
|
|
19
|
+
readonly kind: "class" | "interface" | "struct" | "enum";
|
|
20
|
+
readonly members: Readonly<Record<string, DotnetMemberMetadata>>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Complete metadata file structure
|
|
25
|
+
*/
|
|
26
|
+
export type DotnetMetadataFile = {
|
|
27
|
+
readonly types: Readonly<Record<string, DotnetTypeMetadata>>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registry of all loaded .NET metadata
|
|
32
|
+
* Maps fully-qualified type names to their metadata
|
|
33
|
+
*/
|
|
34
|
+
export class DotnetMetadataRegistry {
|
|
35
|
+
private readonly metadata = new Map<string, DotnetTypeMetadata>();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load a metadata file and add its types to the registry
|
|
39
|
+
*/
|
|
40
|
+
loadMetadataFile(_filePath: string, content: DotnetMetadataFile): void {
|
|
41
|
+
for (const [typeName, typeMetadata] of Object.entries(content.types)) {
|
|
42
|
+
this.metadata.set(typeName, typeMetadata);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Look up metadata for a .NET type by fully-qualified name
|
|
48
|
+
*/
|
|
49
|
+
getTypeMetadata(qualifiedName: string): DotnetTypeMetadata | undefined {
|
|
50
|
+
return this.metadata.get(qualifiedName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Look up metadata for a specific member of a .NET type
|
|
55
|
+
* @param qualifiedTypeName Fully-qualified type name (e.g., "System.IO.StringWriter")
|
|
56
|
+
* @param memberSignature Member signature (e.g., "ToString()" or "Write(string)")
|
|
57
|
+
*/
|
|
58
|
+
getMemberMetadata(
|
|
59
|
+
qualifiedTypeName: string,
|
|
60
|
+
memberSignature: string
|
|
61
|
+
): DotnetMemberMetadata | undefined {
|
|
62
|
+
const typeMetadata = this.metadata.get(qualifiedTypeName);
|
|
63
|
+
if (!typeMetadata) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return typeMetadata.members[memberSignature];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a member is virtual (can be overridden)
|
|
71
|
+
*/
|
|
72
|
+
isVirtualMember(qualifiedTypeName: string, memberSignature: string): boolean {
|
|
73
|
+
const memberMetadata = this.getMemberMetadata(
|
|
74
|
+
qualifiedTypeName,
|
|
75
|
+
memberSignature
|
|
76
|
+
);
|
|
77
|
+
return memberMetadata?.virtual === true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a member is sealed (cannot be overridden)
|
|
82
|
+
*/
|
|
83
|
+
isSealedMember(qualifiedTypeName: string, memberSignature: string): boolean {
|
|
84
|
+
const memberMetadata = this.getMemberMetadata(
|
|
85
|
+
qualifiedTypeName,
|
|
86
|
+
memberSignature
|
|
87
|
+
);
|
|
88
|
+
return memberMetadata?.sealed === true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get all loaded type names
|
|
93
|
+
*/
|
|
94
|
+
getAllTypeNames(): readonly string[] {
|
|
95
|
+
return Array.from(this.metadata.keys());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clear all loaded metadata
|
|
100
|
+
*/
|
|
101
|
+
clear(): void {
|
|
102
|
+
this.metadata.clear();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build a method signature string for metadata lookup
|
|
108
|
+
* Format: "MethodName(type1,type2,...)"
|
|
109
|
+
* @param methodName The method name
|
|
110
|
+
* @param parameterTypes Array of parameter type names (e.g., ["string", "number"])
|
|
111
|
+
* @returns Signature string for metadata lookup
|
|
112
|
+
*/
|
|
113
|
+
export const buildMethodSignature = (
|
|
114
|
+
methodName: string,
|
|
115
|
+
parameterTypes: readonly string[]
|
|
116
|
+
): string => {
|
|
117
|
+
if (parameterTypes.length === 0) {
|
|
118
|
+
return `${methodName}()`;
|
|
119
|
+
}
|
|
120
|
+
return `${methodName}(${parameterTypes.join(",")})`;
|
|
121
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency graph builder - Main orchestrator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { TsonicProgram } from "../program.js";
|
|
7
|
+
import { createModuleGraph, Import } from "../types/module.js";
|
|
8
|
+
import {
|
|
9
|
+
addDiagnostic,
|
|
10
|
+
createDiagnosticsCollector,
|
|
11
|
+
} from "../types/diagnostic.js";
|
|
12
|
+
import {
|
|
13
|
+
createSymbolTable,
|
|
14
|
+
addSymbol,
|
|
15
|
+
buildSymbolTable,
|
|
16
|
+
} from "../symbol-table.js";
|
|
17
|
+
import { DependencyAnalysis } from "./types.js";
|
|
18
|
+
import { extractModuleInfo } from "./extraction.js";
|
|
19
|
+
import { checkCircularDependencies } from "./circular.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build a complete dependency graph for a Tsonic program
|
|
23
|
+
*/
|
|
24
|
+
export const buildDependencyGraph = (
|
|
25
|
+
program: TsonicProgram,
|
|
26
|
+
entryPoints: readonly string[]
|
|
27
|
+
): DependencyAnalysis => {
|
|
28
|
+
const modules = new Map();
|
|
29
|
+
const dependencies = new Map();
|
|
30
|
+
const dependents = new Map();
|
|
31
|
+
let symbolTable = createSymbolTable();
|
|
32
|
+
let diagnostics = createDiagnosticsCollector();
|
|
33
|
+
|
|
34
|
+
// Process all source files
|
|
35
|
+
program.sourceFiles.forEach((sourceFile) => {
|
|
36
|
+
const moduleInfo = extractModuleInfo(sourceFile, program);
|
|
37
|
+
modules.set(sourceFile.fileName, moduleInfo);
|
|
38
|
+
|
|
39
|
+
// Build symbol table
|
|
40
|
+
const symbols = buildSymbolTable(sourceFile, program.checker);
|
|
41
|
+
symbols.forEach((symbol) => {
|
|
42
|
+
symbolTable = addSymbol(symbolTable, symbol);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Build dependency relationships
|
|
47
|
+
modules.forEach((module, modulePath) => {
|
|
48
|
+
const deps: string[] = [];
|
|
49
|
+
|
|
50
|
+
module.imports.forEach((imp: Import) => {
|
|
51
|
+
if (imp.resolvedPath) {
|
|
52
|
+
deps.push(imp.resolvedPath);
|
|
53
|
+
|
|
54
|
+
// Add to dependents map
|
|
55
|
+
const currentDependents = dependents.get(imp.resolvedPath) ?? [];
|
|
56
|
+
dependents.set(imp.resolvedPath, [...currentDependents, modulePath]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
dependencies.set(modulePath, deps);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Check for circular dependencies
|
|
64
|
+
const circularCheck = checkCircularDependencies(dependencies);
|
|
65
|
+
if (!circularCheck.ok) {
|
|
66
|
+
diagnostics = addDiagnostic(diagnostics, circularCheck.error);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const graph = createModuleGraph(
|
|
70
|
+
modules,
|
|
71
|
+
dependencies,
|
|
72
|
+
dependents,
|
|
73
|
+
entryPoints.map((ep) => path.resolve(ep))
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
graph,
|
|
78
|
+
symbolTable,
|
|
79
|
+
diagnostics,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circular dependency detection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { Diagnostic, createDiagnostic } from "../types/diagnostic.js";
|
|
7
|
+
import { Result, ok, error } from "../types/result.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check for circular dependencies using depth-first search
|
|
11
|
+
*/
|
|
12
|
+
export const checkCircularDependencies = (
|
|
13
|
+
dependencies: ReadonlyMap<string, readonly string[]>
|
|
14
|
+
): Result<void, Diagnostic> => {
|
|
15
|
+
const visited = new Set<string>();
|
|
16
|
+
const stack = new Set<string>();
|
|
17
|
+
|
|
18
|
+
const visit = (module: string, path: string[]): string[] | null => {
|
|
19
|
+
if (stack.has(module)) {
|
|
20
|
+
return [...path, module]; // Found cycle
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (visited.has(module)) {
|
|
24
|
+
return null; // Already checked
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
visited.add(module);
|
|
28
|
+
stack.add(module);
|
|
29
|
+
|
|
30
|
+
const deps = dependencies.get(module) ?? [];
|
|
31
|
+
for (const dep of deps) {
|
|
32
|
+
const cycle = visit(dep, [...path, module]);
|
|
33
|
+
if (cycle) {
|
|
34
|
+
return cycle;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
stack.delete(module);
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (const [module] of dependencies) {
|
|
43
|
+
const cycle = visit(module, []);
|
|
44
|
+
if (cycle) {
|
|
45
|
+
return error(
|
|
46
|
+
createDiagnostic(
|
|
47
|
+
"TSN1002",
|
|
48
|
+
"error",
|
|
49
|
+
`Circular dependency detected: ${cycle.map((m) => path.basename(m)).join(" → ")}`,
|
|
50
|
+
undefined,
|
|
51
|
+
"Break the circular dependency by refactoring shared code"
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return ok(undefined);
|
|
58
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export information extraction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import { Export } from "../../types/module.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract export information from an export declaration
|
|
10
|
+
*/
|
|
11
|
+
export const extractExport = (node: ts.ExportDeclaration): Export | null => {
|
|
12
|
+
if (
|
|
13
|
+
!node.moduleSpecifier &&
|
|
14
|
+
node.exportClause &&
|
|
15
|
+
ts.isNamedExports(node.exportClause)
|
|
16
|
+
) {
|
|
17
|
+
// export { a, b as c } - named exports without re-export
|
|
18
|
+
const elements = Array.from(node.exportClause.elements);
|
|
19
|
+
if (elements.length > 0 && elements[0]) {
|
|
20
|
+
const spec = elements[0];
|
|
21
|
+
return {
|
|
22
|
+
kind: "named",
|
|
23
|
+
name: spec.name.text,
|
|
24
|
+
localName: (spec.propertyName ?? spec.name).text,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
31
|
+
// export { ... } from "..." - re-exports
|
|
32
|
+
const fromModule = node.moduleSpecifier.text;
|
|
33
|
+
const exportedNames: { readonly name: string; readonly alias?: string }[] =
|
|
34
|
+
[];
|
|
35
|
+
|
|
36
|
+
if (node.exportClause) {
|
|
37
|
+
if (ts.isNamedExports(node.exportClause)) {
|
|
38
|
+
Array.from(node.exportClause.elements).forEach((spec) => {
|
|
39
|
+
exportedNames.push({
|
|
40
|
+
name: (spec.propertyName ?? spec.name).text,
|
|
41
|
+
alias: spec.propertyName ? spec.name.text : undefined,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
kind: "reexport",
|
|
49
|
+
fromModule,
|
|
50
|
+
exports: exportedNames,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import information extraction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import { TsonicProgram } from "../../program.js";
|
|
7
|
+
import { Import } from "../../types/module.js";
|
|
8
|
+
import { resolveImport } from "../../resolver.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract import information from an import declaration
|
|
12
|
+
*/
|
|
13
|
+
export const extractImport = (
|
|
14
|
+
node: ts.ImportDeclaration,
|
|
15
|
+
sourceFile: ts.SourceFile,
|
|
16
|
+
program: TsonicProgram
|
|
17
|
+
): Import | null => {
|
|
18
|
+
if (!ts.isStringLiteral(node.moduleSpecifier)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const specifier = node.moduleSpecifier.text;
|
|
23
|
+
const result = resolveImport(
|
|
24
|
+
specifier,
|
|
25
|
+
sourceFile.fileName,
|
|
26
|
+
program.options.sourceRoot,
|
|
27
|
+
program.dotnetResolver
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const importedNames: { readonly name: string; readonly alias?: string }[] =
|
|
31
|
+
[];
|
|
32
|
+
|
|
33
|
+
if (node.importClause) {
|
|
34
|
+
// Default import
|
|
35
|
+
if (node.importClause.name) {
|
|
36
|
+
importedNames.push({
|
|
37
|
+
name: "default",
|
|
38
|
+
alias: node.importClause.name.text,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Named imports
|
|
43
|
+
if (node.importClause.namedBindings) {
|
|
44
|
+
if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
45
|
+
// import * as ns from "..."
|
|
46
|
+
importedNames.push({
|
|
47
|
+
name: "*",
|
|
48
|
+
alias: node.importClause.namedBindings.name.text,
|
|
49
|
+
});
|
|
50
|
+
} else if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
51
|
+
// import { a, b as c } from "..."
|
|
52
|
+
node.importClause.namedBindings.elements.forEach((spec) => {
|
|
53
|
+
importedNames.push({
|
|
54
|
+
name: (spec.propertyName ?? spec.name).text,
|
|
55
|
+
alias: spec.propertyName ? spec.name.text : undefined,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.ok) {
|
|
63
|
+
return {
|
|
64
|
+
kind: result.value.isLocal
|
|
65
|
+
? "local"
|
|
66
|
+
: result.value.isDotNet
|
|
67
|
+
? "dotnet"
|
|
68
|
+
: "node_module",
|
|
69
|
+
specifier,
|
|
70
|
+
resolvedPath: result.value.resolvedPath || undefined,
|
|
71
|
+
namespace: result.value.resolvedNamespace,
|
|
72
|
+
importedNames,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
kind: "local",
|
|
78
|
+
specifier,
|
|
79
|
+
importedNames,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module information extraction orchestrator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import { TsonicProgram } from "../../program.js";
|
|
7
|
+
import { ModuleInfo, Import, Export } from "../../types/module.js";
|
|
8
|
+
import { getNamespaceFromPath, getClassNameFromPath } from "../../resolver.js";
|
|
9
|
+
import { hasExportModifier, isTopLevelCode } from "../helpers.js";
|
|
10
|
+
import { extractImport } from "./imports.js";
|
|
11
|
+
import { extractExport } from "./exports.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract module information from a TypeScript source file
|
|
15
|
+
*/
|
|
16
|
+
export const extractModuleInfo = (
|
|
17
|
+
sourceFile: ts.SourceFile,
|
|
18
|
+
program: TsonicProgram
|
|
19
|
+
): ModuleInfo => {
|
|
20
|
+
const imports: Import[] = [];
|
|
21
|
+
const exports: Export[] = [];
|
|
22
|
+
let hasTopLevelCode = false;
|
|
23
|
+
|
|
24
|
+
const visitor = (node: ts.Node): void => {
|
|
25
|
+
// Extract imports
|
|
26
|
+
if (ts.isImportDeclaration(node)) {
|
|
27
|
+
const imp = extractImport(node, sourceFile, program);
|
|
28
|
+
if (imp) {
|
|
29
|
+
imports.push(imp);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract exports
|
|
34
|
+
if (ts.isExportDeclaration(node)) {
|
|
35
|
+
const exp = extractExport(node);
|
|
36
|
+
if (exp) {
|
|
37
|
+
exports.push(exp);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (ts.isExportAssignment(node)) {
|
|
42
|
+
exports.push({
|
|
43
|
+
kind: "default",
|
|
44
|
+
localName: node.expression.getText(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for exported declarations
|
|
49
|
+
if (hasExportModifier(node)) {
|
|
50
|
+
if (ts.isVariableStatement(node)) {
|
|
51
|
+
node.declarationList.declarations.forEach((decl) => {
|
|
52
|
+
if (ts.isIdentifier(decl.name)) {
|
|
53
|
+
exports.push({
|
|
54
|
+
kind: "named",
|
|
55
|
+
name: decl.name.text,
|
|
56
|
+
localName: decl.name.text,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} else if (
|
|
61
|
+
(ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) &&
|
|
62
|
+
node.name
|
|
63
|
+
) {
|
|
64
|
+
exports.push({
|
|
65
|
+
kind: "named",
|
|
66
|
+
name: node.name.text,
|
|
67
|
+
localName: node.name.text,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check for top-level code (non-declaration statements)
|
|
73
|
+
if (isTopLevelCode(node)) {
|
|
74
|
+
hasTopLevelCode = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ts.forEachChild(node, visitor);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
visitor(sourceFile);
|
|
81
|
+
|
|
82
|
+
const namespace = getNamespaceFromPath(
|
|
83
|
+
sourceFile.fileName,
|
|
84
|
+
program.options.sourceRoot,
|
|
85
|
+
program.options.rootNamespace
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const className = getClassNameFromPath(sourceFile.fileName);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
filePath: sourceFile.fileName,
|
|
92
|
+
sourceText: sourceFile.getFullText(),
|
|
93
|
+
imports,
|
|
94
|
+
exports,
|
|
95
|
+
hasTopLevelCode,
|
|
96
|
+
namespace,
|
|
97
|
+
className,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for dependency graph analysis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if a node is top-level executable code (not just declarations)
|
|
9
|
+
*/
|
|
10
|
+
export const isTopLevelCode = (node: ts.Node): boolean => {
|
|
11
|
+
// Check if this is a top-level statement that's not a declaration
|
|
12
|
+
if (node.parent && ts.isSourceFile(node.parent)) {
|
|
13
|
+
return (
|
|
14
|
+
!ts.isModuleDeclaration(node) &&
|
|
15
|
+
!ts.isImportDeclaration(node) &&
|
|
16
|
+
!ts.isExportDeclaration(node) &&
|
|
17
|
+
!ts.isExportAssignment(node) &&
|
|
18
|
+
!ts.isTypeAliasDeclaration(node) &&
|
|
19
|
+
!ts.isInterfaceDeclaration(node) &&
|
|
20
|
+
!(ts.isVariableStatement(node) && !hasExecutableInitializer(node)) &&
|
|
21
|
+
!ts.isFunctionDeclaration(node) &&
|
|
22
|
+
!ts.isClassDeclaration(node) &&
|
|
23
|
+
!ts.isEnumDeclaration(node)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if a variable statement has an initializer that executes code
|
|
31
|
+
*/
|
|
32
|
+
export const hasExecutableInitializer = (
|
|
33
|
+
node: ts.VariableStatement
|
|
34
|
+
): boolean => {
|
|
35
|
+
return node.declarationList.declarations.some(
|
|
36
|
+
(decl) => decl.initializer && !ts.isLiteralExpression(decl.initializer)
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if a node has the export modifier
|
|
42
|
+
*/
|
|
43
|
+
export const hasExportModifier = (node: ts.Node): boolean => {
|
|
44
|
+
if (!ts.canHaveModifiers(node)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const modifiers = ts.getModifiers(node);
|
|
48
|
+
return (
|
|
49
|
+
modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency graph builder - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type { DependencyAnalysis } from "./types.js";
|
|
6
|
+
export { buildDependencyGraph } from "./builder.js";
|
|
7
|
+
export {
|
|
8
|
+
extractModuleInfo,
|
|
9
|
+
extractImport,
|
|
10
|
+
extractExport,
|
|
11
|
+
} from "./extraction.js";
|
|
12
|
+
export { checkCircularDependencies } from "./circular.js";
|
|
13
|
+
export {
|
|
14
|
+
isTopLevelCode,
|
|
15
|
+
hasExecutableInitializer,
|
|
16
|
+
hasExportModifier,
|
|
17
|
+
} from "./helpers.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency graph type definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ModuleGraph } from "../types/module.js";
|
|
6
|
+
import { DiagnosticsCollector } from "../types/diagnostic.js";
|
|
7
|
+
import { SymbolTable } from "../symbol-table.js";
|
|
8
|
+
|
|
9
|
+
export type DependencyAnalysis = {
|
|
10
|
+
readonly graph: ModuleGraph;
|
|
11
|
+
readonly symbolTable: SymbolTable;
|
|
12
|
+
readonly diagnostics: DiagnosticsCollector;
|
|
13
|
+
};
|