@gtsx/core 0.0.1 → 0.0.3

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/dist/analyzer.js CHANGED
@@ -1,6 +1,18 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, relative, resolve, sep } from "node:path";
3
3
  import ts from "typescript";
4
+ export function createGTSXAnalysisCache(sourceFilesByPath = new Map()) {
5
+ return {
6
+ componentDependencyBindingsByPath: new Map(),
7
+ exportedComponentTargetsByPath: new Map(),
8
+ importedGTSXPathByKey: new Map(),
9
+ importedScopeHookNamesByPath: new Map(),
10
+ providerSummariesByPath: new Map(),
11
+ scopeHookProviderNamesByPath: new Map(),
12
+ sourceFilesByPath,
13
+ topLevelFunctionLikeBodiesByPath: new Map(),
14
+ };
15
+ }
4
16
  export function analyzeEntry(options) {
5
17
  const entryCoordinate = parseEntryCoordinate(options.entry);
6
18
  const entryPath = resolve(options.cwd, entryCoordinate.file);
@@ -22,11 +34,30 @@ export function analyzeEntry(options) {
22
34
  ],
23
35
  };
24
36
  }
25
- const sourceText = readFileSync(entryPath, "utf8");
26
- const sourceFile = ts.createSourceFile(entryPath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
37
+ const sourceFile = sourceFileForAbsolutePath(entryPath, options.cache);
38
+ if (!sourceFile) {
39
+ return {
40
+ entry: options.entry,
41
+ mode: "unknown",
42
+ defaultExport: false,
43
+ cases: [],
44
+ providers: {},
45
+ diagnostics: [
46
+ {
47
+ stage: "contract-extraction",
48
+ code: "entry-not-found",
49
+ message: `GTSX entry does not exist: ${options.entry}`,
50
+ file: options.entry,
51
+ },
52
+ ],
53
+ };
54
+ }
27
55
  const componentExportName = getComponentExportName(sourceFile, entryCoordinate.exportName);
28
- const scopeHookNames = getScopeHookNames(sourceFile);
29
- const providerCases = Object.fromEntries([...getGProviderNames(sourceFile)].map((name) => [name, { name, cases: [] }]));
56
+ const scopeHookNames = new Set([
57
+ ...getScopeHookNames(sourceFile),
58
+ ...getImportedScopeHookNames(sourceFile, entryPath, options.cwd, options.cache),
59
+ ]);
60
+ const providerCases = Object.fromEntries(getGProviderSummariesForFile(sourceFile, entryPath, options.cwd, options.cache));
30
61
  const componentAssignments = [];
31
62
  const scopeAssignments = [];
32
63
  for (const statement of sourceFile.statements) {
@@ -55,6 +86,7 @@ export function analyzeEntry(options) {
55
86
  });
56
87
  }
57
88
  else {
89
+ validateCasesAssignmentOrder(sourceFile, componentExportName, componentAssignments, diagnostics, options.entry);
58
90
  const usedScopeHooks = getGScopeHookCalls(sourceFile, componentExportName, scopeHookNames);
59
91
  if (usedScopeHooks.length > 1) {
60
92
  diagnostics.push({
@@ -64,12 +96,20 @@ export function analyzeEntry(options) {
64
96
  file: options.entry,
65
97
  });
66
98
  }
67
- for (const hookName of getNonGTSXHookCalls(sourceFile, componentExportName, scopeHookNames)) {
99
+ for (const violation of getNonGTSXHookCalls(sourceFile, componentExportName, scopeHookNames, {
100
+ cwd: options.cwd,
101
+ entryPath,
102
+ cache: options.cache,
103
+ sourceFilesByPath: options.cache?.sourceFilesByPath ?? new Map([[entryPath, sourceFile]]),
104
+ visitedComponents: new Set(),
105
+ })) {
68
106
  diagnostics.push({
69
107
  stage: "contract-extraction",
70
108
  code: "non-gtsx-hook",
71
- message: `GTSX components may only call GTSX hooks; found "${hookName}". Wrap production hooks with createGScopeHook(...).`,
72
- file: options.entry,
109
+ message: violation.file === normalizeProjectPath(relative(options.cwd, entryPath))
110
+ ? `GTSX components may only call GTSX hooks; found "${violation.hookName}" in "${violation.componentName}". Wrap production hooks with createGScopeHook(...).`
111
+ : `GTSX components may only call GTSX hooks; found "${violation.hookName}" in dependency "${violation.file}#${violation.componentName}". Wrap production hooks with createGScopeHook(...).`,
112
+ file: violation.file,
73
113
  });
74
114
  }
75
115
  }
@@ -90,6 +130,7 @@ export function analyzeEntry(options) {
90
130
  });
91
131
  }
92
132
  const componentCases = componentAssignments.flatMap((assignment) => assignment.cases);
133
+ const componentStaticCases = componentAssignments.flatMap((assignment) => assignment.staticCases);
93
134
  const mode = componentCases.length === 0
94
135
  ? "unknown"
95
136
  : componentCases.some((testCase) => testCase.kind === "scope")
@@ -98,6 +139,14 @@ export function analyzeEntry(options) {
98
139
  const selectedCases = mode === "scope"
99
140
  ? componentCases.map((testCase) => ({ ...testCase, kind: "scope" }))
100
141
  : componentCases.map((testCase) => ({ ...testCase, kind: "pure" }));
142
+ const importedNames = getImportedNames(sourceFile);
143
+ for (const testCase of selectedCases) {
144
+ for (const providerName of testCase.providers ?? []) {
145
+ if (!providerCases[providerName] && importedNames.has(providerName)) {
146
+ providerCases[providerName] = { name: providerName, cases: [] };
147
+ }
148
+ }
149
+ }
101
150
  if (selectedCases.length === 0) {
102
151
  diagnostics.push({
103
152
  stage: "contract-extraction",
@@ -108,6 +157,31 @@ export function analyzeEntry(options) {
108
157
  }
109
158
  for (const testCase of selectedCases) {
110
159
  validateProviderSelections(testCase, providerCases, diagnostics, options.entry);
160
+ validateProviderVariantSelections(testCase, providerCases, diagnostics, options.entry);
161
+ }
162
+ if (componentExportName) {
163
+ const consumedProviderNames = getGProviderConsumers(sourceFile, componentExportName, {
164
+ cwd: options.cwd,
165
+ entryPath,
166
+ cache: options.cache,
167
+ sourceFilesByPath: options.cache?.sourceFilesByPath ?? new Map([[entryPath, sourceFile]]),
168
+ visitedComponents: new Set(),
169
+ });
170
+ validateProviderVariantCoverage(consumedProviderNames, providerCases, selectedCases, diagnostics, options.entry);
171
+ validateJSXTreeCaseReachability(sourceFile, componentExportName, scopeHookNames, componentStaticCases, diagnostics, options.entry, {
172
+ cwd: options.cwd,
173
+ entryPath,
174
+ cache: options.cache,
175
+ sourceFilesByPath: options.cache?.sourceFilesByPath ?? new Map([[entryPath, sourceFile]]),
176
+ visitedComponents: new Set(),
177
+ });
178
+ validateProviderProjectionWarnings(sourceFile, componentExportName, scopeHookNames, providerCases, diagnostics, options.entry, {
179
+ cwd: options.cwd,
180
+ entryPath,
181
+ cache: options.cache,
182
+ sourceFilesByPath: options.cache?.sourceFilesByPath ?? new Map([[entryPath, sourceFile]]),
183
+ visitedComponents: new Set(),
184
+ });
111
185
  }
112
186
  return {
113
187
  entry: options.entry,
@@ -145,6 +219,13 @@ function getDefaultExportName(sourceFile) {
145
219
  if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression)) {
146
220
  return statement.expression.text;
147
221
  }
222
+ if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
223
+ for (const element of statement.exportClause.elements) {
224
+ if (element.name.text === "default") {
225
+ return element.propertyName?.text ?? element.name.text;
226
+ }
227
+ }
228
+ }
148
229
  }
149
230
  return undefined;
150
231
  }
@@ -155,6 +236,20 @@ function getNamedExportName(sourceFile, exportName) {
155
236
  hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
156
237
  return exportName;
157
238
  }
239
+ if (ts.isVariableStatement(statement) && hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
240
+ for (const declaration of statement.declarationList.declarations) {
241
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === exportName && getFunctionLikeBody(sourceFile, exportName)) {
242
+ return exportName;
243
+ }
244
+ }
245
+ }
246
+ if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
247
+ for (const element of statement.exportClause.elements) {
248
+ if (element.name.text === exportName) {
249
+ return element.propertyName?.text ?? element.name.text;
250
+ }
251
+ }
252
+ }
158
253
  }
159
254
  return undefined;
160
255
  }
@@ -165,6 +260,18 @@ function getFirstNamedExportName(sourceFile) {
165
260
  hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
166
261
  return statement.name.text;
167
262
  }
263
+ if (ts.isVariableStatement(statement) && hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
264
+ for (const declaration of statement.declarationList.declarations) {
265
+ if (ts.isIdentifier(declaration.name) && getFunctionLikeBody(sourceFile, declaration.name.text)) {
266
+ return declaration.name.text;
267
+ }
268
+ }
269
+ }
270
+ if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
271
+ const element = statement.exportClause.elements[0];
272
+ if (element)
273
+ return element.propertyName?.text ?? element.name.text;
274
+ }
168
275
  }
169
276
  return undefined;
170
277
  }
@@ -176,222 +283,2229 @@ function getScopeHookNames(sourceFile) {
176
283
  for (const declaration of statement.declarationList.declarations) {
177
284
  if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
178
285
  continue;
179
- if (isCreateGScopeCall(declaration.initializer)) {
286
+ if (isCreateGScopeCall(unwrapExpression(declaration.initializer))) {
180
287
  names.add(declaration.name.text);
181
288
  }
182
289
  }
183
290
  }
184
291
  return names;
185
292
  }
186
- function getGProviderNames(sourceFile) {
187
- const names = new Set();
293
+ function validateCasesAssignmentOrder(sourceFile, componentName, assignments, diagnostics, file) {
294
+ const declarationStart = topLevelValueDeclarationStart(sourceFile, componentName);
295
+ if (declarationStart === undefined)
296
+ return;
297
+ for (const assignment of assignments) {
298
+ if (assignment.targetName !== componentName || assignment.statementStart >= declarationStart)
299
+ continue;
300
+ diagnostics.push({
301
+ stage: "contract-extraction",
302
+ code: "cases-before-component-export",
303
+ message: `Move ${componentName}.cases after the "${componentName}" component declaration. GTSX component boundaries are initialized at runtime, so cases cannot rely on function hoisting.`,
304
+ file,
305
+ });
306
+ }
307
+ }
308
+ function topLevelValueDeclarationStart(sourceFile, name) {
188
309
  for (const statement of sourceFile.statements) {
310
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name?.text === name) {
311
+ return statement.getStart(sourceFile);
312
+ }
189
313
  if (!ts.isVariableStatement(statement))
190
314
  continue;
191
315
  for (const declaration of statement.declarationList.declarations) {
192
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
193
- continue;
194
- if (isCreateGProviderCall(unwrapExpression(declaration.initializer))) {
195
- names.add(declaration.name.text);
316
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === name) {
317
+ return statement.getStart(sourceFile);
196
318
  }
197
319
  }
198
320
  }
199
- return names;
321
+ return undefined;
200
322
  }
