@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for diagnostic types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import {
|
|
8
|
+
createDiagnostic,
|
|
9
|
+
formatDiagnostic,
|
|
10
|
+
createDiagnosticsCollector,
|
|
11
|
+
addDiagnostic,
|
|
12
|
+
mergeDiagnostics,
|
|
13
|
+
isError,
|
|
14
|
+
} from "./diagnostic.js";
|
|
15
|
+
|
|
16
|
+
describe("Diagnostics", () => {
|
|
17
|
+
describe("createDiagnostic", () => {
|
|
18
|
+
it("should create a diagnostic with all fields", () => {
|
|
19
|
+
const diagnostic = createDiagnostic(
|
|
20
|
+
"TSN1001",
|
|
21
|
+
"error",
|
|
22
|
+
"Test error",
|
|
23
|
+
{
|
|
24
|
+
file: "test.ts",
|
|
25
|
+
line: 10,
|
|
26
|
+
column: 5,
|
|
27
|
+
length: 10,
|
|
28
|
+
},
|
|
29
|
+
"Try this instead",
|
|
30
|
+
[]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(diagnostic.code).to.equal("TSN1001");
|
|
34
|
+
expect(diagnostic.severity).to.equal("error");
|
|
35
|
+
expect(diagnostic.message).to.equal("Test error");
|
|
36
|
+
expect(diagnostic.location?.file).to.equal("test.ts");
|
|
37
|
+
expect(diagnostic.hint).to.equal("Try this instead");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should create a diagnostic without optional fields", () => {
|
|
41
|
+
const diagnostic = createDiagnostic("TSN1002", "warning", "Test warning");
|
|
42
|
+
|
|
43
|
+
expect(diagnostic.code).to.equal("TSN1002");
|
|
44
|
+
expect(diagnostic.severity).to.equal("warning");
|
|
45
|
+
expect(diagnostic.location).to.equal(undefined);
|
|
46
|
+
expect(diagnostic.hint).to.equal(undefined);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("formatDiagnostic", () => {
|
|
51
|
+
it("should format diagnostic with location", () => {
|
|
52
|
+
const diagnostic = createDiagnostic(
|
|
53
|
+
"TSN1001",
|
|
54
|
+
"error",
|
|
55
|
+
"Missing .ts extension",
|
|
56
|
+
{
|
|
57
|
+
file: "/src/index.ts",
|
|
58
|
+
line: 5,
|
|
59
|
+
column: 10,
|
|
60
|
+
length: 15,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const formatted = formatDiagnostic(diagnostic);
|
|
65
|
+
expect(formatted).to.equal(
|
|
66
|
+
"/src/index.ts:5:10 error TSN1001: Missing .ts extension"
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should format diagnostic without location", () => {
|
|
71
|
+
const diagnostic = createDiagnostic(
|
|
72
|
+
"TSN2001",
|
|
73
|
+
"warning",
|
|
74
|
+
"Generic warning"
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const formatted = formatDiagnostic(diagnostic);
|
|
78
|
+
expect(formatted).to.equal("warning TSN2001: Generic warning");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should include hint if present", () => {
|
|
82
|
+
const diagnostic = createDiagnostic(
|
|
83
|
+
"TSN1001",
|
|
84
|
+
"error",
|
|
85
|
+
"Missing extension",
|
|
86
|
+
undefined,
|
|
87
|
+
"Add .ts extension"
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const formatted = formatDiagnostic(diagnostic);
|
|
91
|
+
expect(formatted).to.include("Hint: Add .ts extension");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("DiagnosticsCollector", () => {
|
|
96
|
+
it("should start with empty diagnostics", () => {
|
|
97
|
+
const collector = createDiagnosticsCollector();
|
|
98
|
+
expect(collector.diagnostics).to.have.length(0);
|
|
99
|
+
expect(collector.hasErrors).to.equal(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should add diagnostics immutably", () => {
|
|
103
|
+
const collector1 = createDiagnosticsCollector();
|
|
104
|
+
const diagnostic = createDiagnostic("TSN1001", "error", "Test");
|
|
105
|
+
|
|
106
|
+
const collector2 = addDiagnostic(collector1, diagnostic);
|
|
107
|
+
|
|
108
|
+
expect(collector1.diagnostics).to.have.length(0);
|
|
109
|
+
expect(collector2.diagnostics).to.have.length(1);
|
|
110
|
+
expect(collector2.hasErrors).to.equal(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should track hasErrors correctly", () => {
|
|
114
|
+
let collector = createDiagnosticsCollector();
|
|
115
|
+
|
|
116
|
+
collector = addDiagnostic(
|
|
117
|
+
collector,
|
|
118
|
+
createDiagnostic("TSN1001", "warning", "Warning")
|
|
119
|
+
);
|
|
120
|
+
expect(collector.hasErrors).to.equal(false);
|
|
121
|
+
|
|
122
|
+
collector = addDiagnostic(
|
|
123
|
+
collector,
|
|
124
|
+
createDiagnostic("TSN1002", "error", "Error")
|
|
125
|
+
);
|
|
126
|
+
expect(collector.hasErrors).to.equal(true);
|
|
127
|
+
|
|
128
|
+
collector = addDiagnostic(
|
|
129
|
+
collector,
|
|
130
|
+
createDiagnostic("TSN1003", "info", "Info")
|
|
131
|
+
);
|
|
132
|
+
expect(collector.hasErrors).to.equal(true); // Still has errors
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should merge collectors", () => {
|
|
136
|
+
const collector1 = addDiagnostic(
|
|
137
|
+
createDiagnosticsCollector(),
|
|
138
|
+
createDiagnostic("TSN1001", "error", "Error 1")
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const collector2 = addDiagnostic(
|
|
142
|
+
createDiagnosticsCollector(),
|
|
143
|
+
createDiagnostic("TSN1002", "warning", "Warning 1")
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const merged = mergeDiagnostics(collector1, collector2);
|
|
147
|
+
|
|
148
|
+
expect(merged.diagnostics).to.have.length(2);
|
|
149
|
+
expect(merged.hasErrors).to.equal(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("isError", () => {
|
|
154
|
+
it("should identify error diagnostics", () => {
|
|
155
|
+
const error = createDiagnostic("TSN1001", "error", "Test");
|
|
156
|
+
const warning = createDiagnostic("TSN1002", "warning", "Test");
|
|
157
|
+
const info = createDiagnostic("TSN1003", "info", "Test");
|
|
158
|
+
|
|
159
|
+
expect(isError(error)).to.equal(true);
|
|
160
|
+
expect(isError(warning)).to.equal(false);
|
|
161
|
+
expect(isError(info)).to.equal(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic types for Tsonic compiler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type DiagnosticSeverity = "error" | "warning" | "info";
|
|
6
|
+
|
|
7
|
+
export type DiagnosticCode =
|
|
8
|
+
| "TSN1001" // Local import missing .ts extension
|
|
9
|
+
| "TSN1002" // Circular dependency detected
|
|
10
|
+
| "TSN1003" // Case mismatch in import path
|
|
11
|
+
| "TSN1004" // Module not found
|
|
12
|
+
| "TSN1005" // Conflicting exports
|
|
13
|
+
| "TSN1006" // Invalid namespace
|
|
14
|
+
| "TSN2001" // Unsupported TypeScript feature
|
|
15
|
+
| "TSN2002" // Invalid type mapping
|
|
16
|
+
| "TSN2003" // File name conflicts with exported member name
|
|
17
|
+
| "TSN3001" // C# reserved keyword used
|
|
18
|
+
| "TSN3002" // Invalid C# identifier
|
|
19
|
+
| "TSN3011" // Promise chaining (.then/.catch/.finally) not supported
|
|
20
|
+
| "TSN4001" // .NET interop error
|
|
21
|
+
| "TSN4002" // Missing .NET type declaration
|
|
22
|
+
| "TSN5001" // NativeAOT limitation
|
|
23
|
+
| "TSN5002" // Runtime implementation missing
|
|
24
|
+
| "TSN6001" // Internal compiler error
|
|
25
|
+
| "TSN7101" // Recursive mapped types not supported
|
|
26
|
+
| "TSN7102" // Conditional types using infer not supported
|
|
27
|
+
| "TSN7103" // `this` typing not supported
|
|
28
|
+
| "TSN7104" // Generic constructor constraints with rest parameters not supported
|
|
29
|
+
| "TSN7105" // Cannot determine required type specialisations
|
|
30
|
+
| "TSN7201" // Recursive structural alias not supported
|
|
31
|
+
| "TSN7202" // Conditional alias cannot be resolved
|
|
32
|
+
| "TSN7203" // Symbol keys not supported
|
|
33
|
+
| "TSN7204" // Variadic generic interface not supported
|
|
34
|
+
| "TSN7301" // Class cannot implement nominalized interface
|
|
35
|
+
// Static/AOT safety errors (TSN7401-TSN7499)
|
|
36
|
+
| "TSN7401" // 'any' type not supported - requires explicit type
|
|
37
|
+
| "TSN7403" // Object literal requires contextual nominal type
|
|
38
|
+
| "TSN7405" // Untyped lambda parameter - requires explicit type annotation
|
|
39
|
+
| "TSN7413" // Dictionary key must be string type
|
|
40
|
+
// Metadata loading errors (TSN9001-TSN9018)
|
|
41
|
+
| "TSN9001" // Metadata file not found
|
|
42
|
+
| "TSN9002" // Failed to read metadata file
|
|
43
|
+
| "TSN9003" // Invalid JSON in metadata file
|
|
44
|
+
| "TSN9004" // Metadata file must be an object
|
|
45
|
+
| "TSN9005" // Missing or invalid 'namespace' field
|
|
46
|
+
| "TSN9006" // Missing or invalid 'contributingAssemblies' field
|
|
47
|
+
| "TSN9007" // All 'contributingAssemblies' must be strings
|
|
48
|
+
| "TSN9008" // Missing or invalid 'types' field
|
|
49
|
+
| "TSN9009" // Invalid type: must be an object
|
|
50
|
+
| "TSN9010" // Invalid type: missing or invalid field
|
|
51
|
+
| "TSN9011" // Invalid type: 'kind' must be one of ...
|
|
52
|
+
| "TSN9012" // Invalid type: 'accessibility' must be one of ...
|
|
53
|
+
| "TSN9013" // Invalid type: field must be a boolean
|
|
54
|
+
| "TSN9014" // Invalid type: 'arity' must be a non-negative number
|
|
55
|
+
| "TSN9015" // Invalid type: field must be an array
|
|
56
|
+
| "TSN9016" // Metadata directory not found
|
|
57
|
+
| "TSN9017" // Not a directory
|
|
58
|
+
| "TSN9018" // No .metadata.json files found
|
|
59
|
+
// Bindings loading errors (TSN9101-TSN9114)
|
|
60
|
+
| "TSN9101" // Bindings file not found
|
|
61
|
+
| "TSN9102" // Failed to read bindings file
|
|
62
|
+
| "TSN9103" // Invalid JSON in bindings file
|
|
63
|
+
| "TSN9104" // Bindings file must be an object
|
|
64
|
+
| "TSN9105" // Missing or invalid 'namespace' field
|
|
65
|
+
| "TSN9106" // Missing or invalid 'types' field
|
|
66
|
+
| "TSN9107" // Invalid type binding: must be an object
|
|
67
|
+
| "TSN9108" // Invalid type binding: missing or invalid field
|
|
68
|
+
| "TSN9109" // Invalid type binding: 'metadataToken' must be a number
|
|
69
|
+
| "TSN9110" // Invalid type binding: V1 field must be an array if present
|
|
70
|
+
| "TSN9111" // Invalid type binding: V2 field must be an array if present
|
|
71
|
+
| "TSN9112" // Bindings directory not found
|
|
72
|
+
| "TSN9113" // Not a directory
|
|
73
|
+
| "TSN9114"; // No .bindings.json files found
|
|
74
|
+
|
|
75
|
+
export type SourceLocation = {
|
|
76
|
+
readonly file: string;
|
|
77
|
+
readonly line: number;
|
|
78
|
+
readonly column: number;
|
|
79
|
+
readonly length: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type Diagnostic = {
|
|
83
|
+
readonly code: DiagnosticCode;
|
|
84
|
+
readonly severity: DiagnosticSeverity;
|
|
85
|
+
readonly message: string;
|
|
86
|
+
readonly location?: SourceLocation;
|
|
87
|
+
readonly hint?: string;
|
|
88
|
+
readonly relatedLocations?: readonly SourceLocation[];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const createDiagnostic = (
|
|
92
|
+
code: DiagnosticCode,
|
|
93
|
+
severity: DiagnosticSeverity,
|
|
94
|
+
message: string,
|
|
95
|
+
location?: SourceLocation,
|
|
96
|
+
hint?: string,
|
|
97
|
+
relatedLocations?: readonly SourceLocation[]
|
|
98
|
+
): Diagnostic => ({
|
|
99
|
+
code,
|
|
100
|
+
severity,
|
|
101
|
+
message,
|
|
102
|
+
location,
|
|
103
|
+
hint,
|
|
104
|
+
relatedLocations,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const isError = (diagnostic: Diagnostic): boolean =>
|
|
108
|
+
diagnostic.severity === "error";
|
|
109
|
+
|
|
110
|
+
export const formatDiagnostic = (diagnostic: Diagnostic): string => {
|
|
111
|
+
const parts: string[] = [];
|
|
112
|
+
|
|
113
|
+
if (diagnostic.location) {
|
|
114
|
+
parts.push(
|
|
115
|
+
`${diagnostic.location.file}:${diagnostic.location.line}:${diagnostic.location.column}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
parts.push(`${diagnostic.severity} ${diagnostic.code}:`);
|
|
120
|
+
parts.push(diagnostic.message);
|
|
121
|
+
|
|
122
|
+
if (diagnostic.hint) {
|
|
123
|
+
parts.push(`Hint: ${diagnostic.hint}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return parts.join(" ");
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type DiagnosticsCollector = {
|
|
130
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
131
|
+
readonly hasErrors: boolean;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const createDiagnosticsCollector = (): DiagnosticsCollector => ({
|
|
135
|
+
diagnostics: [],
|
|
136
|
+
hasErrors: false,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export const addDiagnostic = (
|
|
140
|
+
collector: DiagnosticsCollector,
|
|
141
|
+
diagnostic: Diagnostic
|
|
142
|
+
): DiagnosticsCollector => ({
|
|
143
|
+
diagnostics: [...collector.diagnostics, diagnostic],
|
|
144
|
+
hasErrors: collector.hasErrors || isError(diagnostic),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export const mergeDiagnostics = (
|
|
148
|
+
collector1: DiagnosticsCollector,
|
|
149
|
+
collector2: DiagnosticsCollector
|
|
150
|
+
): DiagnosticsCollector => ({
|
|
151
|
+
diagnostics: [...collector1.diagnostics, ...collector2.diagnostics],
|
|
152
|
+
hasErrors: collector1.hasErrors || collector2.hasErrors,
|
|
153
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for explicit interface views handling
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { strict as assert } from "assert";
|
|
7
|
+
import {
|
|
8
|
+
isExplicitViewProperty,
|
|
9
|
+
extractInterfaceNameFromView,
|
|
10
|
+
buildViewPropertyName,
|
|
11
|
+
generateInterfaceCast,
|
|
12
|
+
generateGenericInterfaceCast,
|
|
13
|
+
} from "./explicit-views.js";
|
|
14
|
+
|
|
15
|
+
describe("Explicit Interface Views", () => {
|
|
16
|
+
describe("isExplicitViewProperty", () => {
|
|
17
|
+
it("should detect As_IInterface pattern", () => {
|
|
18
|
+
assert.ok(isExplicitViewProperty("As_ICollection"));
|
|
19
|
+
assert.ok(isExplicitViewProperty("As_IEnumerable"));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return false for non-view properties", () => {
|
|
23
|
+
assert.ok(!isExplicitViewProperty("Length"));
|
|
24
|
+
assert.ok(!isExplicitViewProperty("Count"));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("extractInterfaceNameFromView", () => {
|
|
29
|
+
it("should extract interface name from As_ prefix", () => {
|
|
30
|
+
const name = extractInterfaceNameFromView("As_ICollection");
|
|
31
|
+
|
|
32
|
+
assert.equal(name, "ICollection");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should handle generic interface names", () => {
|
|
36
|
+
const name = extractInterfaceNameFromView("As_IEnumerable_1");
|
|
37
|
+
|
|
38
|
+
assert.equal(name, "IEnumerable_1");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return undefined for invalid pattern", () => {
|
|
42
|
+
const name = extractInterfaceNameFromView("InvalidProperty");
|
|
43
|
+
|
|
44
|
+
assert.equal(name, undefined);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("buildViewPropertyName", () => {
|
|
49
|
+
it("should build As_ property name from interface", () => {
|
|
50
|
+
const name = buildViewPropertyName("ICollection");
|
|
51
|
+
|
|
52
|
+
assert.equal(name, "As_ICollection");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle generic interfaces", () => {
|
|
56
|
+
const name = buildViewPropertyName("IEnumerable_1");
|
|
57
|
+
|
|
58
|
+
assert.equal(name, "As_IEnumerable_1");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("generateInterfaceCast", () => {
|
|
63
|
+
it("should generate C# cast expression", () => {
|
|
64
|
+
const cast = generateInterfaceCast(
|
|
65
|
+
"list",
|
|
66
|
+
"System.Collections.ICollection"
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
assert.equal(cast, "((ICollection)list)");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should extract short name from qualified name", () => {
|
|
73
|
+
const cast = generateInterfaceCast(
|
|
74
|
+
"obj",
|
|
75
|
+
"System.Collections.Generic.IEnumerable"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
assert.equal(cast, "((IEnumerable)obj)");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("generateGenericInterfaceCast", () => {
|
|
83
|
+
it("should generate generic cast with type arguments", () => {
|
|
84
|
+
const cast = generateGenericInterfaceCast(
|
|
85
|
+
"list",
|
|
86
|
+
"System.Collections.Generic.ICollection`1",
|
|
87
|
+
["string"]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
assert.equal(cast, "((ICollection<string>)list)");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should handle multiple type arguments", () => {
|
|
94
|
+
const cast = generateGenericInterfaceCast(
|
|
95
|
+
"dict",
|
|
96
|
+
"System.Collections.Generic.IDictionary`2",
|
|
97
|
+
["string", "int"]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
assert.equal(cast, "((IDictionary<string, int>)dict)");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should handle empty type arguments", () => {
|
|
104
|
+
const cast = generateGenericInterfaceCast(
|
|
105
|
+
"obj",
|
|
106
|
+
"System.Collections.ICollection",
|
|
107
|
+
[]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
assert.equal(cast, "((ICollection)obj)");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit Interface Views - Handle As_IInterface pattern for explicit implementations.
|
|
3
|
+
*
|
|
4
|
+
* C# supports explicit interface implementation (EII), where a class implements
|
|
5
|
+
* an interface member explicitly. TypeScript doesn't support EII, so tsbindgen
|
|
6
|
+
* generates special As_IInterface properties that return views of the object.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* TypeScript: list.As_ICollection.CopyTo(array, 0)
|
|
10
|
+
* C#: ((ICollection<T>)list).CopyTo(array, 0)
|
|
11
|
+
*
|
|
12
|
+
* @see spec/explicit-interface-views.md for complete documentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ExplicitView } from "./metadata.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pattern for explicit interface view property names.
|
|
19
|
+
* Format: As_IInterfaceName
|
|
20
|
+
*/
|
|
21
|
+
const VIEW_PROPERTY_PREFIX = "As_";
|
|
22
|
+
const VIEW_PROPERTY_PATTERN = /^As_(.+)$/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a property name matches the explicit view pattern (As_IInterface).
|
|
26
|
+
*
|
|
27
|
+
* @param propertyName - Property name to check
|
|
28
|
+
* @returns True if matches As_IInterface pattern
|
|
29
|
+
*/
|
|
30
|
+
export const isExplicitViewProperty = (propertyName: string): boolean => {
|
|
31
|
+
return propertyName.startsWith(VIEW_PROPERTY_PREFIX);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract interface name from an explicit view property name.
|
|
36
|
+
*
|
|
37
|
+
* @param viewPropertyName - Property name (e.g., "As_ICollection")
|
|
38
|
+
* @returns Interface name (e.g., "ICollection"), or undefined if not a view property
|
|
39
|
+
*/
|
|
40
|
+
export const extractInterfaceNameFromView = (
|
|
41
|
+
viewPropertyName: string
|
|
42
|
+
): string | undefined => {
|
|
43
|
+
const match = viewPropertyName.match(VIEW_PROPERTY_PATTERN);
|
|
44
|
+
if (!match) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return match[1];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build explicit view property name from interface name.
|
|
52
|
+
*
|
|
53
|
+
* @param interfaceName - Interface name (e.g., "ICollection")
|
|
54
|
+
* @returns View property name (e.g., "As_ICollection")
|
|
55
|
+
*/
|
|
56
|
+
export const buildViewPropertyName = (interfaceName: string): string => {
|
|
57
|
+
return `${VIEW_PROPERTY_PREFIX}${interfaceName}`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find explicit view for a given interface in type metadata.
|
|
62
|
+
*
|
|
63
|
+
* @param explicitViews - Array of explicit views from metadata
|
|
64
|
+
* @param interfaceName - CLR interface name to find (e.g., "System.Collections.ICollection")
|
|
65
|
+
* @returns Explicit view if found, undefined otherwise
|
|
66
|
+
*/
|
|
67
|
+
export const findExplicitView = (
|
|
68
|
+
explicitViews: readonly ExplicitView[] | undefined,
|
|
69
|
+
interfaceName: string
|
|
70
|
+
): ExplicitView | undefined => {
|
|
71
|
+
if (!explicitViews) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return explicitViews.find((view) => view.interfaceName === interfaceName);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if a member is in an explicit view (view-only member).
|
|
80
|
+
*
|
|
81
|
+
* @param explicitViews - Array of explicit views from metadata
|
|
82
|
+
* @param memberName - CLR member name to check
|
|
83
|
+
* @returns True if member appears in any view
|
|
84
|
+
*/
|
|
85
|
+
export const isMemberInExplicitView = (
|
|
86
|
+
explicitViews: readonly ExplicitView[] | undefined,
|
|
87
|
+
memberName: string
|
|
88
|
+
): boolean => {
|
|
89
|
+
if (!explicitViews) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const view of explicitViews) {
|
|
94
|
+
if (view.members.some((m) => m.clrName === memberName)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get all interface names that have explicit views.
|
|
104
|
+
*
|
|
105
|
+
* @param explicitViews - Array of explicit views from metadata
|
|
106
|
+
* @returns Array of interface CLR names
|
|
107
|
+
*/
|
|
108
|
+
export const getExplicitViewInterfaces = (
|
|
109
|
+
explicitViews: readonly ExplicitView[] | undefined
|
|
110
|
+
): readonly string[] => {
|
|
111
|
+
if (!explicitViews) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return explicitViews.map((view) => view.interfaceName);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Information about an explicit view member access.
|
|
120
|
+
*/
|
|
121
|
+
export type ExplicitViewAccess = {
|
|
122
|
+
readonly interfaceName: string;
|
|
123
|
+
readonly interfaceTsName: string;
|
|
124
|
+
readonly memberName: string;
|
|
125
|
+
readonly memberKind: "Method" | "Property" | "Event";
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse an explicit view member access expression.
|
|
130
|
+
*
|
|
131
|
+
* Example: obj.As_ICollection.CopyTo
|
|
132
|
+
* Returns: { interfaceName: "ICollection", memberName: "CopyTo", ... }
|
|
133
|
+
*
|
|
134
|
+
* @param viewPropertyName - View property name (e.g., "As_ICollection")
|
|
135
|
+
* @param memberName - Member being accessed (e.g., "CopyTo")
|
|
136
|
+
* @param explicitViews - Array of explicit views from metadata
|
|
137
|
+
* @returns View access info if valid, undefined if invalid
|
|
138
|
+
*/
|
|
139
|
+
export const parseExplicitViewAccess = (
|
|
140
|
+
viewPropertyName: string,
|
|
141
|
+
memberName: string,
|
|
142
|
+
explicitViews: readonly ExplicitView[] | undefined
|
|
143
|
+
): ExplicitViewAccess | undefined => {
|
|
144
|
+
// Extract interface name from view property
|
|
145
|
+
const interfaceTsName = extractInterfaceNameFromView(viewPropertyName);
|
|
146
|
+
if (!interfaceTsName) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Find the explicit view in metadata
|
|
151
|
+
if (!explicitViews) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Look for view that matches the property name
|
|
156
|
+
const view = explicitViews.find((v) => v.tsPropertyName === viewPropertyName);
|
|
157
|
+
if (!view) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Find the member in the view
|
|
162
|
+
const member = view.members.find((m) => m.name === memberName);
|
|
163
|
+
if (!member) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
interfaceName: view.interfaceName,
|
|
169
|
+
interfaceTsName,
|
|
170
|
+
memberName: member.name,
|
|
171
|
+
memberKind: member.kind,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate C# interface cast expression for explicit view access.
|
|
177
|
+
*
|
|
178
|
+
* @param objectExpression - C# expression for the object
|
|
179
|
+
* @param interfaceName - CLR interface name (e.g., "System.Collections.ICollection")
|
|
180
|
+
* @returns C# cast expression (e.g., "((ICollection)obj)")
|
|
181
|
+
*/
|
|
182
|
+
export const generateInterfaceCast = (
|
|
183
|
+
objectExpression: string,
|
|
184
|
+
interfaceName: string
|
|
185
|
+
): string => {
|
|
186
|
+
// Extract short name from fully-qualified name
|
|
187
|
+
const shortName = interfaceName.split(".").pop() || interfaceName;
|
|
188
|
+
|
|
189
|
+
return `((${shortName})${objectExpression})`;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Generate C# interface cast with generics.
|
|
194
|
+
*
|
|
195
|
+
* @param objectExpression - C# expression for the object
|
|
196
|
+
* @param interfaceName - CLR interface name (e.g., "System.Collections.Generic.ICollection`1")
|
|
197
|
+
* @param genericArguments - Generic type arguments in C# syntax (e.g., ["string"])
|
|
198
|
+
* @returns C# cast expression (e.g., "((ICollection<string>)obj)")
|
|
199
|
+
*/
|
|
200
|
+
export const generateGenericInterfaceCast = (
|
|
201
|
+
objectExpression: string,
|
|
202
|
+
interfaceName: string,
|
|
203
|
+
genericArguments: readonly string[]
|
|
204
|
+
): string => {
|
|
205
|
+
// Extract short name from fully-qualified name
|
|
206
|
+
const shortName = interfaceName.split(".").pop() || interfaceName;
|
|
207
|
+
|
|
208
|
+
// Remove generic arity suffix (`1, `2, etc.)
|
|
209
|
+
const nameWithoutArity = shortName.replace(/`\d+$/, "");
|
|
210
|
+
|
|
211
|
+
// Build generic type
|
|
212
|
+
const genericType =
|
|
213
|
+
genericArguments.length > 0
|
|
214
|
+
? `${nameWithoutArity}<${genericArguments.join(", ")}>`
|
|
215
|
+
: nameWithoutArity;
|
|
216
|
+
|
|
217
|
+
return `((${genericType})${objectExpression})`;
|
|
218
|
+
};
|