@memberjunction/react-test-harness 2.121.0 → 2.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/component-linter.d.ts +5 -0
- package/dist/lib/component-linter.d.ts.map +1 -1
- package/dist/lib/component-linter.js +2441 -2270
- package/dist/lib/component-linter.js.map +1 -1
- package/dist/lib/control-flow-analyzer.d.ts +184 -0
- package/dist/lib/control-flow-analyzer.d.ts.map +1 -0
- package/dist/lib/control-flow-analyzer.js +825 -0
- package/dist/lib/control-flow-analyzer.js.map +1 -0
- package/dist/lib/type-context.d.ts +182 -0
- package/dist/lib/type-context.d.ts.map +1 -0
- package/dist/lib/type-context.js +394 -0
- package/dist/lib/type-context.js.map +1 -0
- package/dist/lib/type-inference-engine.d.ts +148 -0
- package/dist/lib/type-inference-engine.d.ts.map +1 -0
- package/dist/lib/type-inference-engine.js +826 -0
- package/dist/lib/type-inference-engine.js.map +1 -0
- package/package.json +4 -4
|
@@ -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
|