201
- function validateProviderSelections(testCase, providerCases, diagnostics, file) {
202
- if (!testCase.providers)
203
- return;
204
- for (const providerName of testCase.providers) {
205
- if (!providerCases[providerName]) {
206
- diagnostics.push({
207
- stage: "contract-extraction",
208
- code: "missing-provider",
209
- message: `Case "${testCase.name}" selects unknown provider "${providerName}".`,
210
- file,
211
- caseName: testCase.name,
212
- });
323
+ function getScopeHookProviderNamesForFile(sourceFile, filePath, cache) {
324
+ const cached = cache?.scopeHookProviderNamesByPath.get(filePath);
325
+ if (cached)
326
+ return cached;
327
+ const hookProviders = new Map();
328
+ for (const statement of sourceFile.statements) {
329
+ if (!ts.isVariableStatement(statement))
330
+ continue;
331
+ for (const declaration of statement.declarationList.declarations) {
332
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
333
+ continue;
334
+ const initializer = unwrapExpression(declaration.initializer);
335
+ if (!isCreateGScopeCall(initializer))
336
+ continue;
337
+ const providersExpression = initializer.arguments[1];
338
+ if (!providersExpression)
339
+ continue;
340
+ const providerNames = readProviderNameList(providersExpression, sourceFile);
341
+ if (providerNames.length > 0)
342
+ hookProviders.set(declaration.name.text, providerNames);
213
343
  }
214
344
  }
345
+ cache?.scopeHookProviderNamesByPath.set(filePath, hookProviders);
346
+ return hookProviders;
215
347
  }
216
- function getNonGTSXHookCalls(sourceFile, componentName, scopeHookNames) {
217
- const component = getFunctionDeclaration(sourceFile, componentName);
218
- if (!component?.body)
219
- return [];
220
- const helperFunctions = getTopLevelFunctionDeclarations(sourceFile);
221
- const visitedHelpers = new Set([componentName]);
222
- const hookNames = new Set();
223
- visit(component.body);
224
- return [...hookNames];
225
- function visit(node) {
226
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
227
- const hookName = node.expression.text;
228
- if (isHookName(hookName) && hookName !== "useGContext" && !scopeHookNames.has(hookName)) {
229
- hookNames.add(hookName);
230
- }
231
- else if (!isHookName(hookName)) {
232
- visitHelper(hookName);
233
- }
234
- }
235
- ts.forEachChild(node, visit);
348
+ function readProviderNameList(expression, sourceFile, visited = new Set()) {
349
+ const value = unwrapExpression(expression);
350
+ if (ts.isArrayLiteralExpression(value)) {
351
+ return value.elements.flatMap((element) => {
352
+ const provider = unwrapExpression(element);
353
+ return ts.isIdentifier(provider) ? [provider.text] : [];
354
+ });
236
355
  }
237
- function visitHelper(functionName) {
238
- if (visitedHelpers.has(functionName))
239
- return;
240
- const helper = helperFunctions.get(functionName);
241
- if (!helper?.body)
242
- return;
243
- visitedHelpers.add(functionName);
244
- visit(helper.body);
356
+ if (ts.isIdentifier(value)) {
357
+ if (visited.has(value.text))
358
+ return [];
359
+ visited.add(value.text);
360
+ const initializer = topLevelVariableInitializer(sourceFile, value.text);
361
+ return initializer ? readProviderNameList(initializer, sourceFile, visited) : [];
245
362
  }
363
+ return [];
246
364
  }
247
- function getGScopeHookCalls(sourceFile, componentName, scopeHookNames) {
248
- const component = getFunctionDeclaration(sourceFile, componentName);
249
- if (!component?.body)
250
- return [];
251
- const helperFunctions = getTopLevelFunctionDeclarations(sourceFile);
252
- const visitedHelpers = new Set([componentName]);
253
- const hookNames = new Set();
254
- visit(component.body);
255
- return [...hookNames];
256
- function visit(node) {
257
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
258
- if (scopeHookNames.has(node.expression.text)) {
259
- hookNames.add(node.expression.text);
260
- }
261
- else if (!isHookName(node.expression.text)) {
262
- visitHelper(node.expression.text);
263
- }
365
+ function topLevelVariableInitializer(sourceFile, name) {
366
+ for (const statement of sourceFile.statements) {
367
+ if (!ts.isVariableStatement(statement))
368
+ continue;
369
+ for (const declaration of statement.declarationList.declarations) {
370
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === name)
371
+ return declaration.initializer;
264
372
  }
265
- ts.forEachChild(node, visit);
266
- }
267
- function visitHelper(functionName) {
268
- if (visitedHelpers.has(functionName))
269
- return;
270
- const helper = helperFunctions.get(functionName);
271
- if (!helper?.body)
272
- return;
273
- visitedHelpers.add(functionName);
274
- visit(helper.body);
275
373
  }
374
+ return undefined;
276
375
  }
277
- function getFunctionDeclaration(sourceFile, functionName) {
278
- return sourceFile.statements.find((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === functionName);
279
- }
280
- function getTopLevelFunctionDeclarations(sourceFile) {
281
- const functions = new Map();
376
+ function getGProviderSummaries(sourceFile) {
377
+ const providers = new Map();
282
378
  for (const statement of sourceFile.statements) {
283
- if (ts.isFunctionDeclaration(statement) && statement.name) {
284
- functions.set(statement.name.text, statement);
379
+ if (!ts.isVariableStatement(statement))
380
+ continue;
381
+ for (const declaration of statement.declarationList.declarations) {
382
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
383
+ continue;
384
+ const initializer = unwrapExpression(declaration.initializer);
385
+ if (!isCreateGProviderCall(initializer))
386
+ continue;
387
+ const variants = readCreateGProviderVariants(initializer);
388
+ providers.set(declaration.name.text, {
389
+ name: declaration.name.text,
390
+ cases: [],
391
+ ...(variants && variants.length > 0 ? { variants } : {}),
392
+ });
285
393
  }
286
394
  }
287
- return functions;
395
+ return providers;
288
396
  }
289
- function isHookName(name) {
290
- return /^use[A-Z0-9_]/.test(name);
397
+ function getGProviderSummariesForFile(sourceFile, filePath, cwd, cache) {
398
+ const cached = cache?.providerSummariesByPath.get(filePath);
399
+ if (cached)
400
+ return cached;
401
+ const providers = new Map(getGProviderSummaries(sourceFile));
402
+ cache?.providerSummariesByPath.set(filePath, providers);
403
+ for (const statement of sourceFile.statements) {
404
+ if (!ts.isImportDeclaration(statement) || !statement.importClause || !ts.isStringLiteral(statement.moduleSpecifier))
405
+ continue;
406
+ const targetPath = resolveImportedGTSXPath(filePath, cwd, statement.moduleSpecifier.text, cache);
407
+ if (!targetPath)
408
+ continue;
409
+ const targetSource = sourceFileForAbsolutePath(targetPath, cache);
410
+ if (!targetSource)
411
+ continue;
412
+ const targetProviders = getGProviderSummariesForFile(targetSource, targetPath, cwd, cache);
413
+ const importClause = statement.importClause;
414
+ if (importClause.name) {
415
+ const importedDefault = targetProviders.get("default");
416
+ if (importedDefault) {
417
+ providers.set(importClause.name.text, { ...importedDefault, name: importClause.name.text });
418
+ }
419
+ }
420
+ const namedBindings = importClause.namedBindings;
421
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
422
+ continue;
423
+ for (const element of namedBindings.elements) {
424
+ const importedName = element.propertyName?.text ?? element.name.text;
425
+ const importedProvider = targetProviders.get(importedName);
426
+ if (importedProvider) {
427
+ providers.set(element.name.text, { ...importedProvider, name: element.name.text });
428
+ }
429
+ }
430
+ }
431
+ return providers;
291
432
  }
292
- function getCasesAssignment(statement, sourceFile, diagnostics) {
293
- if (!ts.isExpressionStatement(statement))
433
+ function readCreateGProviderVariants(expression) {
434
+ const options = expression.arguments[1] ? unwrapExpression(expression.arguments[1]) : undefined;
435
+ if (!options || !ts.isObjectLiteralExpression(options))
294
436
  return undefined;
295
- const expression = statement.expression;
296
- if (!ts.isBinaryExpression(expression) || expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
297
- return undefined;
298
- }
299
- if (!ts.isPropertyAccessExpression(expression.left) || expression.left.name.text !== "cases") {
437
+ const variantsProperty = options.properties.find((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === "variants");
438
+ if (!variantsProperty)
300
439
  return undefined;
301
- }
302
- if (!ts.isIdentifier(expression.left.expression))
440
+ const variantsValue = unwrapExpression(variantsProperty.initializer);
441
+ if (!ts.isArrayLiteralExpression(variantsValue))
303
442
  return undefined;
304
- const casesExpression = unwrapExpression(expression.right);
305
- if (!ts.isObjectLiteralExpression(casesExpression)) {
306
- diagnostics.push({
307
- stage: "contract-extraction",
308
- code: "malformed-cases",
309
- message: "GTSX cases must be a statically enumerable object literal.",
310
- file: sourceFile.fileName,
311
- });
312
- return { targetName: expression.left.expression.text, cases: [] };
443
+ return variantsValue.elements.flatMap((element) => {
444
+ const variant = unwrapExpression(element);
445
+ return ts.isStringLiteral(variant) ? [variant.text] : [];
446
+ });
447
+ }
448
+ function getImportedScopeHookNames(sourceFile, entryPath, cwd, cache) {
449
+ const cached = cache?.importedScopeHookNamesByPath.get(entryPath);
450
+ if (cached)
451
+ return cached;
452
+ const names = new Set();
453
+ for (const statement of sourceFile.statements) {
454
+ if (!ts.isImportDeclaration(statement))
455
+ continue;
456
+ const clause = statement.importClause;
457
+ if (!clause || !ts.isStringLiteral(statement.moduleSpecifier))
458
+ continue;
459
+ const targetPath = resolveImportedGTSXPath(entryPath, cwd, statement.moduleSpecifier.text, cache);
460
+ if (!targetPath)
461
+ continue;
462
+ const targetSource = sourceFileForAbsolutePath(targetPath, cache);
463
+ if (!targetSource)
464
+ continue;
465
+ const exportedScopeHookNames = getExportedScopeHookNames(targetSource);
466
+ if (clause.name && exportedScopeHookNames.has("default") && isHookName(clause.name.text)) {
467
+ names.add(clause.name.text);
468
+ }
469
+ const namedBindings = clause.namedBindings;
470
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
471
+ continue;
472
+ for (const element of namedBindings.elements) {
473
+ const importedName = element.propertyName?.text ?? element.name.text;
474
+ if (exportedScopeHookNames.has(importedName) && isHookName(element.name.text)) {
475
+ names.add(element.name.text);
476
+ }
477
+ }
313
478
  }
314
- return {
315
- targetName: expression.left.expression.text,
316
- cases: readCasesObject(casesExpression, sourceFile, diagnostics),
317
- };
479
+ cache?.importedScopeHookNamesByPath.set(entryPath, names);
480
+ return names;
318
481
  }
319
- function readCasesObject(objectLiteral, sourceFile, diagnostics) {
320
- const cases = [];
321
- for (const property of objectLiteral.properties) {
322
- if (ts.isSpreadAssignment(property)) {
323
- diagnostics.push({
324
- stage: "contract-extraction",
325
- code: "malformed-cases",
326
- message: "GTSX cases do not support spread composition in the first implementation.",
327
- file: sourceFile.fileName,
328
- });
482
+ function getExportedScopeHookNames(sourceFile) {
483
+ const localScopeHookNames = getScopeHookNames(sourceFile);
484
+ const exportedScopeHookNames = new Set();
485
+ for (const statement of sourceFile.statements) {
486
+ if (ts.isVariableStatement(statement) && hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
487
+ for (const declaration of statement.declarationList.declarations) {
488
+ if (ts.isIdentifier(declaration.name) && localScopeHookNames.has(declaration.name.text)) {
489
+ exportedScopeHookNames.add(declaration.name.text);
490
+ }
491
+ }
329
492
  continue;
330
493
  }
331
- if (!ts.isPropertyAssignment(property))
494
+ if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && localScopeHookNames.has(statement.expression.text)) {
495
+ exportedScopeHookNames.add("default");
332
496
  continue;
333
- const caseName = getStaticPropertyName(property.name);
334
- if (!caseName) {
335
- diagnostics.push({
336
- stage: "contract-extraction",
337
- code: "non-static-case-key",
338
- message: "GTSX case keys must be statically enumerable object literal keys.",
339
- file: sourceFile.fileName,
340
- });
497
+ }
498
+ if (!ts.isExportDeclaration(statement) || statement.moduleSpecifier || !statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
341
499
  continue;
342
500
  }
343
- const caseValue = unwrapExpression(property.initializer);
344
- const providers = ts.isObjectLiteralExpression(caseValue) ? readProviderSelections(caseValue) : undefined;
345
- const kind = ts.isObjectLiteralExpression(caseValue) && hasStaticProperty(caseValue, "scope") ? "scope" : "pure";
346
- cases.push({
347
- kind,
348
- name: caseName,
349
- ...(providers && Object.keys(providers).length > 0 ? { providers } : {}),
350
- });
501
+ for (const element of statement.exportClause.elements) {
502
+ const localName = element.propertyName?.text ?? element.name.text;
503
+ if (localScopeHookNames.has(localName)) {
504
+ exportedScopeHookNames.add(element.name.text);
505
+ }
506
+ }
351
507
  }
352
- return cases;
508
+ return exportedScopeHookNames;
353
509
  }
354
- function readProviderSelections(caseValue) {
355
- const providersProperty = caseValue.properties.find((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === "providers");
356
- if (!providersProperty)
357
- return undefined;
358
- const providersValue = unwrapExpression(providersProperty.initializer);
359
- if (!ts.isArrayLiteralExpression(providersValue))
510
+ function resolveImportedGTSXPath(entryPath, cwd, specifier, cache) {
511
+ const cacheKey = `${entryPath}\0${specifier}`;
512
+ const cached = cache?.importedGTSXPathByKey.get(cacheKey);
513
+ if (cached !== undefined)
514
+ return cached ?? undefined;
515
+ const basePath = resolveImportBasePath(entryPath, cwd, specifier);
516
+ if (!basePath) {
517
+ cache?.importedGTSXPathByKey.set(cacheKey, null);
360
518
  return undefined;
361
- const providers = [];
362
- for (const element of providersValue.elements) {
363
- const entry = unwrapExpression(element);
364
- if (!ts.isArrayLiteralExpression(entry))
365
- continue;
366
- const providerExpression = entry.elements[0] ? unwrapExpression(entry.elements[0]) : undefined;
367
- if (providerExpression && ts.isIdentifier(providerExpression)) {
368
- providers.push(providerExpression.text);
369
- }
370
519
  }
371
- return providers;
520
+ const visited = new Set();
521
+ const resolved = resolveImportedGTSXPathFromBase(basePath, cwd, visited, cache);
522
+ cache?.importedGTSXPathByKey.set(cacheKey, resolved ?? null);
523
+ return resolved;
372
524
  }
373
- function getStaticPropertyName(name) {
374
- if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
375
- return name.text;
525
+ function resolveImportedGTSXPathFromBase(basePath, cwd, visited, cache) {
526
+ for (const candidate of importedGTSXPathCandidates(basePath)) {
527
+ if (isFile(candidate))
528
+ return candidate;
529
+ }
530
+ for (const barrelCandidate of importedTSXBarrelCandidates(basePath)) {
531
+ if (!isFile(barrelCandidate) || visited.has(barrelCandidate))
532
+ continue;
533
+ visited.add(barrelCandidate);
534
+ const barrelSource = sourceFileForAbsolutePath(barrelCandidate, cache);
535
+ if (!barrelSource)
536
+ continue;
537
+ for (const statement of barrelSource.statements) {
538
+ if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier))
539
+ continue;
540
+ const nextBasePath = resolveImportBasePath(barrelCandidate, cwd, statement.moduleSpecifier.text);
541
+ const resolvedPath = nextBasePath ? resolveImportedGTSXPathFromBase(nextBasePath, cwd, visited, cache) : undefined;
542
+ if (resolvedPath)
543
+ return resolvedPath;
544
+ }
376
545
  }
377
546
  return undefined;
378
547
  }
379
- function unwrapExpression(expression) {
380
- if (ts.isSatisfiesExpression(expression) || ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression)) {
381
- return unwrapExpression(expression.expression);
548
+ function resolveImportBasePath(entryPath, cwd, specifier) {
549
+ if (specifier.startsWith("@/")) {
550
+ const direct = resolve(cwd, specifier.slice(2));
551
+ if (importedGTSXPathCandidates(direct).some((candidate) => isFile(candidate)) ||
552
+ importedTSXBarrelCandidates(direct).some((candidate) => isFile(candidate))) {
553
+ return direct;
554
+ }
555
+ return resolve(cwd, "src", specifier.slice(2));
382
556
  }
383
- return expression;
557
+ if (specifier.startsWith("."))
558
+ return resolve(dirname(entryPath), specifier);
559
+ return undefined;
384
560
  }
385
- function isCreateGScopeCall(expression) {
386
- return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "createGScopeHook";
561
+ function importedGTSXPathCandidates(basePath) {
562
+ const extensionCandidate = /\.(?:tsx|ts|jsx|js)$/.test(basePath) ? basePath.replace(/\.(?:tsx|ts|jsx|js)$/, ".g.tsx") : undefined;
563
+ const extensionTypeCandidate = /\.(?:tsx|ts|jsx|js)$/.test(basePath) ? basePath.replace(/\.(?:tsx|ts|jsx|js)$/, ".g.ts") : undefined;
564
+ const candidates = [
565
+ basePath.endsWith(".g.tsx") ? basePath : undefined,
566
+ basePath.endsWith(".g.ts") ? basePath : undefined,
567
+ basePath.endsWith(".g") ? `${basePath}.tsx` : undefined,
568
+ basePath.endsWith(".g") ? `${basePath}.ts` : undefined,
569
+ `${basePath}.g.tsx`,
570
+ `${basePath}.g.ts`,
571
+ extensionCandidate,
572
+ extensionTypeCandidate,
573
+ join(basePath, "index.g.tsx"),
574
+ join(basePath, "index.g.ts"),
575
+ ];
576
+ return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
387
577
  }
388
- function isCreateGProviderCall(expression) {
389
- return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "createGProvider";
578
+ function isFile(path) {
579
+ try {
580
+ return statSync(path).isFile();
581
+ }
582
+ catch {
583
+ return false;
584
+ }
390
585
  }
391
- function hasStaticProperty(objectLiteral, propertyName) {
392
- return objectLiteral.properties.some((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === propertyName);
586
+ function importedTSXBarrelCandidates(basePath) {
587
+ const candidates = [
588
+ basePath.endsWith(".tsx") ? basePath : undefined,
589
+ `${basePath}.tsx`,
590
+ join(basePath, "index.tsx"),
591
+ ];
592
+ return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
593
+ }
594
+ function getImportedNames(sourceFile) {
595
+ const names = new Set();
596
+ for (const statement of sourceFile.statements) {
597
+ if (!ts.isImportDeclaration(statement))
598
+ continue;
599
+ const clause = statement.importClause;
600
+ if (!clause)
601
+ continue;
602
+ if (clause.name) {
603
+ names.add(clause.name.text);
604
+ }
605
+ const namedBindings = clause.namedBindings;
606
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
607
+ continue;
608
+ for (const element of namedBindings.elements) {
609
+ names.add(element.name.text);
610
+ }
611
+ }
612
+ return names;
613
+ }
614
+ function validateProviderSelections(testCase, providerCases, diagnostics, file) {
615
+ if (!testCase.providers)
616
+ return;
617
+ for (const providerName of testCase.providers) {
618
+ if (!providerCases[providerName]) {
619
+ diagnostics.push({
620
+ stage: "contract-extraction",
621
+ code: "missing-provider",
622
+ message: `Case "${testCase.name}" selects unknown provider "${providerName}".`,
623
+ file,
624
+ caseName: testCase.name,
625
+ });
626
+ }
627
+ }
628
+ }
629
+ function validateProviderVariantSelections(testCase, providerCases, diagnostics, file) {
630
+ if (!testCase.providerVariants)
631
+ return;
632
+ for (const [providerName, selection] of Object.entries(testCase.providerVariants)) {
633
+ const variants = providerVariantSelectionValues(selection);
634
+ const provider = providerCases[providerName];
635
+ if (!provider) {
636
+ diagnostics.push({
637
+ stage: "contract-extraction",
638
+ code: "missing-provider",
639
+ message: `Case "${testCase.name}" marks unknown provider "${providerName}" variants "${variants.join(", ")}".`,
640
+ file,
641
+ caseName: testCase.name,
642
+ });
643
+ continue;
644
+ }
645
+ if (!provider.variants || provider.variants.length === 0) {
646
+ diagnostics.push({
647
+ stage: "contract-extraction",
648
+ code: "missing-provider-variants",
649
+ message: `Case "${testCase.name}" marks provider "${providerName}" variants "${variants.join(", ")}", but "${providerName}" does not declare variants.`,
650
+ file,
651
+ caseName: testCase.name,
652
+ });
653
+ continue;
654
+ }
655
+ const unknownVariants = variants.filter((variant) => !provider.variants?.includes(variant));
656
+ if (unknownVariants.length > 0) {
657
+ diagnostics.push({
658
+ stage: "contract-extraction",
659
+ code: "unknown-provider-variant",
660
+ message: `Case "${testCase.name}" marks unknown "${providerName}" variants "${unknownVariants.join(", ")}". Expected one of: ${provider.variants.join(", ")}.`,
661
+ file,
662
+ caseName: testCase.name,
663
+ });
664
+ }
665
+ }
666
+ }
667
+ function validateProviderVariantCoverage(consumedProviderNames, providerCases, selectedCases, diagnostics, file) {
668
+ for (const providerName of consumedProviderNames) {
669
+ const provider = providerCases[providerName];
670
+ if (!provider?.variants || provider.variants.length === 0)
671
+ continue;
672
+ const coveredVariants = new Set(selectedCases.flatMap((testCase) => {
673
+ const selection = testCase.providerVariants?.[providerName];
674
+ return selection ? providerVariantSelectionValues(selection) : [];
675
+ }));
676
+ const missingVariants = provider.variants.filter((variant) => !coveredVariants.has(variant));
677
+ if (missingVariants.length === 0)
678
+ continue;
679
+ diagnostics.push({
680
+ stage: "contract-extraction",
681
+ code: "missing-provider-variant-cases",
682
+ message: `GTSX entry consumes provider "${providerName}" but its cases do not cover variants: ${missingVariants.join(", ")}.`,
683
+ file,
684
+ });
685
+ }
686
+ }
687
+ function providerVariantSelectionValues(selection) {
688
+ return Array.isArray(selection) ? selection : [selection];
689
+ }
690
+ function validateJSXTreeCaseReachability(sourceFile, componentName, scopeHookNames, staticCases, diagnostics, file, context) {
691
+ if (staticCases.length === 0)
692
+ return;
693
+ const component = getFunctionLikeDeclaration(sourceFile, componentName);
694
+ if (!component?.body)
695
+ return;
696
+ const branchContext = createJSXBranchAnalysisContext(sourceFile, component, scopeHookNames);
697
+ const defaultValues = defaultStaticValuesForFunctionLike(component);
698
+ const caseFacts = staticCases.map((testCase) => ({
699
+ ...testCase,
700
+ values: new Map([...defaultValues, ...testCase.values]),
701
+ }));
702
+ const dependencies = reachableJSXDependenciesForComponent(sourceFile, componentName, branchContext, context);
703
+ const reported = new Set();
704
+ for (const dependency of dependencies) {
705
+ const opaque = firstOpaqueJSXBranchPredicate(dependency.condition);
706
+ if (opaque) {
707
+ const key = `opaque:${dependency.tagName}:${opaque.text}`;
708
+ if (reported.has(key))
709
+ continue;
710
+ reported.add(key);
711
+ diagnostics.push({
712
+ stage: "contract-extraction",
713
+ code: "opaque-jsx-control-flow",
714
+ message: `JSX dependency <${dependency.tagName}> is controlled by an opaque expression "${opaque.text}". Use props, GTSX context, or GScope values directly so cases can cover the tree structure.`,
715
+ file,
716
+ });
717
+ continue;
718
+ }
719
+ const evaluations = caseFacts.map((testCase) => evaluateJSXBranchPredicate(dependency.condition, testCase.values));
720
+ const coveredCaseNames = caseFacts
721
+ .filter((_testCase, index) => evaluations[index] === true)
722
+ .map((testCase) => testCase.name);
723
+ if (coveredCaseNames.length > 0)
724
+ continue;
725
+ const unknownCaseNames = caseFacts
726
+ .filter((_testCase, index) => evaluations[index] === "unknown")
727
+ .map((testCase) => testCase.name);
728
+ if (unknownCaseNames.length > 0) {
729
+ const key = `unknown:${dependency.tagName}:${formatJSXBranchPredicate(dependency.condition)}`;
730
+ if (reported.has(key))
731
+ continue;
732
+ reported.add(key);
733
+ diagnostics.push({
734
+ stage: "contract-extraction",
735
+ code: "unknown-jsx-branch-coverage",
736
+ message: `JSX dependency <${dependency.tagName}> is controlled by "${formatJSXBranchPredicate(dependency.condition)}", but case values are not static enough to prove coverage. Unknown cases: ${unknownCaseNames.join(", ")}.`,
737
+ file,
738
+ });
739
+ continue;
740
+ }
741
+ const key = `uncovered:${dependency.tagName}:${formatJSXBranchPredicate(dependency.condition)}`;
742
+ if (reported.has(key))
743
+ continue;
744
+ reported.add(key);
745
+ diagnostics.push({
746
+ stage: "contract-extraction",
747
+ code: "uncovered-jsx-branch",
748
+ message: `No case renders JSX dependency <${dependency.tagName}> behind "${formatJSXBranchPredicate(dependency.condition)}". Add a case whose props, GTSX context, or GScope values make that branch reachable.`,
749
+ file,
750
+ });
751
+ }
752
+ }
753
+ function validateProviderProjectionWarnings(sourceFile, componentName, scopeHookNames, providerCases, diagnostics, file, context) {
754
+ const warnings = providerProjectionWarningsForComponent(sourceFile, componentName, scopeHookNames, providerCases, context);
755
+ const reported = new Set();
756
+ for (const warning of warnings) {
757
+ const key = `${warning.target.filePath}#${warning.target.componentName}:${warning.providerName}`;
758
+ if (reported.has(key))
759
+ continue;
760
+ reported.add(key);
761
+ diagnostics.push({
762
+ stage: "contract-extraction",
763
+ severity: "warning",
764
+ code: "unmarked-provider-variant-projection",
765
+ message: `JSX dependency <${warning.tagName}> receives props derived from provider "${warning.providerName}", but its cases do not mark "${warning.providerName}" variants. If those props are environment projections, add GProviderCase markers; if the child is environment-neutral, this warning can be ignored.`,
766
+ file,
767
+ });
768
+ }
769
+ }
770
+ function providerProjectionWarningsForComponent(sourceFile, componentName, scopeHookNames, providerCases, context) {
771
+ const component = getFunctionLikeDeclaration(sourceFile, componentName);
772
+ if (!component?.body)
773
+ return [];
774
+ const warnings = [];
775
+ const branchContext = createJSXBranchAnalysisContext(sourceFile, component, scopeHookNames);
776
+ const helperFunctions = getTopLevelFunctionLikeBodiesForPath(sourceFile, context.entryPath, context.cache);
777
+ const importBindings = componentDependencyBindingsForFile(sourceFile, context.entryPath, context);
778
+ const visitedHelpers = new Set([componentName]);
779
+ visitFunctionLikeBody(component, branchContext, visitedHelpers);
780
+ return warnings;
781
+ function visitFunctionLikeBody(functionLike, currentBranchContext, currentVisitedHelpers) {
782
+ if (!functionLike.body)
783
+ return;
784
+ const localAliases = localComponentAliasBindingsForBody(functionLike.body);
785
+ visit(functionLike.body, currentBranchContext, currentVisitedHelpers, localAliases);
786
+ }
787
+ function visit(node, currentBranchContext, currentVisitedHelpers, localAliases) {
788
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && !isHookName(node.expression.text)) {
789
+ const helper = getFunctionLikeDeclaration(sourceFile, node.expression.text);
790
+ if (helper?.body && expressionContainsJSX(helper.body) && !currentVisitedHelpers.has(node.expression.text)) {
791
+ const helperVisited = new Set(currentVisitedHelpers);
792
+ helperVisited.add(node.expression.text);
793
+ const helperContext = bindFunctionCallArguments(helper, node.arguments, currentBranchContext);
794
+ visitFunctionLikeBody(helper, helperContext, helperVisited);
795
+ return;
796
+ }
797
+ }
798
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
799
+ visitJSXOpeningLike(node, currentBranchContext, localAliases);
800
+ }
801
+ ts.forEachChild(node, (child) => visit(child, currentBranchContext, currentVisitedHelpers, localAliases));
802
+ }
803
+ function visitJSXOpeningLike(node, currentBranchContext, localAliases) {
804
+ const target = componentDependencyTargetForJsxTag(node.tagName, {
805
+ filePath: context.entryPath,
806
+ importBindings,
807
+ localAliases,
808
+ localComponentNames: helperFunctions,
809
+ });
810
+ if (!target)
811
+ return;
812
+ const projectedProviderNames = providerVariantNamesReferencedByJsxAttributes(node.attributes, providerCases, currentBranchContext);
813
+ if (projectedProviderNames.size === 0)
814
+ return;
815
+ const targetSourceFile = sourceFileForPath(target.filePath, context);
816
+ if (!targetSourceFile)
817
+ return;
818
+ for (const providerName of projectedProviderNames) {
819
+ const markerStatus = componentCasesProviderVariantMarkerStatus(targetSourceFile, target.componentName, providerName);
820
+ if (markerStatus !== false)
821
+ continue;
822
+ warnings.push({
823
+ providerName,
824
+ tagName: jsxTagNameText(node.tagName, sourceFile),
825
+ target,
826
+ });
827
+ }
828
+ }
829
+ }
830
+ function providerVariantNamesReferencedByJsxAttributes(attributes, providerCases, context) {
831
+ const providerNames = new Set();
832
+ for (const property of attributes.properties) {
833
+ const expression = ts.isJsxAttribute(property) && property.initializer
834
+ ? ts.isJsxExpression(property.initializer)
835
+ ? property.initializer.expression
836
+ : property.initializer
837
+ : ts.isJsxSpreadAttribute(property)
838
+ ? property.expression
839
+ : undefined;
840
+ if (!expression)
841
+ continue;
842
+ for (const providerName of providerVariantNamesReferencedByExpression(expression, providerCases, context)) {
843
+ providerNames.add(providerName);
844
+ }
845
+ }
846
+ return providerNames;
847
+ }
848
+ function providerVariantNamesReferencedByExpression(expression, providerCases, context) {
849
+ const providerNames = new Set();
850
+ visit(expression);
851
+ return providerNames;
852
+ function visit(node) {
853
+ if (ts.isExpression(node)) {
854
+ const reference = factorReferenceForExpression(node, context);
855
+ if (reference?.root === "context" && (providerCases[reference.providerName]?.variants?.length ?? 0) > 0) {
856
+ providerNames.add(reference.providerName);
857
+ }
858
+ }
859
+ ts.forEachChild(node, visit);
860
+ }
861
+ }
862
+ function componentCasesProviderVariantMarkerStatus(sourceFile, componentName, providerName) {
863
+ let hasCases = false;
864
+ for (const statement of sourceFile.statements) {
865
+ const assignment = getCasesAssignment(statement, sourceFile, []);
866
+ if (!assignment || assignment.targetName !== componentName)
867
+ continue;
868
+ if (assignment.cases.length === 0)
869
+ continue;
870
+ hasCases = true;
871
+ if (assignment.cases.some((testCase) => testCase.providerVariants?.[providerName]))
872
+ return true;
873
+ }
874
+ return hasCases ? false : undefined;
875
+ }
876
+ function createJSXBranchAnalysisContext(sourceFile, component, scopeHookNames) {
877
+ const context = {
878
+ expressionAliases: new Map(),
879
+ factorBindings: new Map(),
880
+ sourceFile,
881
+ };
882
+ bindFunctionParameters(component, context);
883
+ if (component.body && ts.isBlock(component.body)) {
884
+ for (const statement of component.body.statements) {
885
+ bindTopLevelBranchVariableStatement(statement, context, scopeHookNames);
886
+ }
887
+ }
888
+ return context;
889
+ }
890
+ function bindFunctionParameters(component, context) {
891
+ const propsParameter = component.parameters[0];
892
+ if (!propsParameter)
893
+ return;
894
+ if (ts.isIdentifier(propsParameter.name)) {
895
+ context.factorBindings.set(propsParameter.name.text, { root: "props", path: [] });
896
+ return;
897
+ }
898
+ if (ts.isObjectBindingPattern(propsParameter.name)) {
899
+ bindObjectBindingPattern(propsParameter.name, { root: "props", path: [] }, context);
900
+ }
901
+ }
902
+ function bindTopLevelBranchVariableStatement(statement, context, scopeHookNames) {
903
+ if (!ts.isVariableStatement(statement))
904
+ return;
905
+ for (const declaration of statement.declarationList.declarations) {
906
+ if (!declaration.initializer)
907
+ continue;
908
+ const initializer = unwrapExpression(declaration.initializer);
909
+ const scopeProvider = scopeHookProviderFromCall(initializer, scopeHookNames);
910
+ const contextProvider = ts.isCallExpression(initializer) ? gContextProviderNameFromCall(initializer) : undefined;
911
+ const rootReference = scopeProvider
912
+ ? { root: "scope", path: [] }
913
+ : contextProvider
914
+ ? { root: "context", providerName: contextProvider, path: [] }
915
+ : factorReferenceForExpression(initializer, context);
916
+ if (rootReference) {
917
+ if (ts.isIdentifier(declaration.name)) {
918
+ context.factorBindings.set(declaration.name.text, rootReference);
919
+ }
920
+ else if (ts.isObjectBindingPattern(declaration.name)) {
921
+ bindObjectBindingPattern(declaration.name, rootReference, context);
922
+ }
923
+ continue;
924
+ }
925
+ if (ts.isIdentifier(declaration.name) && !expressionContainsJSX(initializer)) {
926
+ context.expressionAliases.set(declaration.name.text, initializer);
927
+ }
928
+ }
929
+ }
930
+ function bindObjectBindingPattern(pattern, rootReference, context) {
931
+ for (const element of pattern.elements) {
932
+ if (element.dotDotDotToken)
933
+ continue;
934
+ const propertyName = element.propertyName ? bindingNameText(element.propertyName) : bindingNameText(element.name);
935
+ if (!propertyName)
936
+ continue;
937
+ const nextReference = appendFactorReferencePath(rootReference, propertyName);
938
+ if (ts.isIdentifier(element.name)) {
939
+ context.factorBindings.set(element.name.text, nextReference);
940
+ }
941
+ else if (ts.isObjectBindingPattern(element.name)) {
942
+ bindObjectBindingPattern(element.name, nextReference, context);
943
+ }
944
+ }
945
+ }
946
+ function bindingNameText(name) {
947
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
948
+ return name.text;
949
+ return undefined;
950
+ }
951
+ function cloneJSXBranchAnalysisContext(context) {
952
+ return {
953
+ expressionAliases: new Map(context.expressionAliases),
954
+ factorBindings: new Map(context.factorBindings),
955
+ sourceFile: context.sourceFile,
956
+ };
957
+ }
958
+ function bindCallbackItemParameter(callback, sourceReference, context) {
959
+ const callbackContext = cloneJSXBranchAnalysisContext(context);
960
+ const itemReference = appendFactorReferencePath(sourceReference, "number");
961
+ const parameter = callback.parameters[0];
962
+ if (!parameter)
963
+ return callbackContext;
964
+ if (ts.isIdentifier(parameter.name)) {
965
+ callbackContext.factorBindings.set(parameter.name.text, itemReference);
966
+ }
967
+ else if (ts.isObjectBindingPattern(parameter.name)) {
968
+ bindObjectBindingPattern(parameter.name, itemReference, callbackContext);
969
+ }
970
+ return callbackContext;
971
+ }
972
+ function bindFunctionCallArguments(functionLike, args, context) {
973
+ const callContext = cloneJSXBranchAnalysisContext(context);
974
+ for (let index = 0; index < functionLike.parameters.length; index += 1) {
975
+ const parameter = functionLike.parameters[index];
976
+ const argument = args[index];
977
+ if (!parameter || !argument)
978
+ continue;
979
+ const argumentReference = factorReferenceForExpression(argument, context);
980
+ if (argumentReference) {
981
+ if (ts.isIdentifier(parameter.name)) {
982
+ callContext.factorBindings.set(parameter.name.text, argumentReference);
983
+ }
984
+ else if (ts.isObjectBindingPattern(parameter.name)) {
985
+ bindObjectBindingPattern(parameter.name, argumentReference, callContext);
986
+ }
987
+ continue;
988
+ }
989
+ if (ts.isIdentifier(parameter.name) && !expressionContainsJSX(argument)) {
990
+ callContext.expressionAliases.set(parameter.name.text, argument);
991
+ }
992
+ }
993
+ return callContext;
994
+ }
995
+ function nonEmptyCollectionPredicate(reference) {
996
+ return {
997
+ kind: "relation",
998
+ operator: ">",
999
+ ref: appendFactorReferencePath(reference, "length"),
1000
+ value: { kind: "number", value: 0 },
1001
+ };
1002
+ }
1003
+ function opaqueJSXBranchPredicate(expression, context) {
1004
+ return {
1005
+ kind: "opaque",
1006
+ reason: "JSX callback source is not a direct props/context/scope expression",
1007
+ text: expression.getText(context.sourceFile),
1008
+ };
1009
+ }
1010
+ function jsxAttributeFactorReference(attributes, context) {
1011
+ for (const property of attributes.properties) {
1012
+ if (!ts.isJsxAttribute(property) || !property.initializer)
1013
+ continue;
1014
+ if (!ts.isIdentifier(property.name))
1015
+ continue;
1016
+ if (!new Set(["items", "rows", "data", "options"]).has(property.name.text))
1017
+ continue;
1018
+ if (!ts.isJsxExpression(property.initializer) || !property.initializer.expression)
1019
+ continue;
1020
+ const reference = factorReferenceForExpression(property.initializer.expression, context);
1021
+ if (reference)
1022
+ return reference;
1023
+ }
1024
+ return undefined;
1025
+ }
1026
+ function defaultStaticValuesForFunctionLike(component) {
1027
+ const values = new Map();
1028
+ const propsParameter = component.parameters[0];
1029
+ if (!propsParameter || !ts.isObjectBindingPattern(propsParameter.name))
1030
+ return values;
1031
+ collectBindingDefaults(propsParameter.name, { root: "props", path: [] }, values);
1032
+ return values;
1033
+ }
1034
+ function collectBindingDefaults(pattern, rootReference, values) {
1035
+ for (const element of pattern.elements) {
1036
+ if (element.dotDotDotToken)
1037
+ continue;
1038
+ const propertyName = element.propertyName ? bindingNameText(element.propertyName) : bindingNameText(element.name);
1039
+ if (!propertyName)
1040
+ continue;
1041
+ const nextReference = appendFactorReferencePath(rootReference, propertyName);
1042
+ if (element.initializer) {
1043
+ const value = readStaticBranchValue(element.initializer);
1044
+ if (value)
1045
+ values.set(factorReferenceKey(nextReference), value);
1046
+ }
1047
+ if (ts.isObjectBindingPattern(element.name)) {
1048
+ collectBindingDefaults(element.name, nextReference, values);
1049
+ }
1050
+ }
1051
+ }
1052
+ function scopeHookProviderFromCall(expression, scopeHookNames) {
1053
+ return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && scopeHookNames.has(expression.expression.text)
1054
+ ? expression.expression.text
1055
+ : undefined;
1056
+ }
1057
+ function reachableJSXDependenciesForComponent(sourceFile, componentName, branchContext, context) {
1058
+ const dependencies = [];
1059
+ const componentBody = getFunctionLikeBody(sourceFile, componentName);
1060
+ if (!componentBody)
1061
+ return dependencies;
1062
+ const helperFunctions = getTopLevelFunctionLikeBodiesForPath(sourceFile, context.entryPath, context.cache);
1063
+ const localComponentNames = helperFunctions;
1064
+ const importBindings = componentDependencyBindingsForFile(sourceFile, context.entryPath, context);
1065
+ const localAliases = localComponentAliasBindingsForBody(componentBody);
1066
+ const always = { kind: "always" };
1067
+ const visitedHelpers = new Set([componentName]);
1068
+ if (ts.isBlock(componentBody)) {
1069
+ for (const statement of componentBody.statements)
1070
+ visitStatement(statement, always, branchContext, visitedHelpers);
1071
+ }
1072
+ else {
1073
+ visitExpression(componentBody, always, branchContext, visitedHelpers);
1074
+ }
1075
+ return dependencies;
1076
+ function visitStatement(statement, condition, currentBranchContext, currentVisitedHelpers) {
1077
+ if (ts.isBlock(statement)) {
1078
+ for (const child of statement.statements)
1079
+ visitStatement(child, condition, currentBranchContext, currentVisitedHelpers);
1080
+ return;
1081
+ }
1082
+ if (ts.isReturnStatement(statement)) {
1083
+ if (statement.expression)
1084
+ visitExpression(statement.expression, condition, currentBranchContext, currentVisitedHelpers);
1085
+ return;
1086
+ }
1087
+ if (ts.isIfStatement(statement)) {
1088
+ const predicate = parseJSXBranchPredicate(statement.expression, currentBranchContext);
1089
+ visitStatementOrBlock(statement.thenStatement, andJSXBranchPredicates(condition, predicate), currentBranchContext, currentVisitedHelpers);
1090
+ if (statement.elseStatement) {
1091
+ visitStatementOrBlock(statement.elseStatement, andJSXBranchPredicates(condition, notJSXBranchPredicate(predicate)), currentBranchContext, currentVisitedHelpers);
1092
+ }
1093
+ return;
1094
+ }
1095
+ if (ts.isExpressionStatement(statement)) {
1096
+ visitExpression(statement.expression, condition, currentBranchContext, currentVisitedHelpers);
1097
+ return;
1098
+ }
1099
+ if (nodeContainsJSX(statement)) {
1100
+ visitOpaqueJSXSubtree(statement, condition, currentBranchContext, currentVisitedHelpers);
1101
+ }
1102
+ }
1103
+ function visitStatementOrBlock(statement, condition, currentBranchContext, currentVisitedHelpers) {
1104
+ if (ts.isBlock(statement)) {
1105
+ for (const child of statement.statements)
1106
+ visitStatement(child, condition, currentBranchContext, currentVisitedHelpers);
1107
+ return;
1108
+ }
1109
+ visitStatement(statement, condition, currentBranchContext, currentVisitedHelpers);
1110
+ }
1111
+ function visitExpression(expression, condition, currentBranchContext, currentVisitedHelpers) {
1112
+ const value = unwrapExpression(expression);
1113
+ if (ts.isJsxElement(value)) {
1114
+ visitJSXOpeningLike(value.openingElement, condition, currentBranchContext, currentVisitedHelpers);
1115
+ for (const child of value.children)
1116
+ visitJSXChild(child, condition, currentBranchContext, currentVisitedHelpers);
1117
+ return;
1118
+ }
1119
+ if (ts.isJsxSelfClosingElement(value)) {
1120
+ visitJSXOpeningLike(value, condition, currentBranchContext, currentVisitedHelpers);
1121
+ return;
1122
+ }
1123
+ if (ts.isJsxFragment(value)) {
1124
+ for (const child of value.children)
1125
+ visitJSXChild(child, condition, currentBranchContext, currentVisitedHelpers);
1126
+ return;
1127
+ }
1128
+ if (ts.isConditionalExpression(value)) {
1129
+ const predicate = parseJSXBranchPredicate(value.condition, currentBranchContext);
1130
+ visitExpression(value.whenTrue, andJSXBranchPredicates(condition, predicate), currentBranchContext, currentVisitedHelpers);
1131
+ visitExpression(value.whenFalse, andJSXBranchPredicates(condition, notJSXBranchPredicate(predicate)), currentBranchContext, currentVisitedHelpers);
1132
+ return;
1133
+ }
1134
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
1135
+ const predicate = parseJSXBranchPredicate(value.left, currentBranchContext);
1136
+ visitExpression(value.right, andJSXBranchPredicates(condition, predicate), currentBranchContext, currentVisitedHelpers);
1137
+ return;
1138
+ }
1139
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
1140
+ const predicate = parseJSXBranchPredicate(value.left, currentBranchContext);
1141
+ visitExpression(value.right, andJSXBranchPredicates(condition, notJSXBranchPredicate(predicate)), currentBranchContext, currentVisitedHelpers);
1142
+ return;
1143
+ }
1144
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
1145
+ visitFunctionLikeBody(value, condition, currentBranchContext, currentVisitedHelpers);
1146
+ return;
1147
+ }
1148
+ if (ts.isCallExpression(value) && visitJSXProducingCall(value, condition, currentBranchContext, currentVisitedHelpers)) {
1149
+ return;
1150
+ }
1151
+ ts.forEachChild(value, (child) => {
1152
+ if (isExpressionWithPossibleJSX(child))
1153
+ visitExpression(child, condition, currentBranchContext, currentVisitedHelpers);
1154
+ });
1155
+ }
1156
+ function visitFunctionLikeBody(functionLike, condition, currentBranchContext, currentVisitedHelpers) {
1157
+ if (!functionLike.body)
1158
+ return;
1159
+ if (ts.isBlock(functionLike.body)) {
1160
+ for (const statement of functionLike.body.statements)
1161
+ visitStatement(statement, condition, currentBranchContext, currentVisitedHelpers);
1162
+ return;
1163
+ }
1164
+ visitExpression(functionLike.body, condition, currentBranchContext, currentVisitedHelpers);
1165
+ }
1166
+ function visitJSXProducingCall(call, condition, currentBranchContext, currentVisitedHelpers) {
1167
+ if (ts.isPropertyAccessExpression(call.expression) && call.expression.name.text === "map") {
1168
+ const callback = call.arguments[0] ? unwrapExpression(call.arguments[0]) : undefined;
1169
+ if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
1170
+ const sourceReference = factorReferenceForExpression(call.expression.expression, currentBranchContext);
1171
+ const callbackCondition = sourceReference
1172
+ ? andJSXBranchPredicates(condition, nonEmptyCollectionPredicate(sourceReference))
1173
+ : andJSXBranchPredicates(condition, opaqueJSXBranchPredicate(call.expression.expression, currentBranchContext));
1174
+ const callbackContext = sourceReference
1175
+ ? bindCallbackItemParameter(callback, sourceReference, currentBranchContext)
1176
+ : cloneJSXBranchAnalysisContext(currentBranchContext);
1177
+ visitFunctionLikeBody(callback, callbackCondition, callbackContext, currentVisitedHelpers);
1178
+ return true;
1179
+ }
1180
+ }
1181
+ if (ts.isIdentifier(call.expression)) {
1182
+ const helper = getFunctionLikeDeclaration(sourceFile, call.expression.text);
1183
+ if (helper?.body && expressionContainsJSX(helper.body) && !currentVisitedHelpers.has(call.expression.text)) {
1184
+ const helperVisited = new Set(currentVisitedHelpers);
1185
+ helperVisited.add(call.expression.text);
1186
+ const helperContext = bindFunctionCallArguments(helper, call.arguments, currentBranchContext);
1187
+ visitFunctionLikeBody(helper, condition, helperContext, helperVisited);
1188
+ return true;
1189
+ }
1190
+ }
1191
+ let handled = false;
1192
+ for (const argument of call.arguments) {
1193
+ if (!expressionContainsJSX(argument))
1194
+ continue;
1195
+ visitExpression(argument, condition, currentBranchContext, currentVisitedHelpers);
1196
+ handled = true;
1197
+ }
1198
+ return handled;
1199
+ }
1200
+ function visitOpaqueJSXSubtree(node, condition, currentBranchContext, currentVisitedHelpers) {
1201
+ const opaqueCondition = andJSXBranchPredicates(condition, {
1202
+ kind: "opaque",
1203
+ reason: "JSX is produced from unsupported statement-level control flow",
1204
+ text: summarizeNodeText(node, currentBranchContext.sourceFile),
1205
+ });
1206
+ visit(node);
1207
+ function visit(current) {
1208
+ if (ts.isJsxElement(current)) {
1209
+ visitJSXOpeningLike(current.openingElement, opaqueCondition, currentBranchContext, currentVisitedHelpers);
1210
+ for (const child of current.children)
1211
+ visit(child);
1212
+ return;
1213
+ }
1214
+ if (ts.isJsxSelfClosingElement(current)) {
1215
+ visitJSXOpeningLike(current, opaqueCondition, currentBranchContext, currentVisitedHelpers);
1216
+ return;
1217
+ }
1218
+ if (ts.isJsxFragment(current)) {
1219
+ for (const child of current.children)
1220
+ visit(child);
1221
+ return;
1222
+ }
1223
+ ts.forEachChild(current, visit);
1224
+ }
1225
+ }
1226
+ function visitJSXChild(child, condition, currentBranchContext, currentVisitedHelpers) {
1227
+ if (ts.isJsxText(child))
1228
+ return;
1229
+ if (ts.isJsxExpression(child)) {
1230
+ if (child.expression)
1231
+ visitExpression(child.expression, condition, currentBranchContext, currentVisitedHelpers);
1232
+ return;
1233
+ }
1234
+ visitExpression(child, condition, currentBranchContext, currentVisitedHelpers);
1235
+ }
1236
+ function visitJSXOpeningLike(node, condition, currentBranchContext, currentVisitedHelpers) {
1237
+ const target = componentDependencyTargetForJsxTag(node.tagName, {
1238
+ filePath: context.entryPath,
1239
+ importBindings,
1240
+ localAliases,
1241
+ localComponentNames,
1242
+ });
1243
+ if (target) {
1244
+ dependencies.push({
1245
+ condition,
1246
+ tagName: jsxTagNameText(node.tagName, sourceFile),
1247
+ target,
1248
+ });
1249
+ }
1250
+ visitJSXAttributes(node.attributes, condition, currentBranchContext, currentVisitedHelpers);
1251
+ }
1252
+ function visitJSXAttributes(attributes, condition, currentBranchContext, currentVisitedHelpers) {
1253
+ const itemSource = jsxAttributeFactorReference(attributes, currentBranchContext);
1254
+ for (const property of attributes.properties) {
1255
+ if (!ts.isJsxAttribute(property) || !property.initializer)
1256
+ continue;
1257
+ const initializer = property.initializer;
1258
+ const expression = ts.isJsxExpression(initializer) ? initializer.expression : initializer;
1259
+ if (!expression || !expressionContainsJSX(expression))
1260
+ continue;
1261
+ const value = unwrapExpression(expression);
1262
+ if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && value.parameters.length > 0) {
1263
+ const callbackCondition = itemSource
1264
+ ? andJSXBranchPredicates(condition, nonEmptyCollectionPredicate(itemSource))
1265
+ : andJSXBranchPredicates(condition, opaqueJSXBranchPredicate(value, currentBranchContext));
1266
+ const callbackContext = itemSource ? bindCallbackItemParameter(value, itemSource, currentBranchContext) : currentBranchContext;
1267
+ visitFunctionLikeBody(value, callbackCondition, callbackContext, currentVisitedHelpers);
1268
+ continue;
1269
+ }
1270
+ visitExpression(expression, condition, currentBranchContext, currentVisitedHelpers);
1271
+ }
1272
+ }
1273
+ }
1274
+ function isExpressionWithPossibleJSX(node) {
1275
+ return ts.isExpression(node) && nodeContainsJSX(node);
1276
+ }
1277
+ function expressionContainsJSX(node) {
1278
+ return nodeContainsJSX(node);
1279
+ }
1280
+ function nodeContainsJSX(node) {
1281
+ let found = false;
1282
+ visit(node);
1283
+ return found;
1284
+ function visit(current) {
1285
+ if (found)
1286
+ return;
1287
+ if (ts.isJsxElement(current) || ts.isJsxSelfClosingElement(current) || ts.isJsxFragment(current)) {
1288
+ found = true;
1289
+ return;
1290
+ }
1291
+ ts.forEachChild(current, visit);
1292
+ }
1293
+ }
1294
+ function summarizeNodeText(node, sourceFile) {
1295
+ const text = node.getText(sourceFile).replace(/\s+/g, " ").trim();
1296
+ return text.length > 140 ? `${text.slice(0, 137)}...` : text;
1297
+ }
1298
+ function parseJSXBranchPredicate(expression, context, resolvingAliases = new Set()) {
1299
+ const value = unwrapExpression(expression);
1300
+ if (value.kind === ts.SyntaxKind.TrueKeyword)
1301
+ return { kind: "static", value: true };
1302
+ if (value.kind === ts.SyntaxKind.FalseKeyword)
1303
+ return { kind: "static", value: false };
1304
+ if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.ExclamationToken) {
1305
+ return notJSXBranchPredicate(parseJSXBranchPredicate(value.operand, context, resolvingAliases));
1306
+ }
1307
+ if (ts.isBinaryExpression(value)) {
1308
+ if (value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
1309
+ return andJSXBranchPredicates(parseJSXBranchPredicate(value.left, context, resolvingAliases), parseJSXBranchPredicate(value.right, context, resolvingAliases));
1310
+ }
1311
+ if (value.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
1312
+ return orJSXBranchPredicates(parseJSXBranchPredicate(value.left, context, resolvingAliases), parseJSXBranchPredicate(value.right, context, resolvingAliases));
1313
+ }
1314
+ const equalityPredicate = parseBinaryComparisonPredicate(value, context, resolvingAliases);
1315
+ if (equalityPredicate)
1316
+ return equalityPredicate;
1317
+ }
1318
+ if (ts.isCallExpression(value) &&
1319
+ ts.isIdentifier(value.expression) &&
1320
+ value.expression.text === "Boolean" &&
1321
+ value.arguments.length === 1 &&
1322
+ value.arguments[0]) {
1323
+ return parseJSXBranchPredicate(value.arguments[0], context, resolvingAliases);
1324
+ }
1325
+ if (ts.isIdentifier(value)) {
1326
+ const alias = context.expressionAliases.get(value.text);
1327
+ if (alias && !resolvingAliases.has(value.text)) {
1328
+ resolvingAliases.add(value.text);
1329
+ const predicate = parseJSXBranchPredicate(alias, context, resolvingAliases);
1330
+ resolvingAliases.delete(value.text);
1331
+ return predicate;
1332
+ }
1333
+ }
1334
+ const reference = factorReferenceForExpression(value, context);
1335
+ if (reference)
1336
+ return { kind: "truthy", ref: reference };
1337
+ const staticValue = readStaticBranchValue(value);
1338
+ const staticTruthy = staticValue ? staticBranchValueTruthy(staticValue) : undefined;
1339
+ if (staticTruthy !== undefined)
1340
+ return { kind: "static", value: staticTruthy };
1341
+ return {
1342
+ kind: "opaque",
1343
+ reason: "condition is not a direct props/context/scope expression",
1344
+ text: value.getText(context.sourceFile),
1345
+ };
1346
+ }
1347
+ function parseBinaryComparisonPredicate(expression, context, resolvingAliases) {
1348
+ const leftReference = factorReferenceForExpression(expression.left, context);
1349
+ const rightReference = factorReferenceForExpression(expression.right, context);
1350
+ const leftValue = readStaticBranchValue(expression.left);
1351
+ const rightValue = readStaticBranchValue(expression.right);
1352
+ const equalityOperators = new Set([
1353
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
1354
+ ts.SyntaxKind.EqualsEqualsToken,
1355
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
1356
+ ts.SyntaxKind.ExclamationEqualsToken,
1357
+ ]);
1358
+ if (equalityOperators.has(expression.operatorToken.kind)) {
1359
+ if (leftReference && rightReference) {
1360
+ const predicate = { kind: "equals-ref", left: leftReference, right: rightReference };
1361
+ return expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
1362
+ expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken
1363
+ ? notJSXBranchPredicate(predicate)
1364
+ : predicate;
1365
+ }
1366
+ const reference = leftReference ?? rightReference;
1367
+ const staticValue = leftReference ? rightValue : rightReference ? leftValue : undefined;
1368
+ if (!reference || !staticValue)
1369
+ return undefined;
1370
+ const predicate = { kind: "equals", ref: reference, value: staticValue };
1371
+ return expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
1372
+ expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken
1373
+ ? notJSXBranchPredicate(predicate)
1374
+ : predicate;
1375
+ }
1376
+ const relationOperator = relationOperatorText(expression.operatorToken.kind);
1377
+ if (relationOperator) {
1378
+ if (leftReference && rightValue)
1379
+ return { kind: "relation", operator: relationOperator, ref: leftReference, value: rightValue };
1380
+ if (rightReference && leftValue) {
1381
+ const invertedOperator = invertRelationOperator(relationOperator);
1382
+ return { kind: "relation", operator: invertedOperator, ref: rightReference, value: leftValue };
1383
+ }
1384
+ }
1385
+ return undefined;
1386
+ }
1387
+ function relationOperatorText(kind) {
1388
+ if (kind === ts.SyntaxKind.LessThanToken)
1389
+ return "<";
1390
+ if (kind === ts.SyntaxKind.LessThanEqualsToken)
1391
+ return "<=";
1392
+ if (kind === ts.SyntaxKind.GreaterThanToken)
1393
+ return ">";
1394
+ if (kind === ts.SyntaxKind.GreaterThanEqualsToken)
1395
+ return ">=";
1396
+ return undefined;
1397
+ }
1398
+ function invertRelationOperator(operator) {
1399
+ if (operator === "<")
1400
+ return ">";
1401
+ if (operator === "<=")
1402
+ return ">=";
1403
+ if (operator === ">")
1404
+ return "<";
1405
+ return "<=";
1406
+ }
1407
+ function factorReferenceForExpression(expression, context) {
1408
+ const value = unwrapExpression(expression);
1409
+ if (ts.isIdentifier(value)) {
1410
+ const direct = context.factorBindings.get(value.text);
1411
+ if (direct)
1412
+ return direct;
1413
+ const alias = context.expressionAliases.get(value.text);
1414
+ return alias ? factorReferenceForExpression(alias, context) : undefined;
1415
+ }
1416
+ if (ts.isPropertyAccessExpression(value)) {
1417
+ const base = factorReferenceForExpression(value.expression, context);
1418
+ return base ? appendFactorReferencePath(base, value.name.text) : undefined;
1419
+ }
1420
+ if (ts.isElementAccessExpression(value) && ts.isStringLiteralLike(value.argumentExpression)) {
1421
+ const base = factorReferenceForExpression(value.expression, context);
1422
+ return base ? appendFactorReferencePath(base, value.argumentExpression.text) : undefined;
1423
+ }
1424
+ if (ts.isElementAccessExpression(value) && ts.isNumericLiteral(value.argumentExpression)) {
1425
+ const base = factorReferenceForExpression(value.expression, context);
1426
+ return base ? appendFactorReferencePath(base, "number") : undefined;
1427
+ }
1428
+ return undefined;
1429
+ }
1430
+ function appendFactorReferencePath(reference, propertyName) {
1431
+ if (reference.root === "context") {
1432
+ return { root: "context", providerName: reference.providerName, path: [...reference.path, propertyName] };
1433
+ }
1434
+ return { root: reference.root, path: [...reference.path, propertyName] };
1435
+ }
1436
+ function andJSXBranchPredicates(left, right) {
1437
+ if (left.kind === "always")
1438
+ return right;
1439
+ if (right.kind === "always")
1440
+ return left;
1441
+ if (left.kind === "static" && left.value)
1442
+ return right;
1443
+ if (right.kind === "static" && right.value)
1444
+ return left;
1445
+ if (left.kind === "static" && !left.value)
1446
+ return left;
1447
+ if (right.kind === "static" && !right.value)
1448
+ return right;
1449
+ return {
1450
+ kind: "and",
1451
+ predicates: [
1452
+ ...(left.kind === "and" ? left.predicates : [left]),
1453
+ ...(right.kind === "and" ? right.predicates : [right]),
1454
+ ],
1455
+ };
1456
+ }
1457
+ function orJSXBranchPredicates(left, right) {
1458
+ if (left.kind === "static" && left.value)
1459
+ return left;
1460
+ if (right.kind === "static" && right.value)
1461
+ return right;
1462
+ if (left.kind === "static" && !left.value)
1463
+ return right;
1464
+ if (right.kind === "static" && !right.value)
1465
+ return left;
1466
+ return {
1467
+ kind: "or",
1468
+ predicates: [
1469
+ ...(left.kind === "or" ? left.predicates : [left]),
1470
+ ...(right.kind === "or" ? right.predicates : [right]),
1471
+ ],
1472
+ };
1473
+ }
1474
+ function notJSXBranchPredicate(predicate) {
1475
+ if (predicate.kind === "static")
1476
+ return { kind: "static", value: !predicate.value };
1477
+ if (predicate.kind === "not")
1478
+ return predicate.predicate;
1479
+ return { kind: "not", predicate };
1480
+ }
1481
+ function firstOpaqueJSXBranchPredicate(predicate) {
1482
+ if (predicate.kind === "opaque")
1483
+ return predicate;
1484
+ if (predicate.kind === "not")
1485
+ return firstOpaqueJSXBranchPredicate(predicate.predicate);
1486
+ if (predicate.kind === "and" || predicate.kind === "or") {
1487
+ for (const child of predicate.predicates) {
1488
+ const opaque = firstOpaqueJSXBranchPredicate(child);
1489
+ if (opaque)
1490
+ return opaque;
1491
+ }
1492
+ }
1493
+ return undefined;
1494
+ }
1495
+ function evaluateJSXBranchPredicate(predicate, values) {
1496
+ if (predicate.kind === "always")
1497
+ return true;
1498
+ if (predicate.kind === "static")
1499
+ return predicate.value;
1500
+ if (predicate.kind === "opaque")
1501
+ return "unknown";
1502
+ if (predicate.kind === "truthy") {
1503
+ return evaluateStaticBranchValueTruthy(staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" });
1504
+ }
1505
+ if (predicate.kind === "equals") {
1506
+ return evaluateSameStaticBranchValue(staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.value);
1507
+ }
1508
+ if (predicate.kind === "equals-ref") {
1509
+ return evaluateSameStaticBranchValue(staticBranchValueForReference(values, predicate.left) ?? { kind: "undefined" }, staticBranchValueForReference(values, predicate.right) ?? { kind: "undefined" });
1510
+ }
1511
+ if (predicate.kind === "relation") {
1512
+ return evaluateStaticBranchRelation(staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.operator, predicate.value);
1513
+ }
1514
+ if (predicate.kind === "not") {
1515
+ return evaluateNegatedJSXBranchPredicate(predicate.predicate, values);
1516
+ }
1517
+ if (predicate.kind === "and") {
1518
+ let unknown = false;
1519
+ for (const child of predicate.predicates) {
1520
+ const value = evaluateJSXBranchPredicate(child, values);
1521
+ if (value === false)
1522
+ return false;
1523
+ if (value === "unknown")
1524
+ unknown = true;
1525
+ }
1526
+ return unknown ? "unknown" : true;
1527
+ }
1528
+ if (predicate.kind === "or") {
1529
+ let unknown = false;
1530
+ for (const child of predicate.predicates) {
1531
+ const value = evaluateJSXBranchPredicate(child, values);
1532
+ if (value === true)
1533
+ return true;
1534
+ if (value === "unknown")
1535
+ unknown = true;
1536
+ }
1537
+ return unknown ? "unknown" : false;
1538
+ }
1539
+ return "unknown";
1540
+ }
1541
+ function evaluateNegatedJSXBranchPredicate(predicate, values) {
1542
+ if (predicate.kind === "truthy") {
1543
+ const value = staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" };
1544
+ if (value.kind === "oneOf") {
1545
+ let unknown = false;
1546
+ for (const option of value.values) {
1547
+ const result = evaluateStaticBranchValueTruthy(option);
1548
+ if (result === false)
1549
+ return true;
1550
+ if (result === "unknown")
1551
+ unknown = true;
1552
+ }
1553
+ return unknown ? "unknown" : false;
1554
+ }
1555
+ const truthy = evaluateStaticBranchValueTruthy(value);
1556
+ return truthy === "unknown" ? "unknown" : !truthy;
1557
+ }
1558
+ if (predicate.kind === "equals") {
1559
+ const value = staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" };
1560
+ if (value.kind === "oneOf") {
1561
+ let unknown = false;
1562
+ for (const option of value.values) {
1563
+ const result = evaluateSameStaticBranchValue(option, predicate.value);
1564
+ if (result === false)
1565
+ return true;
1566
+ if (result === "unknown")
1567
+ unknown = true;
1568
+ }
1569
+ return unknown ? "unknown" : false;
1570
+ }
1571
+ const result = evaluateSameStaticBranchValue(value, predicate.value);
1572
+ return result === "unknown" ? "unknown" : !result;
1573
+ }
1574
+ if (predicate.kind === "equals-ref") {
1575
+ const left = staticBranchValueForReference(values, predicate.left) ?? { kind: "undefined" };
1576
+ const right = staticBranchValueForReference(values, predicate.right) ?? { kind: "undefined" };
1577
+ if (left.kind === "oneOf") {
1578
+ let unknown = false;
1579
+ for (const option of left.values) {
1580
+ const result = evaluateSameStaticBranchValue(option, right);
1581
+ if (result === false)
1582
+ return true;
1583
+ if (result === "unknown")
1584
+ unknown = true;
1585
+ }
1586
+ return unknown ? "unknown" : false;
1587
+ }
1588
+ if (right.kind === "oneOf") {
1589
+ let unknown = false;
1590
+ for (const option of right.values) {
1591
+ const result = evaluateSameStaticBranchValue(left, option);
1592
+ if (result === false)
1593
+ return true;
1594
+ if (result === "unknown")
1595
+ unknown = true;
1596
+ }
1597
+ return unknown ? "unknown" : false;
1598
+ }
1599
+ const result = evaluateSameStaticBranchValue(left, right);
1600
+ return result === "unknown" ? "unknown" : !result;
1601
+ }
1602
+ if (predicate.kind === "relation") {
1603
+ const value = staticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" };
1604
+ if (value.kind === "oneOf") {
1605
+ let unknown = false;
1606
+ for (const option of value.values) {
1607
+ const result = evaluateStaticBranchRelation(option, predicate.operator, predicate.value);
1608
+ if (result === false)
1609
+ return true;
1610
+ if (result === "unknown")
1611
+ unknown = true;
1612
+ }
1613
+ return unknown ? "unknown" : false;
1614
+ }
1615
+ const result = evaluateStaticBranchRelation(value, predicate.operator, predicate.value);
1616
+ return result === "unknown" ? "unknown" : !result;
1617
+ }
1618
+ if (predicate.kind === "and")
1619
+ return evaluateJSXBranchPredicate({ kind: "or", predicates: predicate.predicates.map(notJSXBranchPredicate) }, values);
1620
+ if (predicate.kind === "or")
1621
+ return evaluateJSXBranchPredicate({ kind: "and", predicates: predicate.predicates.map(notJSXBranchPredicate) }, values);
1622
+ const value = evaluateJSXBranchPredicate(predicate, values);
1623
+ return value === "unknown" ? "unknown" : !value;
1624
+ }
1625
+ function staticBranchValueForReference(values, reference) {
1626
+ const key = factorReferenceKey(reference);
1627
+ const exact = values.get(key);
1628
+ if (exact)
1629
+ return exact;
1630
+ const parts = key.split(".");
1631
+ while (parts.length > 1) {
1632
+ parts.pop();
1633
+ const ancestor = values.get(parts.join("."));
1634
+ if (ancestor && staticBranchValueIncludesUnknown(ancestor))
1635
+ return { kind: "unknown" };
1636
+ }
1637
+ return undefined;
1638
+ }
1639
+ function staticBranchValueIncludesUnknown(value) {
1640
+ return value.kind === "unknown" || (value.kind === "oneOf" && value.values.some(staticBranchValueIncludesUnknown));
1641
+ }
1642
+ function evaluateStaticBranchValueTruthy(value) {
1643
+ if (value.kind === "unknown")
1644
+ return "unknown";
1645
+ if (value.kind === "oneOf") {
1646
+ let unknown = false;
1647
+ for (const option of value.values) {
1648
+ const result = evaluateStaticBranchValueTruthy(option);
1649
+ if (result === true)
1650
+ return true;
1651
+ if (result === "unknown")
1652
+ unknown = true;
1653
+ }
1654
+ return unknown ? "unknown" : false;
1655
+ }
1656
+ return staticBranchValueTruthy(value);
1657
+ }
1658
+ function evaluateSameStaticBranchValue(left, right) {
1659
+ if (left.kind === "unknown" || right.kind === "unknown")
1660
+ return "unknown";
1661
+ if (left.kind === "oneOf") {
1662
+ let unknown = false;
1663
+ for (const option of left.values) {
1664
+ const result = evaluateSameStaticBranchValue(option, right);
1665
+ if (result === true)
1666
+ return true;
1667
+ if (result === "unknown")
1668
+ unknown = true;
1669
+ }
1670
+ return unknown ? "unknown" : false;
1671
+ }
1672
+ if (right.kind === "oneOf") {
1673
+ let unknown = false;
1674
+ for (const option of right.values) {
1675
+ const result = evaluateSameStaticBranchValue(left, option);
1676
+ if (result === true)
1677
+ return true;
1678
+ if (result === "unknown")
1679
+ unknown = true;
1680
+ }
1681
+ return unknown ? "unknown" : false;
1682
+ }
1683
+ return sameStaticBranchValue(left, right);
1684
+ }
1685
+ function staticBranchValueTruthy(value) {
1686
+ if (value.kind === "unknown")
1687
+ return false;
1688
+ if (value.kind === "oneOf")
1689
+ return value.values.some(staticBranchValueTruthy);
1690
+ if (value.kind === "undefined" || value.kind === "null")
1691
+ return false;
1692
+ if (value.kind === "boolean")
1693
+ return value.value;
1694
+ if (value.kind === "number")
1695
+ return value.value !== 0 && !Number.isNaN(value.value);
1696
+ if (value.kind === "string")
1697
+ return value.value.length > 0;
1698
+ if (value.kind === "array" || value.kind === "object" || value.kind === "truthy")
1699
+ return true;
1700
+ return false;
1701
+ }
1702
+ function sameStaticBranchValue(left, right) {
1703
+ if (left.kind === "oneOf")
1704
+ return left.values.some((value) => sameStaticBranchValue(value, right));
1705
+ if (right.kind === "oneOf")
1706
+ return right.values.some((value) => sameStaticBranchValue(left, value));
1707
+ if (left.kind === "undefined" && right.kind === "undefined")
1708
+ return true;
1709
+ if (left.kind === "null" && right.kind === "null")
1710
+ return true;
1711
+ if (left.kind === "boolean" && right.kind === "boolean")
1712
+ return left.value === right.value;
1713
+ if (left.kind === "number" && right.kind === "number")
1714
+ return left.value === right.value;
1715
+ if (left.kind === "string" && right.kind === "string")
1716
+ return left.value === right.value;
1717
+ if (left.kind === "array" && right.kind === "array")
1718
+ return left.length === right.length;
1719
+ return false;
1720
+ }
1721
+ function evaluateStaticBranchRelation(left, operator, right) {
1722
+ if (left.kind === "unknown" || right.kind === "unknown")
1723
+ return "unknown";
1724
+ if (left.kind === "oneOf") {
1725
+ let unknown = false;
1726
+ for (const value of left.values) {
1727
+ const result = evaluateStaticBranchRelation(value, operator, right);
1728
+ if (result === true)
1729
+ return true;
1730
+ if (result === "unknown")
1731
+ unknown = true;
1732
+ }
1733
+ return unknown ? "unknown" : false;
1734
+ }
1735
+ if (right.kind === "oneOf") {
1736
+ let unknown = false;
1737
+ for (const value of right.values) {
1738
+ const result = evaluateStaticBranchRelation(left, operator, value);
1739
+ if (result === true)
1740
+ return true;
1741
+ if (result === "unknown")
1742
+ unknown = true;
1743
+ }
1744
+ return unknown ? "unknown" : false;
1745
+ }
1746
+ const leftNumber = staticBranchValueNumber(left);
1747
+ const rightNumber = staticBranchValueNumber(right);
1748
+ if (leftNumber === undefined || rightNumber === undefined)
1749
+ return "unknown";
1750
+ if (operator === "<")
1751
+ return leftNumber < rightNumber;
1752
+ if (operator === "<=")
1753
+ return leftNumber <= rightNumber;
1754
+ if (operator === ">")
1755
+ return leftNumber > rightNumber;
1756
+ return leftNumber >= rightNumber;
1757
+ }
1758
+ function staticBranchValueNumber(value) {
1759
+ if (value.kind === "number")
1760
+ return value.value;
1761
+ if (value.kind === "array")
1762
+ return value.length;
1763
+ return undefined;
1764
+ }
1765
+ function factorReferenceKey(reference) {
1766
+ if (reference.root === "context")
1767
+ return ["context", reference.providerName, ...reference.path].join(".");
1768
+ return [reference.root, ...reference.path].join(".");
1769
+ }
1770
+ function formatJSXBranchPredicate(predicate) {
1771
+ if (predicate.kind === "always")
1772
+ return "always";
1773
+ if (predicate.kind === "static")
1774
+ return String(predicate.value);
1775
+ if (predicate.kind === "truthy")
1776
+ return factorReferenceKey(predicate.ref);
1777
+ if (predicate.kind === "equals")
1778
+ return `${factorReferenceKey(predicate.ref)} === ${formatStaticBranchValue(predicate.value)}`;
1779
+ if (predicate.kind === "equals-ref")
1780
+ return `${factorReferenceKey(predicate.left)} === ${factorReferenceKey(predicate.right)}`;
1781
+ if (predicate.kind === "relation") {
1782
+ return `${factorReferenceKey(predicate.ref)} ${predicate.operator} ${formatStaticBranchValue(predicate.value)}`;
1783
+ }
1784
+ if (predicate.kind === "not")
1785
+ return `!(${formatJSXBranchPredicate(predicate.predicate)})`;
1786
+ if (predicate.kind === "and")
1787
+ return predicate.predicates.map(formatJSXBranchPredicate).join(" && ");
1788
+ if (predicate.kind === "or")
1789
+ return predicate.predicates.map(formatJSXBranchPredicate).join(" || ");
1790
+ return predicate.text;
1791
+ }
1792
+ function formatStaticBranchValue(value) {
1793
+ if (value.kind === "boolean")
1794
+ return String(value.value);
1795
+ if (value.kind === "number")
1796
+ return String(value.value);
1797
+ if (value.kind === "string")
1798
+ return JSON.stringify(value.value);
1799
+ if (value.kind === "array")
1800
+ return `array(length=${value.length})`;
1801
+ if (value.kind === "oneOf")
1802
+ return `oneOf(${value.values.map(formatStaticBranchValue).join(", ")})`;
1803
+ return value.kind;
1804
+ }
1805
+ function jsxTagNameText(tagName, sourceFile) {
1806
+ if (ts.isIdentifier(tagName))
1807
+ return tagName.text;
1808
+ return tagName.getText(sourceFile);
1809
+ }
1810
+ function getNonGTSXHookCalls(sourceFile, componentName, scopeHookNames, context) {
1811
+ const violations = new Map();
1812
+ visitComponent(sourceFile, context.entryPath, componentName);
1813
+ return [...violations.values()];
1814
+ function visitComponent(currentSourceFile, currentPath, currentComponentName) {
1815
+ const componentKey = `${currentPath}#${currentComponentName}`;
1816
+ if (context.visitedComponents.has(componentKey))
1817
+ return;
1818
+ context.visitedComponents.add(componentKey);
1819
+ const componentBody = getFunctionLikeBody(currentSourceFile, currentComponentName);
1820
+ if (!componentBody)
1821
+ return;
1822
+ const currentScopeHookNames = currentPath === context.entryPath
1823
+ ? scopeHookNames
1824
+ : new Set([
1825
+ ...getScopeHookNames(currentSourceFile),
1826
+ ...getImportedScopeHookNames(currentSourceFile, currentPath, context.cwd, context.cache),
1827
+ ]);
1828
+ const helperFunctions = getTopLevelFunctionLikeBodiesForPath(currentSourceFile, currentPath, context.cache);
1829
+ const visitedHelpers = new Set([currentComponentName]);
1830
+ const localComponentNames = helperFunctions;
1831
+ const importBindings = componentDependencyBindingsForFile(currentSourceFile, currentPath, context);
1832
+ const file = normalizeProjectPath(relative(context.cwd, currentPath));
1833
+ visit(componentBody, localComponentAliasBindingsForBody(componentBody));
1834
+ function visit(node, localAliases) {
1835
+ if (ts.isCallExpression(node)) {
1836
+ const hookName = getHookCallName(node, currentSourceFile);
1837
+ if (hookName) {
1838
+ if (!isAllowedGTSXHookCall(node, hookName, currentScopeHookNames)) {
1839
+ const key = `${file}#${currentComponentName}:${hookName}`;
1840
+ violations.set(key, { hookName, componentName: currentComponentName, file });
1841
+ }
1842
+ }
1843
+ else if (ts.isIdentifier(node.expression)) {
1844
+ visitHelper(node.expression.text);
1845
+ }
1846
+ }
1847
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
1848
+ visitJSXDependency(node.tagName, localAliases);
1849
+ }
1850
+ ts.forEachChild(node, (child) => visit(child, localAliases));
1851
+ }
1852
+ function visitHelper(functionName) {
1853
+ if (visitedHelpers.has(functionName))
1854
+ return;
1855
+ const helperBody = helperFunctions.get(functionName);
1856
+ if (!helperBody)
1857
+ return;
1858
+ visitedHelpers.add(functionName);
1859
+ visit(helperBody, localComponentAliasBindingsForBody(helperBody));
1860
+ }
1861
+ function visitJSXDependency(tagName, localAliases) {
1862
+ const target = componentDependencyTargetForJsxTag(tagName, {
1863
+ filePath: currentPath,
1864
+ importBindings,
1865
+ localAliases,
1866
+ localComponentNames,
1867
+ });
1868
+ if (!target)
1869
+ return;
1870
+ const targetSourceFile = sourceFileForPath(target.filePath, context);
1871
+ if (!targetSourceFile)
1872
+ return;
1873
+ visitComponent(targetSourceFile, target.filePath, target.componentName);
1874
+ }
1875
+ }
1876
+ }
1877
+ function getGProviderConsumers(sourceFile, componentName, context) {
1878
+ const providers = new Set();
1879
+ visitComponent(sourceFile, context.entryPath, componentName);
1880
+ return providers;
1881
+ function visitComponent(currentSourceFile, currentPath, currentComponentName) {
1882
+ const componentKey = `${currentPath}#${currentComponentName}`;
1883
+ if (context.visitedComponents.has(componentKey))
1884
+ return;
1885
+ context.visitedComponents.add(componentKey);
1886
+ const componentBody = getFunctionLikeBody(currentSourceFile, currentComponentName);
1887
+ if (!componentBody)
1888
+ return;
1889
+ const scopeHookProviderNames = getScopeHookProviderNamesForFile(currentSourceFile, currentPath, context.cache);
1890
+ const helperFunctions = getTopLevelFunctionLikeBodiesForPath(currentSourceFile, currentPath, context.cache);
1891
+ const visitedHelpers = new Set([currentComponentName]);
1892
+ const localComponentNames = helperFunctions;
1893
+ const importBindings = componentDependencyBindingsForFile(currentSourceFile, currentPath, context);
1894
+ visit(componentBody, localComponentAliasBindingsForBody(componentBody));
1895
+ function visit(node, localAliases) {
1896
+ if (ts.isCallExpression(node)) {
1897
+ const contextProvider = gContextProviderNameFromCall(node);
1898
+ if (contextProvider) {
1899
+ providers.add(contextProvider);
1900
+ }
1901
+ else if (ts.isIdentifier(node.expression) && scopeHookProviderNames.has(node.expression.text)) {
1902
+ for (const providerName of scopeHookProviderNames.get(node.expression.text) ?? [])
1903
+ providers.add(providerName);
1904
+ }
1905
+ else if (ts.isIdentifier(node.expression) && !isHookName(node.expression.text)) {
1906
+ visitHelper(node.expression.text);
1907
+ }
1908
+ }
1909
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
1910
+ visitJSXDependency(node.tagName, localAliases);
1911
+ }
1912
+ ts.forEachChild(node, (child) => visit(child, localAliases));
1913
+ }
1914
+ function visitHelper(functionName) {
1915
+ if (visitedHelpers.has(functionName))
1916
+ return;
1917
+ const helperBody = helperFunctions.get(functionName);
1918
+ if (!helperBody)
1919
+ return;
1920
+ visitedHelpers.add(functionName);
1921
+ visit(helperBody, localComponentAliasBindingsForBody(helperBody));
1922
+ }
1923
+ function visitJSXDependency(tagName, localAliases) {
1924
+ const target = componentDependencyTargetForJsxTag(tagName, {
1925
+ filePath: currentPath,
1926
+ importBindings,
1927
+ localAliases,
1928
+ localComponentNames,
1929
+ });
1930
+ if (!target)
1931
+ return;
1932
+ const targetSourceFile = sourceFileForPath(target.filePath, context);
1933
+ if (!targetSourceFile)
1934
+ return;
1935
+ visitComponent(targetSourceFile, target.filePath, target.componentName);
1936
+ }
1937
+ }
1938
+ }
1939
+ function gContextProviderNameFromCall(node) {
1940
+ if (!ts.isIdentifier(node.expression))
1941
+ return undefined;
1942
+ if (node.expression.text !== "useGContext" && node.expression.text !== "useGContextUpdate")
1943
+ return undefined;
1944
+ const provider = node.arguments[0] ? unwrapExpression(node.arguments[0]) : undefined;
1945
+ return provider && ts.isIdentifier(provider) ? provider.text : undefined;
1946
+ }
1947
+ function localComponentAliasBindingsForBody(body) {
1948
+ const aliases = new Map();
1949
+ if (!ts.isBlock(body))
1950
+ return aliases;
1951
+ for (const statement of body.statements) {
1952
+ if (!ts.isVariableStatement(statement))
1953
+ continue;
1954
+ for (const declaration of statement.declarationList.declarations) {
1955
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
1956
+ continue;
1957
+ const initializer = unwrapExpression(declaration.initializer);
1958
+ if (ts.isIdentifier(initializer)) {
1959
+ aliases.set(declaration.name.text, initializer.text);
1960
+ }
1961
+ }
1962
+ }
1963
+ return aliases;
1964
+ }
1965
+ function getFunctionLikeBody(sourceFile, functionName) {
1966
+ return getFunctionLikeDeclaration(sourceFile, functionName)?.body;
1967
+ }
1968
+ function getFunctionLikeDeclaration(sourceFile, functionName) {
1969
+ for (const statement of sourceFile.statements) {
1970
+ if (ts.isFunctionDeclaration(statement) && statement.name?.text === functionName) {
1971
+ return statement;
1972
+ }
1973
+ if (!ts.isVariableStatement(statement))
1974
+ continue;
1975
+ for (const declaration of statement.declarationList.declarations) {
1976
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== functionName || !declaration.initializer)
1977
+ continue;
1978
+ const initializer = unwrapExpression(declaration.initializer);
1979
+ if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
1980
+ return initializer;
1981
+ }
1982
+ }
1983
+ }
1984
+ return undefined;
1985
+ }
1986
+ function getTopLevelFunctionLikeBodies(sourceFile) {
1987
+ const functions = new Map();
1988
+ for (const statement of sourceFile.statements) {
1989
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) {
1990
+ functions.set(statement.name.text, statement.body);
1991
+ continue;
1992
+ }
1993
+ if (!ts.isVariableStatement(statement))
1994
+ continue;
1995
+ for (const declaration of statement.declarationList.declarations) {
1996
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
1997
+ continue;
1998
+ const initializer = unwrapExpression(declaration.initializer);
1999
+ if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
2000
+ functions.set(declaration.name.text, initializer.body);
2001
+ }
2002
+ }
2003
+ }
2004
+ return functions;
2005
+ }
2006
+ function getTopLevelFunctionLikeBodiesForPath(sourceFile, filePath, cache) {
2007
+ const cached = cache?.topLevelFunctionLikeBodiesByPath.get(filePath);
2008
+ if (cached)
2009
+ return cached;
2010
+ const functions = getTopLevelFunctionLikeBodies(sourceFile);
2011
+ cache?.topLevelFunctionLikeBodiesByPath.set(filePath, functions);
2012
+ return functions;
2013
+ }
2014
+ function componentDependencyBindingsForFile(sourceFile, filePath, context) {
2015
+ const cached = context.cache?.componentDependencyBindingsByPath.get(filePath);
2016
+ if (cached)
2017
+ return cached;
2018
+ const bindings = {
2019
+ names: new Map(),
2020
+ namespaces: new Map(),
2021
+ };
2022
+ for (const statement of sourceFile.statements) {
2023
+ if (!ts.isImportDeclaration(statement) || !statement.importClause)
2024
+ continue;
2025
+ if (!ts.isStringLiteral(statement.moduleSpecifier))
2026
+ continue;
2027
+ const targetPath = resolveImportedGTSXPath(filePath, context.cwd, statement.moduleSpecifier.text, context.cache);
2028
+ if (!targetPath)
2029
+ continue;
2030
+ const targetSourceFile = sourceFileForPath(targetPath, context);
2031
+ if (!targetSourceFile)
2032
+ continue;
2033
+ const importClause = statement.importClause;
2034
+ if (importClause.name) {
2035
+ const componentName = getComponentExportName(targetSourceFile, "default");
2036
+ if (componentName) {
2037
+ bindings.names.set(importClause.name.text, { filePath: targetPath, componentName });
2038
+ }
2039
+ }
2040
+ if (!importClause.namedBindings)
2041
+ continue;
2042
+ if (ts.isNamedImports(importClause.namedBindings)) {
2043
+ for (const element of importClause.namedBindings.elements) {
2044
+ const importedName = element.propertyName?.text ?? element.name.text;
2045
+ const componentName = getComponentExportName(targetSourceFile, importedName);
2046
+ if (componentName) {
2047
+ bindings.names.set(element.name.text, { filePath: targetPath, componentName });
2048
+ }
2049
+ }
2050
+ }
2051
+ else if (ts.isNamespaceImport(importClause.namedBindings)) {
2052
+ const namespaceExports = exportedComponentTargetsForFile(targetSourceFile, targetPath, context.cache);
2053
+ if (namespaceExports.size > 0) {
2054
+ bindings.namespaces.set(importClause.namedBindings.name.text, namespaceExports);
2055
+ }
2056
+ }
2057
+ }
2058
+ context.cache?.componentDependencyBindingsByPath.set(filePath, bindings);
2059
+ return bindings;
2060
+ }
2061
+ function sourceFileForPath(filePath, context) {
2062
+ const cached = context.sourceFilesByPath.get(filePath);
2063
+ if (cached)
2064
+ return cached;
2065
+ const sourceFile = sourceFileForAbsolutePath(filePath, context.cache);
2066
+ if (!sourceFile)
2067
+ return undefined;
2068
+ context.sourceFilesByPath.set(filePath, sourceFile);
2069
+ return sourceFile;
2070
+ }
2071
+ function sourceFileForAbsolutePath(filePath, cache) {
2072
+ const cached = cache?.sourceFilesByPath.get(filePath);
2073
+ if (cached)
2074
+ return cached;
2075
+ if (!isFile(filePath))
2076
+ return undefined;
2077
+ const sourceFile = ts.createSourceFile(filePath, readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
2078
+ cache?.sourceFilesByPath.set(filePath, sourceFile);
2079
+ return sourceFile;
2080
+ }
2081
+ function exportedComponentTargetsForFile(sourceFile, filePath, cache) {
2082
+ const cached = cache?.exportedComponentTargetsByPath.get(filePath);
2083
+ if (cached)
2084
+ return cached;
2085
+ const targets = new Map();
2086
+ const defaultName = getComponentExportName(sourceFile, "default");
2087
+ if (defaultName) {
2088
+ targets.set("default", { filePath, componentName: defaultName });
2089
+ }
2090
+ for (const statement of sourceFile.statements) {
2091
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
2092
+ if (hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
2093
+ targets.set(statement.name.text, { filePath, componentName: statement.name.text });
2094
+ }
2095
+ continue;
2096
+ }
2097
+ if (ts.isVariableStatement(statement) && hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
2098
+ for (const declaration of statement.declarationList.declarations) {
2099
+ if (ts.isIdentifier(declaration.name) && getFunctionLikeBody(sourceFile, declaration.name.text)) {
2100
+ targets.set(declaration.name.text, { filePath, componentName: declaration.name.text });
2101
+ }
2102
+ }
2103
+ continue;
2104
+ }
2105
+ if (!ts.isExportDeclaration(statement) || statement.moduleSpecifier || !statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
2106
+ continue;
2107
+ }
2108
+ for (const element of statement.exportClause.elements) {
2109
+ const localName = element.propertyName?.text ?? element.name.text;
2110
+ if (getFunctionLikeBody(sourceFile, localName)) {
2111
+ targets.set(element.name.text, { filePath, componentName: localName });
2112
+ }
2113
+ }
2114
+ }
2115
+ cache?.exportedComponentTargetsByPath.set(filePath, targets);
2116
+ return targets;
2117
+ }
2118
+ function componentDependencyTargetForJsxTag(tagName, input) {
2119
+ if (ts.isIdentifier(tagName)) {
2120
+ return componentDependencyTargetForIdentifier(tagName.text, input);
2121
+ }
2122
+ if (ts.isPropertyAccessExpression(tagName) && ts.isIdentifier(tagName.expression)) {
2123
+ return input.importBindings.namespaces.get(tagName.expression.text)?.get(tagName.name.text);
2124
+ }
2125
+ return undefined;
2126
+ }
2127
+ function componentDependencyTargetForIdentifier(name, input) {
2128
+ if (!isComponentName(name))
2129
+ return undefined;
2130
+ let currentName = name;
2131
+ const visited = new Set();
2132
+ while (!visited.has(currentName)) {
2133
+ visited.add(currentName);
2134
+ const aliasTarget = input.localAliases?.get(currentName);
2135
+ if (aliasTarget) {
2136
+ currentName = aliasTarget;
2137
+ continue;
2138
+ }
2139
+ if (input.localComponentNames?.has(currentName)) {
2140
+ return { filePath: input.filePath, componentName: currentName };
2141
+ }
2142
+ const importTarget = input.importBindings.names.get(currentName);
2143
+ if (importTarget)
2144
+ return importTarget;
2145
+ return undefined;
2146
+ }
2147
+ return undefined;
2148
+ }
2149
+ function getHookCallName(node, sourceFile) {
2150
+ if (ts.isIdentifier(node.expression) && isHookName(node.expression.text)) {
2151
+ return node.expression.text;
2152
+ }
2153
+ if (ts.isPropertyAccessExpression(node.expression) && isHookName(node.expression.name.text)) {
2154
+ return node.expression.getText(sourceFile);
2155
+ }
2156
+ return undefined;
2157
+ }
2158
+ function isAllowedGTSXHookCall(node, hookName, scopeHookNames) {
2159
+ if (ts.isIdentifier(node.expression)) {
2160
+ return hookName === "useGContext" || hookName === "useGContextUpdate" || scopeHookNames.has(hookName);
2161
+ }
2162
+ if (ts.isPropertyAccessExpression(node.expression)) {
2163
+ return node.expression.name.text === "useUpdate";
2164
+ }
2165
+ return false;
2166
+ }
2167
+ function getGScopeHookCalls(sourceFile, componentName, scopeHookNames) {
2168
+ const component = getFunctionDeclaration(sourceFile, componentName);
2169
+ if (!component?.body)
2170
+ return [];
2171
+ const helperFunctions = getTopLevelFunctionDeclarations(sourceFile);
2172
+ const visitedHelpers = new Set([componentName]);
2173
+ const hookNames = new Set();
2174
+ visit(component.body);
2175
+ return [...hookNames];
2176
+ function visit(node) {
2177
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
2178
+ if (scopeHookNames.has(node.expression.text)) {
2179
+ hookNames.add(node.expression.text);
2180
+ }
2181
+ else if (!isHookName(node.expression.text)) {
2182
+ visitHelper(node.expression.text);
2183
+ }
2184
+ }
2185
+ ts.forEachChild(node, visit);
2186
+ }
2187
+ function visitHelper(functionName) {
2188
+ if (visitedHelpers.has(functionName))
2189
+ return;
2190
+ const helper = helperFunctions.get(functionName);
2191
+ if (!helper?.body)
2192
+ return;
2193
+ visitedHelpers.add(functionName);
2194
+ visit(helper.body);
2195
+ }
2196
+ }
2197
+ function getFunctionDeclaration(sourceFile, functionName) {
2198
+ return sourceFile.statements.find((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === functionName);
2199
+ }
2200
+ function getTopLevelFunctionDeclarations(sourceFile) {
2201
+ const functions = new Map();
2202
+ for (const statement of sourceFile.statements) {
2203
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
2204
+ functions.set(statement.name.text, statement);
2205
+ }
2206
+ }
2207
+ return functions;
2208
+ }
2209
+ function isHookName(name) {
2210
+ return name === "use" || /^use[A-Z0-9_]/.test(name);
2211
+ }
2212
+ function isComponentName(name) {
2213
+ return /^[A-Z]/.test(name);
2214
+ }
2215
+ function jsxTagIdentifier(tagName) {
2216
+ if (ts.isIdentifier(tagName))
2217
+ return tagName.text;
2218
+ return undefined;
2219
+ }
2220
+ function getCasesAssignment(statement, sourceFile, diagnostics) {
2221
+ if (!ts.isExpressionStatement(statement))
2222
+ return undefined;
2223
+ const expression = statement.expression;
2224
+ if (!ts.isBinaryExpression(expression) || expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
2225
+ return undefined;
2226
+ }
2227
+ if (!ts.isPropertyAccessExpression(expression.left) || expression.left.name.text !== "cases") {
2228
+ return undefined;
2229
+ }
2230
+ if (!ts.isIdentifier(expression.left.expression))
2231
+ return undefined;
2232
+ const casesExpression = unwrapExpression(expression.right);
2233
+ if (!ts.isObjectLiteralExpression(casesExpression)) {
2234
+ diagnostics.push({
2235
+ stage: "contract-extraction",
2236
+ code: "malformed-cases",
2237
+ message: "GTSX cases must be a statically enumerable object literal.",
2238
+ file: sourceFile.fileName,
2239
+ });
2240
+ return { targetName: expression.left.expression.text, cases: [], staticCases: [], statementStart: statement.getStart(sourceFile) };
2241
+ }
2242
+ return {
2243
+ targetName: expression.left.expression.text,
2244
+ statementStart: statement.getStart(sourceFile),
2245
+ ...readCasesObject(casesExpression, sourceFile, diagnostics),
2246
+ };
2247
+ }
2248
+ function readCasesObject(objectLiteral, sourceFile, diagnostics) {
2249
+ const cases = [];
2250
+ const staticCases = [];
2251
+ for (const property of objectLiteral.properties) {
2252
+ if (ts.isSpreadAssignment(property)) {
2253
+ diagnostics.push({
2254
+ stage: "contract-extraction",
2255
+ code: "malformed-cases",
2256
+ message: "GTSX cases do not support spread composition in the first implementation.",
2257
+ file: sourceFile.fileName,
2258
+ });
2259
+ continue;
2260
+ }
2261
+ if (!ts.isPropertyAssignment(property))
2262
+ continue;
2263
+ const caseName = getStaticPropertyName(property.name);
2264
+ if (!caseName) {
2265
+ diagnostics.push({
2266
+ stage: "contract-extraction",
2267
+ code: "non-static-case-key",
2268
+ message: "GTSX case keys must be statically enumerable object literal keys.",
2269
+ file: sourceFile.fileName,
2270
+ });
2271
+ continue;
2272
+ }
2273
+ const providerVariants = readProviderVariantMarkers(property.initializer);
2274
+ const caseValue = unwrapExpression(property.initializer);
2275
+ const providers = ts.isObjectLiteralExpression(caseValue) ? readProviderSelections(caseValue) : undefined;
2276
+ const kind = ts.isObjectLiteralExpression(caseValue) && hasStaticProperty(caseValue, "scope") ? "scope" : "pure";
2277
+ cases.push({
2278
+ kind,
2279
+ name: caseName,
2280
+ ...(providerVariants && Object.keys(providerVariants).length > 0 ? { providerVariants } : {}),
2281
+ ...(providers && Object.keys(providers).length > 0 ? { providers } : {}),
2282
+ });
2283
+ staticCases.push(readCaseStaticFacts(caseName, caseValue, providerVariants));
2284
+ }
2285
+ return { cases, staticCases };
2286
+ }
2287
+ function readProviderVariantMarkers(expression) {
2288
+ if (ts.isSatisfiesExpression(expression))
2289
+ return readProviderVariantMarkersFromType(expression.type);
2290
+ if (ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression))
2291
+ return readProviderVariantMarkers(expression.expression);
2292
+ return undefined;
2293
+ }
2294
+ function readCaseStaticFacts(caseName, caseValue, providerVariants) {
2295
+ const values = new Map();
2296
+ if (ts.isObjectLiteralExpression(caseValue)) {
2297
+ const props = objectLiteralPropertyExpression(caseValue, "props");
2298
+ if (props)
2299
+ flattenStaticObjectExpression(props, "props", values);
2300
+ const scope = objectLiteralPropertyExpression(caseValue, "scope");
2301
+ if (scope)
2302
+ flattenStaticObjectExpression(scope, "scope", values);
2303
+ const providers = objectLiteralPropertyExpression(caseValue, "providers");
2304
+ if (providers)
2305
+ flattenProviderStaticValues(providers, values);
2306
+ }
2307
+ for (const [providerName, selection] of Object.entries(providerVariants ?? {})) {
2308
+ const variants = providerVariantSelectionValues(selection);
2309
+ values.set(`context.${providerName}.variant`, variants.length === 1
2310
+ ? { kind: "string", value: variants[0] }
2311
+ : { kind: "oneOf", values: variants.map((variant) => ({ kind: "string", value: variant })) });
2312
+ }
2313
+ return {
2314
+ name: caseName,
2315
+ ...(providerVariants && Object.keys(providerVariants).length > 0 ? { providerVariants } : {}),
2316
+ values,
2317
+ };
2318
+ }
2319
+ function objectLiteralPropertyExpression(objectLiteral, propertyName) {
2320
+ const property = objectLiteral.properties.find((candidate) => ts.isPropertyAssignment(candidate) && getStaticPropertyName(candidate.name) === propertyName);
2321
+ return property ? unwrapExpression(property.initializer) : undefined;
2322
+ }
2323
+ function flattenProviderStaticValues(expression, values) {
2324
+ const providersValue = unwrapExpression(expression);
2325
+ if (!ts.isArrayLiteralExpression(providersValue))
2326
+ return;
2327
+ for (const element of providersValue.elements) {
2328
+ const entry = unwrapExpression(element);
2329
+ if (!ts.isArrayLiteralExpression(entry))
2330
+ continue;
2331
+ const providerExpression = entry.elements[0] ? unwrapExpression(entry.elements[0]) : undefined;
2332
+ const valueExpression = entry.elements[1] ? unwrapExpression(entry.elements[1]) : undefined;
2333
+ if (!providerExpression || !ts.isIdentifier(providerExpression) || !valueExpression)
2334
+ continue;
2335
+ flattenStaticObjectExpression(valueExpression, `context.${providerExpression.text}`, values);
2336
+ }
2337
+ }
2338
+ function flattenStaticObjectExpression(expression, prefix, values) {
2339
+ const value = unwrapExpression(expression);
2340
+ const staticValue = readStaticBranchValue(value);
2341
+ if (staticValue) {
2342
+ setStaticBranchValue(values, prefix, staticValue);
2343
+ if (staticValue.kind === "array")
2344
+ setStaticBranchValue(values, `${prefix}.length`, { kind: "number", value: staticValue.length });
2345
+ if (staticValue.kind === "string")
2346
+ setStaticBranchValue(values, `${prefix}.length`, { kind: "number", value: staticValue.value.length });
2347
+ }
2348
+ if (ts.isArrayLiteralExpression(value)) {
2349
+ for (const element of value.elements) {
2350
+ if (ts.isSpreadElement(element)) {
2351
+ setStaticBranchValue(values, `${prefix}.number`, { kind: "unknown" });
2352
+ continue;
2353
+ }
2354
+ flattenStaticObjectExpression(element, `${prefix}.number`, values);
2355
+ }
2356
+ return;
2357
+ }
2358
+ if (!ts.isObjectLiteralExpression(value)) {
2359
+ if (!staticValue)
2360
+ setStaticBranchValue(values, prefix, { kind: "unknown" });
2361
+ return;
2362
+ }
2363
+ for (const property of value.properties) {
2364
+ if (ts.isSpreadAssignment(property)) {
2365
+ setStaticBranchValue(values, prefix, { kind: "unknown" });
2366
+ continue;
2367
+ }
2368
+ if (!ts.isPropertyAssignment(property))
2369
+ continue;
2370
+ const propertyName = getStaticPropertyName(property.name);
2371
+ if (!propertyName)
2372
+ continue;
2373
+ flattenStaticObjectExpression(property.initializer, `${prefix}.${propertyName}`, values);
2374
+ }
2375
+ }
2376
+ function setStaticBranchValue(values, key, value) {
2377
+ const existing = values.get(key);
2378
+ if (!existing) {
2379
+ values.set(key, value);
2380
+ return;
2381
+ }
2382
+ if (sameStaticBranchValue(existing, value))
2383
+ return;
2384
+ values.set(key, { kind: "oneOf", values: [...staticBranchValueOptions(existing), value] });
2385
+ }
2386
+ function staticBranchValueOptions(value) {
2387
+ return value.kind === "oneOf" ? value.values : [value];
2388
+ }
2389
+ function readStaticBranchValue(expression) {
2390
+ const value = unwrapExpression(expression);
2391
+ if (value.kind === ts.SyntaxKind.TrueKeyword)
2392
+ return { kind: "boolean", value: true };
2393
+ if (value.kind === ts.SyntaxKind.FalseKeyword)
2394
+ return { kind: "boolean", value: false };
2395
+ if (value.kind === ts.SyntaxKind.NullKeyword)
2396
+ return { kind: "null" };
2397
+ if (ts.isIdentifier(value) && value.text === "undefined")
2398
+ return { kind: "undefined" };
2399
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
2400
+ return { kind: "string", value: value.text };
2401
+ if (ts.isNumericLiteral(value))
2402
+ return { kind: "number", value: Number(value.text) };
2403
+ if (ts.isArrayLiteralExpression(value)) {
2404
+ if (value.elements.some((element) => ts.isSpreadElement(element)))
2405
+ return undefined;
2406
+ return { kind: "array", length: value.elements.length };
2407
+ }
2408
+ if (ts.isObjectLiteralExpression(value))
2409
+ return { kind: "object" };
2410
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
2411
+ return { kind: "truthy" };
2412
+ if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.ExclamationToken) {
2413
+ const inner = readStaticBranchValue(value.operand);
2414
+ const truthy = inner ? staticBranchValueTruthy(inner) : undefined;
2415
+ return truthy === undefined ? undefined : { kind: "boolean", value: !truthy };
2416
+ }
2417
+ return undefined;
2418
+ }
2419
+ function readProviderVariantMarkersFromType(typeNode) {
2420
+ const variants = new Map();
2421
+ visit(typeNode);
2422
+ return Object.fromEntries([...variants.entries()].map(([providerName, providerVariants]) => {
2423
+ const values = [...providerVariants];
2424
+ return [providerName, values.length === 1 ? values[0] : values];
2425
+ }));
2426
+ function visit(node) {
2427
+ if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
2428
+ for (const child of node.types)
2429
+ visit(child);
2430
+ return;
2431
+ }
2432
+ if (ts.isParenthesizedTypeNode(node)) {
2433
+ visit(node.type);
2434
+ return;
2435
+ }
2436
+ if (!ts.isTypeReferenceNode(node))
2437
+ return;
2438
+ if (!ts.isIdentifier(node.typeName) || node.typeName.text !== "GProviderCase")
2439
+ return;
2440
+ const providerType = node.typeArguments?.[0];
2441
+ const variantType = node.typeArguments?.[1];
2442
+ if (!providerType || !variantType || !ts.isTypeQueryNode(providerType))
2443
+ return;
2444
+ if (!ts.isIdentifier(providerType.exprName))
2445
+ return;
2446
+ const providerVariants = readProviderVariantTypeValues(variantType);
2447
+ if (providerVariants.length === 0)
2448
+ return;
2449
+ const providerName = providerType.exprName.text;
2450
+ const current = variants.get(providerName) ?? new Set();
2451
+ for (const variant of providerVariants)
2452
+ current.add(variant);
2453
+ variants.set(providerName, current);
2454
+ }
2455
+ }
2456
+ function readProviderVariantTypeValues(typeNode) {
2457
+ if (ts.isParenthesizedTypeNode(typeNode))
2458
+ return readProviderVariantTypeValues(typeNode.type);
2459
+ if (ts.isUnionTypeNode(typeNode))
2460
+ return typeNode.types.flatMap(readProviderVariantTypeValues);
2461
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal))
2462
+ return [typeNode.literal.text];
2463
+ return [];
2464
+ }
2465
+ function readProviderSelections(caseValue) {
2466
+ const providersProperty = caseValue.properties.find((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === "providers");
2467
+ if (!providersProperty)
2468
+ return undefined;
2469
+ const providersValue = unwrapExpression(providersProperty.initializer);
2470
+ if (!ts.isArrayLiteralExpression(providersValue))
2471
+ return undefined;
2472
+ const providers = [];
2473
+ for (const element of providersValue.elements) {
2474
+ const entry = unwrapExpression(element);
2475
+ if (!ts.isArrayLiteralExpression(entry))
2476
+ continue;
2477
+ const providerExpression = entry.elements[0] ? unwrapExpression(entry.elements[0]) : undefined;
2478
+ if (providerExpression && ts.isIdentifier(providerExpression)) {
2479
+ providers.push(providerExpression.text);
2480
+ }
2481
+ }
2482
+ return providers;
2483
+ }
2484
+ function getStaticPropertyName(name) {
2485
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
2486
+ return name.text;
2487
+ }
2488
+ return undefined;
2489
+ }
2490
+ function unwrapExpression(expression) {
2491
+ if (ts.isSatisfiesExpression(expression) || ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression)) {
2492
+ return unwrapExpression(expression.expression);
2493
+ }
2494
+ return expression;
2495
+ }
2496
+ function isCreateGScopeCall(expression) {
2497
+ return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "createGScopeHook";
2498
+ }
2499
+ function isCreateGProviderCall(expression) {
2500
+ return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "createGProvider";
2501
+ }
2502
+ function hasStaticProperty(objectLiteral, propertyName) {
2503
+ return objectLiteral.properties.some((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === propertyName);
393
2504
  }
394
2505
  function hasModifier(node, kind) {
395
2506
  return Boolean(ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === kind));
396
2507
  }
2508
+ function normalizeProjectPath(filePath) {
2509
+ return filePath.split(sep).join("/");
2510
+ }
397
2511
  //# sourceMappingURL=analyzer.js.map