@memberjunction/react-test-harness 2.121.0 → 2.122.0

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.
@@ -0,0 +1,826 @@
1
+ "use strict";
2
+ /**
3
+ * Type Inference Engine - AST-based type inference for component linting
4
+ *
5
+ * This module analyzes JavaScript AST to infer and track types throughout
6
+ * component code. It integrates with TypeContext to provide comprehensive
7
+ * type information for validation rules.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || function (mod) {
26
+ if (mod && mod.__esModule) return mod;
27
+ var result = {};
28
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29
+ __setModuleDefault(result, mod);
30
+ return result;
31
+ };
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyzeTypes = exports.TypeInferenceEngine = void 0;
37
+ const traverse_1 = __importDefault(require("@babel/traverse"));
38
+ const t = __importStar(require("@babel/types"));
39
+ const type_context_1 = require("./type-context");
40
+ /**
41
+ * TypeInferenceEngine - Analyzes AST to infer and track types
42
+ */
43
+ class TypeInferenceEngine {
44
+ constructor(componentSpec, contextUser) {
45
+ this.errors = [];
46
+ this.functionReturnTypes = new Map();
47
+ this.componentSpec = componentSpec;
48
+ this.contextUser = contextUser;
49
+ this.typeContext = new type_context_1.TypeContext(componentSpec);
50
+ }
51
+ /**
52
+ * Analyze an AST and build type context
53
+ */
54
+ async analyze(ast) {
55
+ this.errors = [];
56
+ // First pass: collect all variable declarations and their types
57
+ await this.collectVariableTypes(ast);
58
+ // Return the result
59
+ return {
60
+ typeContext: this.typeContext,
61
+ errors: this.errors
62
+ };
63
+ }
64
+ /**
65
+ * Get the type context after analysis
66
+ */
67
+ getTypeContext() {
68
+ return this.typeContext;
69
+ }
70
+ /**
71
+ * First pass: collect variable types from declarations and assignments
72
+ */
73
+ async collectVariableTypes(ast) {
74
+ const self = this;
75
+ (0, traverse_1.default)(ast, {
76
+ // Track variable declarations
77
+ VariableDeclarator(path) {
78
+ self.inferDeclaratorType(path);
79
+ // Also check if it's a function expression assignment
80
+ // const calculateMetrics = () => { return {...} }
81
+ const node = path.node;
82
+ if (t.isIdentifier(node.id) &&
83
+ (t.isArrowFunctionExpression(node.init) || t.isFunctionExpression(node.init))) {
84
+ self.trackFunctionReturnType(node.id.name, node.init);
85
+ }
86
+ },
87
+ // Track assignments to existing variables
88
+ AssignmentExpression(path) {
89
+ self.inferAssignmentType(path);
90
+ },
91
+ // Track function parameters and return types
92
+ FunctionDeclaration(path) {
93
+ self.inferFunctionParameterTypes(path);
94
+ // Track return type of named functions
95
+ if (path.node.id) {
96
+ self.trackFunctionReturnType(path.node.id.name, path.node);
97
+ }
98
+ },
99
+ ArrowFunctionExpression(path) {
100
+ self.inferArrowFunctionParameterTypes(path);
101
+ }
102
+ });
103
+ }
104
+ /**
105
+ * Infer type from a variable declarator
106
+ */
107
+ inferDeclaratorType(path) {
108
+ const node = path.node;
109
+ // Get variable name(s)
110
+ if (t.isIdentifier(node.id)) {
111
+ const varName = node.id.name;
112
+ if (node.init) {
113
+ const type = this.inferExpressionType(node.init, path);
114
+ this.typeContext.setVariableType(varName, type);
115
+ }
116
+ else {
117
+ // Declared but not initialized
118
+ this.typeContext.setVariableType(varName, { type: 'undefined', nullable: true });
119
+ }
120
+ }
121
+ else if (t.isObjectPattern(node.id) && node.init) {
122
+ // Destructuring: const { a, b } = obj
123
+ this.inferDestructuringTypes(node.id, node.init, path);
124
+ }
125
+ else if (t.isArrayPattern(node.id) && node.init) {
126
+ // Array destructuring: const [a, b] = arr
127
+ this.inferArrayDestructuringTypes(node.id, node.init, path);
128
+ }
129
+ }
130
+ /**
131
+ * Infer type from an assignment expression
132
+ */
133
+ inferAssignmentType(path) {
134
+ const node = path.node;
135
+ if (t.isIdentifier(node.left)) {
136
+ const varName = node.left.name;
137
+ const type = this.inferExpressionType(node.right, path);
138
+ this.typeContext.setVariableType(varName, type);
139
+ }
140
+ }
141
+ /**
142
+ * Infer types for function parameters (component props)
143
+ */
144
+ inferFunctionParameterTypes(path) {
145
+ const params = path.node.params;
146
+ for (const param of params) {
147
+ if (t.isIdentifier(param)) {
148
+ // Simple parameter - unknown type
149
+ this.typeContext.setVariableType(param.name, type_context_1.StandardTypes.unknown);
150
+ }
151
+ else if (t.isObjectPattern(param)) {
152
+ // Destructured props - this is the common component pattern
153
+ this.inferComponentPropsTypes(param);
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Infer types for arrow function parameters
159
+ */
160
+ inferArrowFunctionParameterTypes(path) {
161
+ const params = path.node.params;
162
+ for (const param of params) {
163
+ if (t.isIdentifier(param)) {
164
+ this.typeContext.setVariableType(param.name, type_context_1.StandardTypes.unknown);
165
+ }
166
+ else if (t.isObjectPattern(param)) {
167
+ this.inferComponentPropsTypes(param);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Infer types for component props from destructuring pattern
173
+ */
174
+ inferComponentPropsTypes(pattern) {
175
+ for (const prop of pattern.properties) {
176
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
177
+ const propName = prop.key.name;
178
+ // Known standard props
179
+ switch (propName) {
180
+ case 'utilities':
181
+ this.typeContext.setVariableType(propName, {
182
+ type: 'object',
183
+ fields: new Map([
184
+ ['rv', { type: 'object', fromMetadata: true }], // RunView service
185
+ ['rq', { type: 'object', fromMetadata: true }], // RunQuery service
186
+ ['md', { type: 'object', fromMetadata: true }] // Metadata
187
+ ])
188
+ });
189
+ break;
190
+ case 'styles':
191
+ this.typeContext.setVariableType(propName, { type: 'object' });
192
+ break;
193
+ case 'components':
194
+ this.typeContext.setVariableType(propName, { type: 'object' });
195
+ break;
196
+ case 'callbacks':
197
+ this.typeContext.setVariableType(propName, { type: 'object' });
198
+ break;
199
+ case 'savedUserSettings':
200
+ this.typeContext.setVariableType(propName, { type: 'object', nullable: true });
201
+ break;
202
+ case 'onSavedUserSettingsChange':
203
+ case 'onSaveUserSettings':
204
+ this.typeContext.setVariableType(propName, { type: 'function' });
205
+ break;
206
+ default:
207
+ // Check if it's a prop defined in the component spec
208
+ if (this.componentSpec?.properties) {
209
+ const specProp = this.componentSpec.properties.find(p => p.name === propName);
210
+ if (specProp) {
211
+ this.typeContext.setVariableType(propName, {
212
+ type: this.mapSpecTypeToJSType(specProp.type),
213
+ nullable: !specProp.required
214
+ });
215
+ break;
216
+ }
217
+ }
218
+ // Check if it's an event (on* prefix)
219
+ if (propName.startsWith('on')) {
220
+ this.typeContext.setVariableType(propName, { type: 'function', nullable: true });
221
+ }
222
+ else {
223
+ this.typeContext.setVariableType(propName, type_context_1.StandardTypes.unknown);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Track function return type by analyzing its body
231
+ */
232
+ trackFunctionReturnType(functionName, func) {
233
+ const body = func.body;
234
+ // Arrow function with expression body: () => ({...})
235
+ if (t.isExpression(body)) {
236
+ const returnType = this.inferExpressionType(body);
237
+ this.functionReturnTypes.set(functionName, returnType);
238
+ return;
239
+ }
240
+ // Function with block body - find return statements
241
+ if (t.isBlockStatement(body)) {
242
+ // Try to analyze the function body for object building patterns
243
+ const returnType = this.analyzeObjectBuildingPattern(body);
244
+ if (returnType) {
245
+ this.functionReturnTypes.set(functionName, returnType);
246
+ return;
247
+ }
248
+ // Fallback: Find the first return statement
249
+ for (const stmt of body.body) {
250
+ if (t.isReturnStatement(stmt) && stmt.argument) {
251
+ const inferredType = this.inferExpressionType(stmt.argument);
252
+ this.functionReturnTypes.set(functionName, inferredType);
253
+ return;
254
+ }
255
+ }
256
+ }
257
+ // No return statement found or void function
258
+ this.functionReturnTypes.set(functionName, type_context_1.StandardTypes.unknown);
259
+ }
260
+ /**
261
+ * Analyze common object-building patterns in function bodies
262
+ * Pattern: const obj = {}; forEach(...) { obj[key] = { prop: value } }; return obj;
263
+ */
264
+ analyzeObjectBuildingPattern(body) {
265
+ let objectVarName = null;
266
+ let objectElementType = null;
267
+ const debugEnabled = false;
268
+ // Look for: const obj = {}; ... obj[key] = { ... }; ... return obj;
269
+ for (const stmt of body.body) {
270
+ // Find variable declaration: const obj = {};
271
+ if (t.isVariableDeclaration(stmt)) {
272
+ for (const decl of stmt.declarations) {
273
+ if (t.isIdentifier(decl.id) && t.isObjectExpression(decl.init) && decl.init.properties.length === 0) {
274
+ objectVarName = decl.id.name;
275
+ if (debugEnabled)
276
+ console.log('[TypeInference] Found empty object declaration:', objectVarName);
277
+ }
278
+ }
279
+ }
280
+ // Look for forEach or for loops that populate the object
281
+ if (objectVarName && t.isExpressionStatement(stmt) && t.isCallExpression(stmt.expression)) {
282
+ const call = stmt.expression;
283
+ if (t.isMemberExpression(call.callee) &&
284
+ t.isIdentifier(call.callee.property) &&
285
+ call.callee.property.name === 'forEach' &&
286
+ call.arguments.length > 0) {
287
+ const callback = call.arguments[0];
288
+ if ((t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback)) &&
289
+ t.isBlockStatement(callback.body)) {
290
+ // Look for obj[something] = { ... } pattern inside forEach
291
+ for (const innerStmt of callback.body.body) {
292
+ if (t.isExpressionStatement(innerStmt) &&
293
+ t.isAssignmentExpression(innerStmt.expression) &&
294
+ innerStmt.expression.operator === '=') {
295
+ const left = innerStmt.expression.left;
296
+ const right = innerStmt.expression.right;
297
+ // Check if left is obj[...] and right is object literal
298
+ if (t.isMemberExpression(left) &&
299
+ left.computed &&
300
+ t.isIdentifier(left.object) &&
301
+ left.object.name === objectVarName &&
302
+ t.isObjectExpression(right)) {
303
+ // Infer the type of the object literal
304
+ objectElementType = this.inferObjectType(right);
305
+ if (debugEnabled)
306
+ console.log('[TypeInference] Found object literal assignment, inferred type:', objectElementType);
307
+ break;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ // Check if this function returns the object
315
+ if (objectVarName && objectElementType && t.isReturnStatement(stmt)) {
316
+ if (t.isIdentifier(stmt.argument) && stmt.argument.name === objectVarName) {
317
+ // Function returns an object whose values are of the element type
318
+ const result = {
319
+ type: 'object',
320
+ fields: new Map(), // Dictionary, not structured object
321
+ objectValueType: objectElementType
322
+ };
323
+ if (debugEnabled)
324
+ console.log('[TypeInference] Returning object building pattern type:', result);
325
+ return result;
326
+ }
327
+ }
328
+ }
329
+ if (debugEnabled && objectVarName && objectElementType) {
330
+ console.log('[TypeInference] Had object and element type but no return statement matched');
331
+ }
332
+ return null;
333
+ }
334
+ /**
335
+ * Map component spec type to JavaScript type
336
+ */
337
+ mapSpecTypeToJSType(specType) {
338
+ if (!specType)
339
+ return 'unknown';
340
+ const type = specType.toLowerCase();
341
+ if (type === 'string')
342
+ return 'string';
343
+ if (type === 'number' || type === 'int' || type === 'integer' || type === 'float' || type === 'decimal')
344
+ return 'number';
345
+ if (type === 'boolean' || type === 'bool')
346
+ return 'boolean';
347
+ if (type.startsWith('array') || type.endsWith('[]'))
348
+ return 'array';
349
+ if (type === 'object')
350
+ return 'object';
351
+ if (type === 'function')
352
+ return 'function';
353
+ return 'unknown';
354
+ }
355
+ /**
356
+ * Infer types from object destructuring
357
+ */
358
+ inferDestructuringTypes(pattern, init, path) {
359
+ const sourceType = this.inferExpressionType(init, path);
360
+ for (const prop of pattern.properties) {
361
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
362
+ const propName = prop.key.name;
363
+ const varName = t.isIdentifier(prop.value) ? prop.value.name : propName;
364
+ // Try to get the property type from the source
365
+ if (sourceType.fields?.has(propName)) {
366
+ const fieldType = sourceType.fields.get(propName);
367
+ this.typeContext.setVariableType(varName, { type: fieldType.type, nullable: fieldType.nullable });
368
+ }
369
+ else {
370
+ this.typeContext.setVariableType(varName, type_context_1.StandardTypes.unknown);
371
+ }
372
+ }
373
+ else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
374
+ // Rest element: const { a, ...rest } = obj
375
+ this.typeContext.setVariableType(prop.argument.name, { type: 'object' });
376
+ }
377
+ }
378
+ }
379
+ /**
380
+ * Infer types from array destructuring
381
+ */
382
+ inferArrayDestructuringTypes(pattern, init, path) {
383
+ const sourceType = this.inferExpressionType(init, path);
384
+ for (let i = 0; i < pattern.elements.length; i++) {
385
+ const element = pattern.elements[i];
386
+ if (t.isIdentifier(element)) {
387
+ // Get element type from array
388
+ if (sourceType.arrayElementType) {
389
+ this.typeContext.setVariableType(element.name, sourceType.arrayElementType);
390
+ }
391
+ else {
392
+ this.typeContext.setVariableType(element.name, type_context_1.StandardTypes.unknown);
393
+ }
394
+ }
395
+ else if (t.isRestElement(element) && t.isIdentifier(element.argument)) {
396
+ // Rest element: const [a, ...rest] = arr
397
+ this.typeContext.setVariableType(element.argument.name, {
398
+ type: 'array',
399
+ arrayElementType: sourceType.arrayElementType
400
+ });
401
+ }
402
+ }
403
+ }
404
+ /**
405
+ * Infer the type of an expression
406
+ */
407
+ inferExpressionType(node, path) {
408
+ // Literals
409
+ if (t.isStringLiteral(node) || t.isTemplateLiteral(node)) {
410
+ return type_context_1.StandardTypes.string;
411
+ }
412
+ if (t.isNumericLiteral(node)) {
413
+ return type_context_1.StandardTypes.number;
414
+ }
415
+ if (t.isBooleanLiteral(node)) {
416
+ return type_context_1.StandardTypes.boolean;
417
+ }
418
+ if (t.isNullLiteral(node)) {
419
+ return type_context_1.StandardTypes.null;
420
+ }
421
+ if (t.isArrayExpression(node)) {
422
+ return this.inferArrayType(node, path);
423
+ }
424
+ if (t.isObjectExpression(node)) {
425
+ return this.inferObjectType(node, path);
426
+ }
427
+ // Identifiers (variable references)
428
+ if (t.isIdentifier(node)) {
429
+ return this.typeContext.getVariableType(node.name) || type_context_1.StandardTypes.unknown;
430
+ }
431
+ // Function expressions
432
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
433
+ return type_context_1.StandardTypes.function;
434
+ }
435
+ // Call expressions
436
+ if (t.isCallExpression(node)) {
437
+ return this.inferCallExpressionType(node, path);
438
+ }
439
+ // New expressions (constructor calls like new Date())
440
+ if (t.isNewExpression(node)) {
441
+ return this.inferNewExpressionType(node, path);
442
+ }
443
+ // Member expressions
444
+ if (t.isMemberExpression(node)) {
445
+ return this.inferMemberExpressionType(node, path);
446
+ }
447
+ // Binary expressions
448
+ if (t.isBinaryExpression(node)) {
449
+ return this.inferBinaryExpressionType(node, path);
450
+ }
451
+ // Unary expressions
452
+ if (t.isUnaryExpression(node)) {
453
+ return this.inferUnaryExpressionType(node);
454
+ }
455
+ // Conditional (ternary) expressions
456
+ if (t.isConditionalExpression(node)) {
457
+ // Return the type of the consequent (or alternate if different, return unknown)
458
+ const consequentType = this.inferExpressionType(node.consequent, path);
459
+ const alternateType = this.inferExpressionType(node.alternate, path);
460
+ if ((0, type_context_1.areTypesCompatible)(consequentType, alternateType)) {
461
+ return consequentType;
462
+ }
463
+ return type_context_1.StandardTypes.unknown;
464
+ }
465
+ // Logical expressions (&&, ||, ??)
466
+ if (t.isLogicalExpression(node)) {
467
+ // For ||, the result is the first truthy value
468
+ // For &&, the result is the first falsy or last value
469
+ // For ??, the result is the first non-nullish value
470
+ return this.inferExpressionType(node.right, path);
471
+ }
472
+ // Await expressions
473
+ if (t.isAwaitExpression(node)) {
474
+ return this.inferExpressionType(node.argument, path);
475
+ }
476
+ return type_context_1.StandardTypes.unknown;
477
+ }
478
+ /**
479
+ * Infer type for array expressions
480
+ */
481
+ inferArrayType(node, path) {
482
+ if (node.elements.length === 0) {
483
+ return { type: 'array', arrayElementType: type_context_1.StandardTypes.unknown };
484
+ }
485
+ // Try to infer element type from first element
486
+ const firstElement = node.elements[0];
487
+ if (firstElement && !t.isSpreadElement(firstElement)) {
488
+ const elementType = this.inferExpressionType(firstElement, path);
489
+ return { type: 'array', arrayElementType: elementType };
490
+ }
491
+ return { type: 'array', arrayElementType: type_context_1.StandardTypes.unknown };
492
+ }
493
+ /**
494
+ * Infer type for object expressions
495
+ */
496
+ inferObjectType(node, path) {
497
+ const fields = new Map();
498
+ for (const prop of node.properties) {
499
+ // Handle spread elements: { ...otherObject, newProp: value }
500
+ if (t.isSpreadElement(prop)) {
501
+ const spreadType = this.inferExpressionType(prop.argument, path);
502
+ // If spreading an object, merge its fields into our fields map
503
+ if (spreadType.type === 'object' && spreadType.fields) {
504
+ for (const [fieldName, fieldInfo] of spreadType.fields.entries()) {
505
+ // Only add if not already present (later properties override spread)
506
+ if (!fields.has(fieldName)) {
507
+ fields.set(fieldName, fieldInfo);
508
+ }
509
+ }
510
+ }
511
+ }
512
+ // Handle regular object properties
513
+ else if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
514
+ const propName = prop.key.name;
515
+ const propType = this.inferExpressionType(prop.value, path);
516
+ fields.set(propName, {
517
+ type: propType.type,
518
+ fromMetadata: false,
519
+ nullable: propType.nullable
520
+ });
521
+ }
522
+ }
523
+ return { type: 'object', fields };
524
+ }
525
+ /**
526
+ * Infer type for call expressions
527
+ */
528
+ inferCallExpressionType(node, path) {
529
+ // Check for user-defined function calls
530
+ if (t.isIdentifier(node.callee)) {
531
+ const functionName = node.callee.name;
532
+ // Check if we've tracked this function's return type
533
+ const returnType = this.functionReturnTypes.get(functionName);
534
+ if (returnType) {
535
+ return returnType;
536
+ }
537
+ // Check for React hooks that return computed values
538
+ // useMemo(() => { return {...} }, [deps]) - returns the memoized value
539
+ if (functionName === 'useMemo' && node.arguments.length > 0) {
540
+ const callback = node.arguments[0];
541
+ // Extract the return type from the callback
542
+ if (t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback)) {
543
+ const body = callback.body;
544
+ // Arrow function with expression body: useMemo(() => ({...}))
545
+ if (t.isExpression(body)) {
546
+ return this.inferExpressionType(body, path);
547
+ }
548
+ // Arrow function with block body: useMemo(() => { return {...} })
549
+ if (t.isBlockStatement(body)) {
550
+ // Find return statement
551
+ for (const stmt of body.body) {
552
+ if (t.isReturnStatement(stmt) && stmt.argument) {
553
+ return this.inferExpressionType(stmt.argument, path);
554
+ }
555
+ }
556
+ }
557
+ }
558
+ }
559
+ // useCallback returns a function, but we don't track function return types
560
+ // Other hooks (useState, useEffect, etc.) have specific return types we could handle
561
+ }
562
+ // Check for RunView/RunQuery calls
563
+ if (t.isMemberExpression(node.callee)) {
564
+ const calleeObj = node.callee.object;
565
+ const calleeProp = node.callee.property;
566
+ // utilities.rv.RunView or utilities.rv.RunViews
567
+ if (t.isMemberExpression(calleeObj) &&
568
+ t.isIdentifier(calleeObj.property) &&
569
+ t.isIdentifier(calleeProp)) {
570
+ const serviceName = calleeObj.property.name;
571
+ const methodName = calleeProp.name;
572
+ if (serviceName === 'rv' && (methodName === 'RunView' || methodName === 'RunViews')) {
573
+ return this.inferRunViewResultType(node);
574
+ }
575
+ if (serviceName === 'rq' && methodName === 'RunQuery') {
576
+ return this.inferRunQueryResultType(node);
577
+ }
578
+ }
579
+ }
580
+ // Object.values() - returns array of object's values
581
+ if (t.isMemberExpression(node.callee) &&
582
+ t.isIdentifier(node.callee.object) &&
583
+ node.callee.object.name === 'Object' &&
584
+ t.isIdentifier(node.callee.property) &&
585
+ node.callee.property.name === 'values' &&
586
+ node.arguments.length === 1) {
587
+ const objectType = this.inferExpressionType(node.arguments[0], path);
588
+ if (objectType.type === 'object' && objectType.objectValueType) {
589
+ // Return array of the object's value type
590
+ return {
591
+ type: 'array',
592
+ arrayElementType: objectType.objectValueType
593
+ };
594
+ }
595
+ // Fallback: unknown array
596
+ return { type: 'array', arrayElementType: type_context_1.StandardTypes.unknown };
597
+ }
598
+ // Array methods that return arrays
599
+ if (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property)) {
600
+ const methodName = node.callee.property.name;
601
+ const arrayMethods = ['filter', 'map', 'slice', 'concat', 'flat', 'flatMap', 'sort', 'reverse'];
602
+ if (arrayMethods.includes(methodName)) {
603
+ const arrayType = this.inferExpressionType(node.callee.object, path);
604
+ if (arrayType.type === 'array') {
605
+ // For map, the element type might change
606
+ if (methodName === 'map') {
607
+ return { type: 'array', arrayElementType: type_context_1.StandardTypes.unknown };
608
+ }
609
+ return arrayType;
610
+ }
611
+ }
612
+ // Array methods that return elements
613
+ if (methodName === 'find') {
614
+ const arrayType = this.inferExpressionType(node.callee.object, path);
615
+ if (arrayType.arrayElementType) {
616
+ return { ...arrayType.arrayElementType, nullable: true };
617
+ }
618
+ }
619
+ // Methods that return numbers
620
+ if (['indexOf', 'findIndex', 'length'].includes(methodName)) {
621
+ return type_context_1.StandardTypes.number;
622
+ }
623
+ // Methods that return booleans
624
+ if (['includes', 'some', 'every'].includes(methodName)) {
625
+ return type_context_1.StandardTypes.boolean;
626
+ }
627
+ }
628
+ return type_context_1.StandardTypes.unknown;
629
+ }
630
+ /**
631
+ * Infer type for new expressions (constructor calls)
632
+ */
633
+ inferNewExpressionType(node, path) {
634
+ // Check for common constructors
635
+ if (t.isIdentifier(node.callee)) {
636
+ const constructorName = node.callee.name;
637
+ // Date constructor
638
+ if (constructorName === 'Date') {
639
+ return { type: 'Date', fromMetadata: false };
640
+ }
641
+ // Array constructor
642
+ if (constructorName === 'Array') {
643
+ return { type: 'array', arrayElementType: type_context_1.StandardTypes.unknown };
644
+ }
645
+ // Object constructor
646
+ if (constructorName === 'Object') {
647
+ return { type: 'object', fields: new Map() };
648
+ }
649
+ // Map constructor
650
+ if (constructorName === 'Map') {
651
+ return { type: 'Map', fromMetadata: false };
652
+ }
653
+ // Set constructor
654
+ if (constructorName === 'Set') {
655
+ return { type: 'Set', fromMetadata: false };
656
+ }
657
+ // RegExp constructor
658
+ if (constructorName === 'RegExp') {
659
+ return { type: 'RegExp', fromMetadata: false };
660
+ }
661
+ // Error and related constructors
662
+ if (['Error', 'TypeError', 'RangeError', 'ReferenceError', 'SyntaxError'].includes(constructorName)) {
663
+ return { type: 'Error', fromMetadata: false };
664
+ }
665
+ }
666
+ return type_context_1.StandardTypes.unknown;
667
+ }
668
+ /**
669
+ * Infer type for RunView result
670
+ */
671
+ inferRunViewResultType(node) {
672
+ // Try to extract EntityName from the arguments
673
+ let entityName;
674
+ if (node.arguments.length > 0 && t.isObjectExpression(node.arguments[0])) {
675
+ for (const prop of node.arguments[0].properties) {
676
+ if (t.isObjectProperty(prop) &&
677
+ t.isIdentifier(prop.key) &&
678
+ prop.key.name === 'EntityName' &&
679
+ t.isStringLiteral(prop.value)) {
680
+ entityName = prop.value.value;
681
+ break;
682
+ }
683
+ }
684
+ }
685
+ return this.typeContext.createRunViewResultType(entityName || 'unknown');
686
+ }
687
+ /**
688
+ * Infer type for RunQuery result
689
+ */
690
+ inferRunQueryResultType(node) {
691
+ // Try to extract QueryName from the arguments
692
+ let queryName;
693
+ if (node.arguments.length > 0 && t.isObjectExpression(node.arguments[0])) {
694
+ for (const prop of node.arguments[0].properties) {
695
+ if (t.isObjectProperty(prop) &&
696
+ t.isIdentifier(prop.key) &&
697
+ prop.key.name === 'QueryName' &&
698
+ t.isStringLiteral(prop.value)) {
699
+ queryName = prop.value.value;
700
+ break;
701
+ }
702
+ }
703
+ }
704
+ return this.typeContext.createRunQueryResultType(queryName || 'unknown');
705
+ }
706
+ /**
707
+ * Infer type for member expressions
708
+ */
709
+ inferMemberExpressionType(node, path) {
710
+ const objectType = this.inferExpressionType(node.object, path);
711
+ // Array index access
712
+ if (node.computed && t.isNumericLiteral(node.property)) {
713
+ if (objectType.arrayElementType) {
714
+ return objectType.arrayElementType;
715
+ }
716
+ }
717
+ // Property access
718
+ if (t.isIdentifier(node.property)) {
719
+ const propName = node.property.name;
720
+ // Check if the object has known fields
721
+ if (objectType.fields?.has(propName)) {
722
+ const field = objectType.fields.get(propName);
723
+ return { type: field.type, nullable: field.nullable };
724
+ }
725
+ // Special case: Results property on RunView/RunQuery result
726
+ if (propName === 'Results' && objectType.type === 'object') {
727
+ // This should be an array of entity/query rows
728
+ if (objectType.entityName) {
729
+ return {
730
+ type: 'array',
731
+ arrayElementType: {
732
+ type: 'entity-row',
733
+ entityName: objectType.entityName
734
+ }
735
+ };
736
+ }
737
+ if (objectType.queryName) {
738
+ return {
739
+ type: 'array',
740
+ arrayElementType: {
741
+ type: 'query-row',
742
+ queryName: objectType.queryName
743
+ }
744
+ };
745
+ }
746
+ }
747
+ // Array length
748
+ if (propName === 'length' && objectType.type === 'array') {
749
+ return type_context_1.StandardTypes.number;
750
+ }
751
+ }
752
+ return type_context_1.StandardTypes.unknown;
753
+ }
754
+ /**
755
+ * Infer type for binary expressions
756
+ */
757
+ inferBinaryExpressionType(node, path) {
758
+ const operator = node.operator;
759
+ // Comparison operators always return boolean
760
+ if (['==', '!=', '===', '!==', '<', '<=', '>', '>=', 'in', 'instanceof'].includes(operator)) {
761
+ return type_context_1.StandardTypes.boolean;
762
+ }
763
+ // Arithmetic operators return number
764
+ if (['-', '*', '/', '%', '**', '|', '&', '^', '<<', '>>', '>>>'].includes(operator)) {
765
+ return type_context_1.StandardTypes.number;
766
+ }
767
+ // + can be string or number
768
+ if (operator === '+') {
769
+ // node.left can be PrivateName in some cases, skip type inference for those
770
+ if (t.isPrivateName(node.left)) {
771
+ return type_context_1.StandardTypes.unknown;
772
+ }
773
+ const leftType = this.inferExpressionType(node.left, path);
774
+ const rightType = this.inferExpressionType(node.right, path);
775
+ if (leftType.type === 'string' || rightType.type === 'string') {
776
+ return type_context_1.StandardTypes.string;
777
+ }
778
+ if (leftType.type === 'number' && rightType.type === 'number') {
779
+ return type_context_1.StandardTypes.number;
780
+ }
781
+ }
782
+ return type_context_1.StandardTypes.unknown;
783
+ }
784
+ /**
785
+ * Infer type for unary expressions
786
+ */
787
+ inferUnaryExpressionType(node) {
788
+ const operator = node.operator;
789
+ if (operator === '!') {
790
+ return type_context_1.StandardTypes.boolean;
791
+ }
792
+ if (operator === 'typeof') {
793
+ return type_context_1.StandardTypes.string;
794
+ }
795
+ if (['+', '-', '~'].includes(operator)) {
796
+ return type_context_1.StandardTypes.number;
797
+ }
798
+ if (operator === 'void') {
799
+ return type_context_1.StandardTypes.undefined;
800
+ }
801
+ return type_context_1.StandardTypes.unknown;
802
+ }
803
+ /**
804
+ * Get inferred type for a variable by name
805
+ */
806
+ getVariableType(name) {
807
+ return this.typeContext.getVariableType(name);
808
+ }
809
+ /**
810
+ * Check if an expression is a specific type
811
+ */
812
+ isType(node, expectedType, path) {
813
+ const actualType = this.inferExpressionType(node, path);
814
+ return actualType.type === expectedType;
815
+ }
816
+ }
817
+ exports.TypeInferenceEngine = TypeInferenceEngine;
818
+ /**
819
+ * Convenience function to analyze an AST and return type context
820
+ */
821
+ async function analyzeTypes(ast, componentSpec, contextUser) {
822
+ const engine = new TypeInferenceEngine(componentSpec, contextUser);
823
+ return engine.analyze(ast);
824
+ }
825
+ exports.analyzeTypes = analyzeTypes;
826
+ //# sourceMappingURL=type-inference-engine.js.map