@stream44.studio/encapsulate 0.2.0-rc.2 → 0.2.0-rc.4
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.
Potentially problematic release.
This version of @stream44.studio/encapsulate might be problematic. Click here for more details.
- package/LICENSE.md +4 -199
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/capsule-projectors/CapsuleModuleProjector.v0.ts +81 -72
- package/src/encapsulate.ts +175 -25
- package/src/spine-contracts/CapsuleSpineContract.v0/Membrane.v0.ts +207 -135
- package/src/spine-contracts/CapsuleSpineContract.v0/Static.v0.ts +131 -37
- package/src/spine-factories/CapsuleSpineFactory.v0.ts +53 -6
- package/src/static-analyzer.v0.ts +291 -52
|
@@ -22,25 +22,25 @@ const ENCAPSULATE_MODULE_EXPORTS = new Set([
|
|
|
22
22
|
async function constructNpmUri(absoluteFilepath: string, spineRoot: string): Promise<string | null> {
|
|
23
23
|
let currentDir = dirname(absoluteFilepath)
|
|
24
24
|
const maxDepth = 20 // Prevent infinite loops
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
for (let i = 0; i < maxDepth; i++) {
|
|
27
27
|
const packageJsonPath = join(currentDir, 'package.json')
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
try {
|
|
30
30
|
await stat(packageJsonPath)
|
|
31
31
|
// Found package.json, read it
|
|
32
32
|
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'))
|
|
33
33
|
const packageName = packageJson.name
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
if (!packageName) {
|
|
36
36
|
// No name in package.json, continue searching
|
|
37
37
|
currentDir = dirname(currentDir)
|
|
38
38
|
continue
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
// Get the relative path from the package root to the file
|
|
42
42
|
const relativeFromPackage = relative(currentDir, absoluteFilepath)
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// Construct npm URI: packageName/relativePath
|
|
45
45
|
return `${packageName}/${relativeFromPackage}`
|
|
46
46
|
} catch (error) {
|
|
@@ -53,7 +53,7 @@ async function constructNpmUri(absoluteFilepath: string, spineRoot: string): Pro
|
|
|
53
53
|
currentDir = parentDir
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
return null
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -63,6 +63,12 @@ const MODULE_GLOBAL_BUILTINS = new Set([
|
|
|
63
63
|
|
|
64
64
|
'process',
|
|
65
65
|
|
|
66
|
+
// Bun runtime
|
|
67
|
+
'Bun',
|
|
68
|
+
|
|
69
|
+
// Node.js Buffer
|
|
70
|
+
'Buffer',
|
|
71
|
+
|
|
66
72
|
// Console API
|
|
67
73
|
'console',
|
|
68
74
|
|
|
@@ -186,7 +192,7 @@ export function StaticAnalyzer({
|
|
|
186
192
|
parseModule: async ({ spineOptions, encapsulateOptions }: { spineOptions: any, encapsulateOptions: any }) => {
|
|
187
193
|
|
|
188
194
|
const moduleFilepath = join(spineOptions.spineFilesystemRoot, encapsulateOptions.moduleFilepath)
|
|
189
|
-
|
|
195
|
+
|
|
190
196
|
// Determine the cache file path based on whether the module is external or internal
|
|
191
197
|
let cacheFilePath: string
|
|
192
198
|
if (encapsulateOptions.moduleFilepath.startsWith('../')) {
|
|
@@ -202,7 +208,7 @@ export function StaticAnalyzer({
|
|
|
202
208
|
// Internal module - use relative path as-is
|
|
203
209
|
cacheFilePath = encapsulateOptions.moduleFilepath
|
|
204
210
|
}
|
|
205
|
-
|
|
211
|
+
|
|
206
212
|
const capsuleSourceLineRef = `${cacheFilePath}:${encapsulateOptions.importStackLine}`
|
|
207
213
|
|
|
208
214
|
// Try to load from cache first
|
|
@@ -306,16 +312,16 @@ export function StaticAnalyzer({
|
|
|
306
312
|
|
|
307
313
|
// Construct npm URI for the module - try for all modules
|
|
308
314
|
let moduleUri: string | null = await constructNpmUri(moduleFilepath, spineOptions.spineFilesystemRoot)
|
|
309
|
-
|
|
315
|
+
|
|
310
316
|
// If npm URI construction failed, fall back to moduleFilepath
|
|
311
317
|
if (!moduleUri) {
|
|
312
318
|
moduleUri = encapsulateOptions.moduleFilepath
|
|
313
319
|
}
|
|
314
|
-
|
|
320
|
+
|
|
315
321
|
// Strip file extension from URI
|
|
316
322
|
const moduleUriWithoutExt = moduleUri.replace(/\.(ts|tsx|js|jsx)$/, '')
|
|
317
323
|
const capsuleSourceUriLineRef = `${moduleUriWithoutExt}:${encapsulateOptions.importStackLine}`
|
|
318
|
-
|
|
324
|
+
|
|
319
325
|
// Store moduleUri without extension
|
|
320
326
|
moduleUri = moduleUriWithoutExt
|
|
321
327
|
|
|
@@ -371,6 +377,21 @@ export function StaticAnalyzer({
|
|
|
371
377
|
const optionsEndPos = sourceFile.getLineAndCharacterOfPosition(optionsObject.getEnd())
|
|
372
378
|
cst.source.optionsStartLine = optionsStartPos.line + 1
|
|
373
379
|
cst.source.optionsEndLine = optionsEndPos.line + 1
|
|
380
|
+
|
|
381
|
+
// Extract extendsCapsule option if present
|
|
382
|
+
for (const prop of optionsObject.properties) {
|
|
383
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'extendsCapsule') {
|
|
384
|
+
// Check if it's a string literal (relative path or npm URI)
|
|
385
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
386
|
+
cst.source.extendsCapsule = prop.initializer.text
|
|
387
|
+
}
|
|
388
|
+
// Check if it's an identifier (capsule variable reference)
|
|
389
|
+
else if (ts.isIdentifier(prop.initializer)) {
|
|
390
|
+
cst.source.extendsCapsule = prop.initializer.text
|
|
391
|
+
}
|
|
392
|
+
break
|
|
393
|
+
}
|
|
394
|
+
}
|
|
374
395
|
}
|
|
375
396
|
|
|
376
397
|
// Parse spineContract definitions (e.g., '$spineContract1': { ... })
|
|
@@ -423,6 +444,15 @@ export function StaticAnalyzer({
|
|
|
423
444
|
}
|
|
424
445
|
}
|
|
425
446
|
|
|
447
|
+
// Check for 'as' property at the property contract level
|
|
448
|
+
for (const contractProp of propValue.properties) {
|
|
449
|
+
if (ts.isPropertyAssignment(contractProp) && ts.isIdentifier(contractProp.name) && contractProp.name.text === 'as') {
|
|
450
|
+
if (ts.isStringLiteral(contractProp.initializer)) {
|
|
451
|
+
spineContractDef.properties[propName].as = contractProp.initializer.text
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
426
456
|
// Parse properties within the property contract
|
|
427
457
|
for (const contractProp of propValue.properties) {
|
|
428
458
|
if (ts.isPropertyAssignment(contractProp) && (ts.isIdentifier(contractProp.name) || ts.isStringLiteral(contractProp.name))) {
|
|
@@ -533,7 +563,9 @@ export function StaticAnalyzer({
|
|
|
533
563
|
|
|
534
564
|
// Add a dynamic mapping for each non-default property contract
|
|
535
565
|
for (const propContractUri of nonDefaultContracts) {
|
|
536
|
-
|
|
566
|
+
// Check if 'as' alias is defined for this property contract
|
|
567
|
+
const aliasName = spineContract.properties[propContractUri]?.as
|
|
568
|
+
const contractKey = aliasName || ('#' + propContractUri.substring(1))
|
|
537
569
|
spineContract.properties['#'].properties[contractKey] = {
|
|
538
570
|
declarationLine: -1,
|
|
539
571
|
definitionStartLine: -1,
|
|
@@ -541,7 +573,8 @@ export function StaticAnalyzer({
|
|
|
541
573
|
type: 'CapsulePropertyTypes.Mapping',
|
|
542
574
|
valueType: 'string',
|
|
543
575
|
valueExpression: `"${propContractUri.substring(1)}"`,
|
|
544
|
-
propertyContractDelegate: propContractUri
|
|
576
|
+
propertyContractDelegate: propContractUri,
|
|
577
|
+
as: aliasName
|
|
545
578
|
}
|
|
546
579
|
}
|
|
547
580
|
}
|
|
@@ -777,7 +810,7 @@ function extractFunctionSignature(fn: ts.FunctionExpression | ts.ArrowFunction,
|
|
|
777
810
|
return `(${params.join(', ')}) => ${returnType}`
|
|
778
811
|
}
|
|
779
812
|
|
|
780
|
-
// Extract module-local functions that are self-contained
|
|
813
|
+
// Extract module-local functions and variables that are self-contained
|
|
781
814
|
function extractModuleLocalCode(
|
|
782
815
|
ambientReferences: Record<string, any>,
|
|
783
816
|
sourceFile: ts.SourceFile,
|
|
@@ -787,12 +820,21 @@ function extractModuleLocalCode(
|
|
|
787
820
|
): Record<string, string> {
|
|
788
821
|
const moduleLocalCode: Record<string, string> = {}
|
|
789
822
|
const moduleLocalFunctions = new Map<string, ts.FunctionDeclaration>()
|
|
823
|
+
const moduleLocalVariables = new Map<string, ts.VariableDeclaration>()
|
|
790
824
|
|
|
791
|
-
// First, collect all top-level function declarations in the module
|
|
825
|
+
// First, collect all top-level function declarations and variable declarations in the module
|
|
792
826
|
for (const statement of sourceFile.statements) {
|
|
793
827
|
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
794
828
|
moduleLocalFunctions.set(statement.name.text, statement)
|
|
795
829
|
}
|
|
830
|
+
// Collect module-level variable declarations
|
|
831
|
+
if (ts.isVariableStatement(statement)) {
|
|
832
|
+
for (const decl of statement.declarationList.declarations) {
|
|
833
|
+
if (ts.isIdentifier(decl.name)) {
|
|
834
|
+
moduleLocalVariables.set(decl.name.text, decl)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
796
838
|
}
|
|
797
839
|
|
|
798
840
|
// Also collect functions from the local scope around the call node
|
|
@@ -813,7 +855,7 @@ function extractModuleLocalCode(
|
|
|
813
855
|
}
|
|
814
856
|
}
|
|
815
857
|
|
|
816
|
-
// Check each ambient reference to see if it's a module-local function
|
|
858
|
+
// Check each ambient reference to see if it's a module-local function or variable
|
|
817
859
|
for (const [name, ref] of Object.entries(ambientReferences)) {
|
|
818
860
|
const refTyped = ref as any
|
|
819
861
|
|
|
@@ -824,45 +866,68 @@ function extractModuleLocalCode(
|
|
|
824
866
|
|
|
825
867
|
// Check if this identifier refers to a module-local function
|
|
826
868
|
const funcDecl = moduleLocalFunctions.get(name)
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
869
|
+
if (funcDecl) {
|
|
870
|
+
// Analyze the function to see if it's self-contained
|
|
871
|
+
const dependencies = new Set<string>()
|
|
872
|
+
const isContained = analyzeFunctionDependencies(funcDecl, sourceFile, importMap, assignmentMap, moduleLocalFunctions, dependencies)
|
|
873
|
+
|
|
874
|
+
if (isContained) {
|
|
875
|
+
// Mark this as module-local in ambient references
|
|
876
|
+
refTyped.type = 'module-local'
|
|
877
|
+
|
|
878
|
+
// Collect the function code and all its dependencies
|
|
879
|
+
const collectedCode: string[] = []
|
|
880
|
+
const processed = new Set<string>()
|
|
881
|
+
|
|
882
|
+
function collectFunction(fnName: string) {
|
|
883
|
+
if (processed.has(fnName)) return
|
|
884
|
+
processed.add(fnName)
|
|
885
|
+
|
|
886
|
+
const fn = moduleLocalFunctions.get(fnName)
|
|
887
|
+
if (fn) {
|
|
888
|
+
const fnCode = fn.getText(sourceFile)
|
|
889
|
+
collectedCode.push(fnCode)
|
|
890
|
+
// Also add each function as a separate entry in moduleLocalCode
|
|
891
|
+
if (!moduleLocalCode[fnName]) {
|
|
892
|
+
moduleLocalCode[fnName] = fnCode
|
|
893
|
+
}
|
|
852
894
|
}
|
|
853
895
|
}
|
|
854
|
-
}
|
|
855
896
|
|
|
856
|
-
|
|
857
|
-
|
|
897
|
+
// Collect the main function
|
|
898
|
+
collectFunction(name)
|
|
899
|
+
|
|
900
|
+
// Collect all dependencies
|
|
901
|
+
for (const dep of dependencies) {
|
|
902
|
+
collectFunction(dep)
|
|
903
|
+
}
|
|
858
904
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
collectFunction(dep)
|
|
905
|
+
// Store the collected code (main function with all dependencies)
|
|
906
|
+
moduleLocalCode[name] = collectedCode.join('\n\n')
|
|
862
907
|
}
|
|
908
|
+
continue
|
|
909
|
+
}
|
|
863
910
|
|
|
864
|
-
|
|
865
|
-
|
|
911
|
+
// Check if this identifier refers to a module-local variable
|
|
912
|
+
const varDecl = moduleLocalVariables.get(name)
|
|
913
|
+
if (varDecl) {
|
|
914
|
+
// Analyze the variable to see if it's self-contained
|
|
915
|
+
const varDependencies = analyzeVariableDependencies(varDecl, sourceFile, importMap, assignmentMap, moduleLocalFunctions, moduleLocalVariables)
|
|
916
|
+
|
|
917
|
+
if (varDependencies.isContained) {
|
|
918
|
+
// Mark this as module-local in ambient references
|
|
919
|
+
refTyped.type = 'module-local'
|
|
920
|
+
|
|
921
|
+
// Get the variable declaration code
|
|
922
|
+
// We need to get the full variable statement (const/let/var KEYS_DIR = ...)
|
|
923
|
+
const varStatement = varDecl.parent?.parent
|
|
924
|
+
if (varStatement && ts.isVariableStatement(varStatement)) {
|
|
925
|
+
moduleLocalCode[name] = varStatement.getText(sourceFile)
|
|
926
|
+
} else {
|
|
927
|
+
// Fallback to just the declaration
|
|
928
|
+
moduleLocalCode[name] = varDecl.getText(sourceFile)
|
|
929
|
+
}
|
|
930
|
+
}
|
|
866
931
|
}
|
|
867
932
|
}
|
|
868
933
|
|
|
@@ -961,6 +1026,11 @@ function analyzeFunctionDependencies(
|
|
|
961
1026
|
|
|
962
1027
|
// Second pass: check for external dependencies
|
|
963
1028
|
function visit(node: ts.Node, currentScope: Set<string> = localIdentifiers) {
|
|
1029
|
+
// Skip type nodes to avoid false positives from type annotations
|
|
1030
|
+
if (ts.isTypeNode(node)) {
|
|
1031
|
+
return
|
|
1032
|
+
}
|
|
1033
|
+
|
|
964
1034
|
// Use the appropriate scope for nested functions
|
|
965
1035
|
if (nestedFunctionScopes.has(node)) {
|
|
966
1036
|
currentScope = nestedFunctionScopes.get(node)!
|
|
@@ -1028,6 +1098,92 @@ function analyzeFunctionDependencies(
|
|
|
1028
1098
|
return isContained
|
|
1029
1099
|
}
|
|
1030
1100
|
|
|
1101
|
+
// Analyze if a variable declaration is self-contained (only depends on imports and builtins)
|
|
1102
|
+
// Returns whether it's contained and the import dependencies needed
|
|
1103
|
+
function analyzeVariableDependencies(
|
|
1104
|
+
varDecl: ts.VariableDeclaration,
|
|
1105
|
+
sourceFile: ts.SourceFile,
|
|
1106
|
+
importMap: Map<string, { importSpecifier: string, moduleUri: string }>,
|
|
1107
|
+
assignmentMap: Map<string, { importSpecifier: string, moduleUri: string }>,
|
|
1108
|
+
moduleLocalFunctions: Map<string, ts.FunctionDeclaration>,
|
|
1109
|
+
moduleLocalVariables: Map<string, ts.VariableDeclaration>
|
|
1110
|
+
): { isContained: boolean, importDependencies: Map<string, { importSpecifier: string, moduleUri: string }> } {
|
|
1111
|
+
const importDependencies = new Map<string, { importSpecifier: string, moduleUri: string }>()
|
|
1112
|
+
let isContained = true
|
|
1113
|
+
|
|
1114
|
+
if (!varDecl.initializer) {
|
|
1115
|
+
// No initializer means it's just a declaration, treat as contained
|
|
1116
|
+
return { isContained: true, importDependencies }
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function visit(node: ts.Node) {
|
|
1120
|
+
// Skip type nodes
|
|
1121
|
+
if (ts.isTypeNode(node)) {
|
|
1122
|
+
return
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (ts.isIdentifier(node)) {
|
|
1126
|
+
const identifierName = node.text
|
|
1127
|
+
|
|
1128
|
+
// Skip special keywords
|
|
1129
|
+
if (identifierName === 'this' || identifierName === 'undefined' || identifierName === 'null') {
|
|
1130
|
+
return
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Skip property access names
|
|
1134
|
+
const parent = node.parent
|
|
1135
|
+
if (parent && ts.isPropertyAccessExpression(parent) && parent.name === node) {
|
|
1136
|
+
return
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Skip property names in object literals
|
|
1140
|
+
if (parent && ts.isPropertyAssignment(parent) && parent.name === node) {
|
|
1141
|
+
return
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Check if it's an import - track as dependency
|
|
1145
|
+
const importInfo = importMap.get(identifierName)
|
|
1146
|
+
if (importInfo) {
|
|
1147
|
+
importDependencies.set(identifierName, importInfo)
|
|
1148
|
+
return
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Check if it's an assignment from import
|
|
1152
|
+
const assignmentInfo = assignmentMap.get(identifierName)
|
|
1153
|
+
if (assignmentInfo) {
|
|
1154
|
+
importDependencies.set(identifierName, assignmentInfo)
|
|
1155
|
+
return
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Check if it's a module-global builtin - allowed
|
|
1159
|
+
if (MODULE_GLOBAL_BUILTINS.has(identifierName)) {
|
|
1160
|
+
return
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Check if it's another module-local variable - recursively analyze
|
|
1164
|
+
if (moduleLocalVariables.has(identifierName)) {
|
|
1165
|
+
// For now, allow references to other module-local variables
|
|
1166
|
+
// A more complete implementation would recursively analyze
|
|
1167
|
+
return
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Check if it's a module-local function - allowed
|
|
1171
|
+
if (moduleLocalFunctions.has(identifierName)) {
|
|
1172
|
+
return
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Unknown external reference - not self-contained
|
|
1176
|
+
isContained = false
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
ts.forEachChild(node, visit)
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
visit(varDecl.initializer)
|
|
1183
|
+
|
|
1184
|
+
return { isContained, importDependencies }
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1031
1187
|
// Helper to extract binding identifiers for analysis
|
|
1032
1188
|
function extractBindingIdentifiersForAnalysis(name: ts.BindingName, targetSet: Set<string>) {
|
|
1033
1189
|
if (ts.isIdentifier(name)) {
|
|
@@ -1060,13 +1216,21 @@ function extractCapsuleAmbientReferences(
|
|
|
1060
1216
|
const propertyNames = new Set<string>()
|
|
1061
1217
|
const invocationParameters = new Set<string>()
|
|
1062
1218
|
const moduleLocalFunctions = new Map<string, ts.FunctionDeclaration>()
|
|
1219
|
+
const moduleLocalVariables = new Map<string, ts.VariableDeclaration>()
|
|
1063
1220
|
|
|
1064
|
-
// Collect module-local functions from
|
|
1065
|
-
// First, collect top-level module functions
|
|
1221
|
+
// Collect module-local functions and variables from module top-level
|
|
1066
1222
|
for (const statement of sourceFile.statements) {
|
|
1067
1223
|
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
1068
1224
|
moduleLocalFunctions.set(statement.name.text, statement)
|
|
1069
1225
|
}
|
|
1226
|
+
// Collect module-level variable declarations
|
|
1227
|
+
if (ts.isVariableStatement(statement)) {
|
|
1228
|
+
for (const decl of statement.declarationList.declarations) {
|
|
1229
|
+
if (ts.isIdentifier(decl.name)) {
|
|
1230
|
+
moduleLocalVariables.set(decl.name.text, decl)
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1070
1234
|
}
|
|
1071
1235
|
|
|
1072
1236
|
// Find enclosing function and collect its parameters and local functions
|
|
@@ -1235,6 +1399,32 @@ function extractCapsuleAmbientReferences(
|
|
|
1235
1399
|
}
|
|
1236
1400
|
}
|
|
1237
1401
|
|
|
1402
|
+
// Check if it's a module-local variable (const/let/var at module level)
|
|
1403
|
+
const varDecl = moduleLocalVariables.get(identifierName)
|
|
1404
|
+
if (varDecl) {
|
|
1405
|
+
// Analyze the variable's initializer for dependencies
|
|
1406
|
+
const varDependencies = analyzeVariableDependencies(varDecl, sourceFile, importMap, assignmentMap, moduleLocalFunctions, moduleLocalVariables)
|
|
1407
|
+
|
|
1408
|
+
if (varDependencies.isContained) {
|
|
1409
|
+
// Mark as module-local and add any import dependencies to ambientRefs
|
|
1410
|
+
ambientRefs[identifierName] = {
|
|
1411
|
+
type: 'module-local'
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Add import dependencies from the variable's initializer
|
|
1415
|
+
for (const [depName, depInfo] of varDependencies.importDependencies) {
|
|
1416
|
+
if (!ambientRefs[depName]) {
|
|
1417
|
+
ambientRefs[depName] = {
|
|
1418
|
+
type: 'import',
|
|
1419
|
+
importSpecifier: depInfo.importSpecifier,
|
|
1420
|
+
moduleUri: depInfo.moduleUri
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1238
1428
|
// This is a literal ambient reference
|
|
1239
1429
|
// Check if the ambient reference is provided
|
|
1240
1430
|
if (runtimeAmbientRefs && identifierName in runtimeAmbientRefs) {
|
|
@@ -1282,12 +1472,22 @@ function extractAndValidateAmbientReferences(
|
|
|
1282
1472
|
): Record<string, any> {
|
|
1283
1473
|
// Build module-local functions map for checking
|
|
1284
1474
|
const moduleLocalFunctions = new Map<string, ts.FunctionDeclaration>()
|
|
1475
|
+
// Build module-local variables map for checking (const/let/var declarations at module level)
|
|
1476
|
+
const moduleLocalVariables = new Map<string, ts.VariableDeclaration>()
|
|
1285
1477
|
|
|
1286
|
-
// Collect top-level module functions
|
|
1478
|
+
// Collect top-level module functions and variables
|
|
1287
1479
|
for (const statement of sourceFile.statements) {
|
|
1288
1480
|
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
1289
1481
|
moduleLocalFunctions.set(statement.name.text, statement)
|
|
1290
1482
|
}
|
|
1483
|
+
// Collect module-level variable declarations
|
|
1484
|
+
if (ts.isVariableStatement(statement)) {
|
|
1485
|
+
for (const decl of statement.declarationList.declarations) {
|
|
1486
|
+
if (ts.isIdentifier(decl.name)) {
|
|
1487
|
+
moduleLocalVariables.set(decl.name.text, decl)
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1291
1491
|
}
|
|
1292
1492
|
|
|
1293
1493
|
const ambientRefs: Record<string, any> = {}
|
|
@@ -1381,6 +1581,10 @@ function extractAndValidateAmbientReferences(
|
|
|
1381
1581
|
// Track function declarations
|
|
1382
1582
|
if (ts.isFunctionDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
|
|
1383
1583
|
localIdentifiers.add(node.name.text)
|
|
1584
|
+
// Also track parameters from nested function declarations
|
|
1585
|
+
for (const param of node.parameters) {
|
|
1586
|
+
extractBindingIdentifiers(param.name)
|
|
1587
|
+
}
|
|
1384
1588
|
}
|
|
1385
1589
|
|
|
1386
1590
|
// Track named function expressions (e.g., function Counter() {})
|
|
@@ -1447,6 +1651,15 @@ function extractAndValidateAmbientReferences(
|
|
|
1447
1651
|
return
|
|
1448
1652
|
}
|
|
1449
1653
|
|
|
1654
|
+
// Skip if this is a capsule['#'] pattern - the capsule name reference
|
|
1655
|
+
// These references are resolved at encapsulation time and replaced with the actual string value
|
|
1656
|
+
if (parent && ts.isElementAccessExpression(parent) && parent.expression === node) {
|
|
1657
|
+
const arg = parent.argumentExpression
|
|
1658
|
+
if (arg && ts.isStringLiteral(arg) && arg.text === '#') {
|
|
1659
|
+
return
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1450
1663
|
// Check if we already added this reference
|
|
1451
1664
|
if (ambientRefs[identifierName]) {
|
|
1452
1665
|
return
|
|
@@ -1500,6 +1713,32 @@ function extractAndValidateAmbientReferences(
|
|
|
1500
1713
|
}
|
|
1501
1714
|
}
|
|
1502
1715
|
|
|
1716
|
+
// Check if it's a module-local variable (const/let/var at module level)
|
|
1717
|
+
const varDecl = moduleLocalVariables.get(identifierName)
|
|
1718
|
+
if (varDecl) {
|
|
1719
|
+
// Analyze the variable's initializer for dependencies
|
|
1720
|
+
const varDependencies = analyzeVariableDependencies(varDecl, sourceFile, importMap, assignmentMap, moduleLocalFunctions, moduleLocalVariables)
|
|
1721
|
+
|
|
1722
|
+
if (varDependencies.isContained) {
|
|
1723
|
+
// Mark as module-local and add any import dependencies to ambientRefs
|
|
1724
|
+
ambientRefs[identifierName] = {
|
|
1725
|
+
type: 'module-local'
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// Add import dependencies from the variable's initializer
|
|
1729
|
+
for (const [depName, depInfo] of varDependencies.importDependencies) {
|
|
1730
|
+
if (!ambientRefs[depName]) {
|
|
1731
|
+
ambientRefs[depName] = {
|
|
1732
|
+
type: 'import',
|
|
1733
|
+
importSpecifier: depInfo.importSpecifier,
|
|
1734
|
+
moduleUri: depInfo.moduleUri
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1503
1742
|
// Check if this is a JSX intrinsic element (like 'div', 'button', etc.) in a .tsx/.jsx file
|
|
1504
1743
|
const fileName = sourceFile.fileName
|
|
1505
1744
|
const isJsxFile = fileName.endsWith('.tsx') || fileName.endsWith('.jsx')
|