@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.
Files changed (145) hide show
  1. package/package.json +53 -0
  2. package/src/dependency-graph.ts +18 -0
  3. package/src/dotnet-metadata.ts +121 -0
  4. package/src/graph/builder.ts +81 -0
  5. package/src/graph/circular.ts +58 -0
  6. package/src/graph/extraction/exports.ts +55 -0
  7. package/src/graph/extraction/imports.ts +81 -0
  8. package/src/graph/extraction/index.ts +7 -0
  9. package/src/graph/extraction/orchestrator.ts +99 -0
  10. package/src/graph/extraction.ts +10 -0
  11. package/src/graph/helpers.ts +51 -0
  12. package/src/graph/index.ts +17 -0
  13. package/src/graph/types.ts +13 -0
  14. package/src/index.ts +80 -0
  15. package/src/ir/binding-resolution.test.ts +585 -0
  16. package/src/ir/builder/exports.ts +78 -0
  17. package/src/ir/builder/helpers.ts +27 -0
  18. package/src/ir/builder/imports.ts +153 -0
  19. package/src/ir/builder/index.ts +10 -0
  20. package/src/ir/builder/orchestrator.ts +178 -0
  21. package/src/ir/builder/statements.ts +55 -0
  22. package/src/ir/builder/types.ts +8 -0
  23. package/src/ir/builder/validation.ts +129 -0
  24. package/src/ir/builder.test.ts +581 -0
  25. package/src/ir/builder.ts +14 -0
  26. package/src/ir/converters/expressions/access.ts +99 -0
  27. package/src/ir/converters/expressions/calls.ts +137 -0
  28. package/src/ir/converters/expressions/collections.ts +84 -0
  29. package/src/ir/converters/expressions/functions.ts +62 -0
  30. package/src/ir/converters/expressions/helpers.ts +264 -0
  31. package/src/ir/converters/expressions/index.ts +43 -0
  32. package/src/ir/converters/expressions/literals.ts +22 -0
  33. package/src/ir/converters/expressions/operators.ts +147 -0
  34. package/src/ir/converters/expressions/other.ts +60 -0
  35. package/src/ir/converters/statements/control/blocks.ts +22 -0
  36. package/src/ir/converters/statements/control/conditionals.ts +67 -0
  37. package/src/ir/converters/statements/control/exceptions.ts +43 -0
  38. package/src/ir/converters/statements/control/index.ts +17 -0
  39. package/src/ir/converters/statements/control/loops.ts +99 -0
  40. package/src/ir/converters/statements/control.ts +17 -0
  41. package/src/ir/converters/statements/declarations/classes/constructors.ts +120 -0
  42. package/src/ir/converters/statements/declarations/classes/index.ts +12 -0
  43. package/src/ir/converters/statements/declarations/classes/methods.ts +61 -0
  44. package/src/ir/converters/statements/declarations/classes/orchestrator.ts +166 -0
  45. package/src/ir/converters/statements/declarations/classes/override-detection.ts +116 -0
  46. package/src/ir/converters/statements/declarations/classes/properties.ts +63 -0
  47. package/src/ir/converters/statements/declarations/classes.ts +6 -0
  48. package/src/ir/converters/statements/declarations/enums.ts +29 -0
  49. package/src/ir/converters/statements/declarations/functions.ts +39 -0
  50. package/src/ir/converters/statements/declarations/index.ts +14 -0
  51. package/src/ir/converters/statements/declarations/interfaces.ts +131 -0
  52. package/src/ir/converters/statements/declarations/registry.ts +45 -0
  53. package/src/ir/converters/statements/declarations/type-aliases.ts +25 -0
  54. package/src/ir/converters/statements/declarations/variables.ts +60 -0
  55. package/src/ir/converters/statements/declarations.ts +16 -0
  56. package/src/ir/converters/statements/helpers.ts +174 -0
  57. package/src/ir/converters/statements/index.ts +40 -0
  58. package/src/ir/expression-converter.ts +207 -0
  59. package/src/ir/generic-validator.ts +100 -0
  60. package/src/ir/hierarchical-bindings-e2e.test.ts +163 -0
  61. package/src/ir/index.ts +6 -0
  62. package/src/ir/statement-converter.ts +128 -0
  63. package/src/ir/type-converter/arrays.ts +20 -0
  64. package/src/ir/type-converter/converter.ts +10 -0
  65. package/src/ir/type-converter/functions.ts +22 -0
  66. package/src/ir/type-converter/index.ts +11 -0
  67. package/src/ir/type-converter/inference.ts +122 -0
  68. package/src/ir/type-converter/literals.ts +40 -0
  69. package/src/ir/type-converter/objects.ts +107 -0
  70. package/src/ir/type-converter/orchestrator.ts +85 -0
  71. package/src/ir/type-converter/patterns.ts +73 -0
  72. package/src/ir/type-converter/primitives.ts +57 -0
  73. package/src/ir/type-converter/references.ts +64 -0
  74. package/src/ir/type-converter/unions-intersections.ts +34 -0
  75. package/src/ir/type-converter.ts +13 -0
  76. package/src/ir/types/expressions.ts +215 -0
  77. package/src/ir/types/guards.ts +39 -0
  78. package/src/ir/types/helpers.ts +135 -0
  79. package/src/ir/types/index.ts +108 -0
  80. package/src/ir/types/ir-types.ts +96 -0
  81. package/src/ir/types/module.ts +57 -0
  82. package/src/ir/types/statements.ts +238 -0
  83. package/src/ir/types.ts +97 -0
  84. package/src/metadata/bindings-loader.test.ts +144 -0
  85. package/src/metadata/bindings-loader.ts +357 -0
  86. package/src/metadata/index.ts +15 -0
  87. package/src/metadata/library-loader.ts +153 -0
  88. package/src/metadata/loader.test.ts +156 -0
  89. package/src/metadata/loader.ts +382 -0
  90. package/src/program/bindings.test.ts +512 -0
  91. package/src/program/bindings.ts +253 -0
  92. package/src/program/config.ts +30 -0
  93. package/src/program/creation.ts +249 -0
  94. package/src/program/dependency-graph.ts +245 -0
  95. package/src/program/diagnostics.ts +103 -0
  96. package/src/program/index.ts +19 -0
  97. package/src/program/metadata.ts +68 -0
  98. package/src/program/queries.ts +18 -0
  99. package/src/program/types.ts +38 -0
  100. package/src/program.ts +13 -0
  101. package/src/resolver/dotnet-import-resolver.ts +226 -0
  102. package/src/resolver/import-resolution.ts +177 -0
  103. package/src/resolver/index.ts +18 -0
  104. package/src/resolver/namespace.test.ts +86 -0
  105. package/src/resolver/namespace.ts +42 -0
  106. package/src/resolver/naming.ts +38 -0
  107. package/src/resolver/path-resolution.ts +22 -0
  108. package/src/resolver/types.ts +15 -0
  109. package/src/resolver.test.ts +155 -0
  110. package/src/resolver.ts +14 -0
  111. package/src/symbol-table/builder.ts +114 -0
  112. package/src/symbol-table/creation.ts +42 -0
  113. package/src/symbol-table/helpers.ts +18 -0
  114. package/src/symbol-table/index.ts +13 -0
  115. package/src/symbol-table/queries.ts +42 -0
  116. package/src/symbol-table/types.ts +28 -0
  117. package/src/symbol-table.ts +14 -0
  118. package/src/types/bindings.ts +172 -0
  119. package/src/types/diagnostic.test.ts +164 -0
  120. package/src/types/diagnostic.ts +153 -0
  121. package/src/types/explicit-views.test.ts +113 -0
  122. package/src/types/explicit-views.ts +218 -0
  123. package/src/types/metadata.ts +229 -0
  124. package/src/types/module.ts +99 -0
  125. package/src/types/nested-types.test.ts +194 -0
  126. package/src/types/nested-types.ts +215 -0
  127. package/src/types/parameter-modifiers.ts +173 -0
  128. package/src/types/ref-parameters.test.ts +192 -0
  129. package/src/types/ref-parameters.ts +268 -0
  130. package/src/types/result.test.ts +157 -0
  131. package/src/types/result.ts +48 -0
  132. package/src/types/support-types.test.ts +81 -0
  133. package/src/types/support-types.ts +288 -0
  134. package/src/types/test-harness.ts +180 -0
  135. package/src/validation/exports.ts +98 -0
  136. package/src/validation/features.ts +89 -0
  137. package/src/validation/generics.ts +40 -0
  138. package/src/validation/helpers.ts +31 -0
  139. package/src/validation/imports.ts +97 -0
  140. package/src/validation/index.ts +11 -0
  141. package/src/validation/orchestrator.ts +51 -0
  142. package/src/validation/static-safety.ts +267 -0
  143. package/src/validator.test.ts +468 -0
  144. package/src/validator.ts +15 -0
  145. 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
+ };