kimchilang 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/workflows/ci.yml +66 -0
  2. package/README.md +1547 -0
  3. package/create-kimchi-app/README.md +44 -0
  4. package/create-kimchi-app/index.js +214 -0
  5. package/create-kimchi-app/package.json +22 -0
  6. package/editors/README.md +121 -0
  7. package/editors/sublime/KimchiLang.sublime-syntax +138 -0
  8. package/editors/vscode/README.md +90 -0
  9. package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
  10. package/editors/vscode/language-configuration.json +37 -0
  11. package/editors/vscode/package.json +55 -0
  12. package/editors/vscode/src/extension.js +354 -0
  13. package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
  14. package/examples/api/client.km +36 -0
  15. package/examples/async_pipe.km +58 -0
  16. package/examples/basic.kimchi +109 -0
  17. package/examples/cli_framework/README.md +92 -0
  18. package/examples/cli_framework/calculator.km +61 -0
  19. package/examples/cli_framework/deploy.km +126 -0
  20. package/examples/cli_framework/greeter.km +26 -0
  21. package/examples/config.static +27 -0
  22. package/examples/config.static.js +10 -0
  23. package/examples/env_test.km +37 -0
  24. package/examples/fibonacci.kimchi +17 -0
  25. package/examples/greeter.km +15 -0
  26. package/examples/hello.js +1 -0
  27. package/examples/hello.kimchi +3 -0
  28. package/examples/js_interop.km +42 -0
  29. package/examples/logger_example.km +34 -0
  30. package/examples/memo_fibonacci.km +17 -0
  31. package/examples/myapp/lib/http.js +14 -0
  32. package/examples/myapp/lib/http.km +16 -0
  33. package/examples/myapp/main.km +16 -0
  34. package/examples/myapp/main_with_mock.km +42 -0
  35. package/examples/myapp/services/api.js +18 -0
  36. package/examples/myapp/services/api.km +18 -0
  37. package/examples/new_features.kimchi +52 -0
  38. package/examples/project_example.static +20 -0
  39. package/examples/readme_examples.km +240 -0
  40. package/examples/reduce_pattern_match.km +85 -0
  41. package/examples/regex_match.km +46 -0
  42. package/examples/sample.js +45 -0
  43. package/examples/sample.km +39 -0
  44. package/examples/secrets.static +35 -0
  45. package/examples/secrets.static.js +30 -0
  46. package/examples/shell-example.mjs +144 -0
  47. package/examples/shell_example.km +19 -0
  48. package/examples/stdlib_test.km +22 -0
  49. package/examples/test_example.km +69 -0
  50. package/examples/testing/README.md +88 -0
  51. package/examples/testing/http_client.km +18 -0
  52. package/examples/testing/math.km +48 -0
  53. package/examples/testing/math.test.km +93 -0
  54. package/examples/testing/user_service.km +29 -0
  55. package/examples/testing/user_service.test.km +72 -0
  56. package/examples/use-config.mjs +141 -0
  57. package/examples/use_config.km +13 -0
  58. package/install.sh +59 -0
  59. package/package.json +29 -0
  60. package/pantry/acorn/index.km +1 -0
  61. package/pantry/is_number/index.km +1 -0
  62. package/pantry/is_odd/index.km +2 -0
  63. package/project.static +6 -0
  64. package/src/cli.js +1245 -0
  65. package/src/generator.js +1241 -0
  66. package/src/index.js +141 -0
  67. package/src/js2km.js +568 -0
  68. package/src/lexer.js +822 -0
  69. package/src/linter.js +810 -0
  70. package/src/package-manager.js +307 -0
  71. package/src/parser.js +1876 -0
  72. package/src/static-parser.js +500 -0
  73. package/src/typechecker.js +950 -0
  74. package/stdlib/array.km +0 -0
  75. package/stdlib/bitwise.km +38 -0
  76. package/stdlib/console.km +49 -0
  77. package/stdlib/date.km +97 -0
  78. package/stdlib/function.km +44 -0
  79. package/stdlib/http.km +197 -0
  80. package/stdlib/http.md +333 -0
  81. package/stdlib/index.km +26 -0
  82. package/stdlib/json.km +17 -0
  83. package/stdlib/logger.js +114 -0
  84. package/stdlib/logger.km +104 -0
  85. package/stdlib/math.km +120 -0
  86. package/stdlib/object.km +41 -0
  87. package/stdlib/promise.km +33 -0
  88. package/stdlib/string.km +93 -0
  89. package/stdlib/testing.md +265 -0
  90. package/test/test.js +599 -0
@@ -0,0 +1,950 @@
1
+ import { NodeType } from './parser.js';
2
+
3
+ // Type definitions
4
+ export const Type = {
5
+ Unknown: 'unknown',
6
+ Any: 'any',
7
+ Number: 'number',
8
+ String: 'string',
9
+ Boolean: 'boolean',
10
+ Null: 'null',
11
+ Array: 'array',
12
+ Object: 'object',
13
+ Function: 'function',
14
+ Void: 'void',
15
+ Enum: 'enum',
16
+ Module: 'module',
17
+ };
18
+
19
+ // Global module type registry - stores the exported interface of each module
20
+ const moduleTypeRegistry = new Map();
21
+
22
+ class TypeError extends Error {
23
+ constructor(message, node) {
24
+ const line = node?.line || 1;
25
+ const column = node?.column || 1;
26
+ super(`Type Error at ${line}:${column}: ${message}`);
27
+ this.name = 'TypeError';
28
+ this.node = node;
29
+ this.line = line;
30
+ this.column = column;
31
+ }
32
+ }
33
+
34
+ export class TypeChecker {
35
+ constructor(options = {}) {
36
+ this.scopes = [new Map()]; // Stack of scopes
37
+ this.errors = [];
38
+ this.functions = new Map(); // Function signatures
39
+ this.enums = new Map(); // Enum definitions
40
+ this.modulePath = options.modulePath || null;
41
+ this.moduleExports = {}; // Track exposed declarations for this module
42
+ this.argTypes = new Map(); // Track arg declaration types
43
+ }
44
+
45
+ // Static methods for module type registry
46
+ static registerModuleType(modulePath, exportType) {
47
+ moduleTypeRegistry.set(modulePath, exportType);
48
+ }
49
+
50
+ static getModuleType(modulePath) {
51
+ return moduleTypeRegistry.get(modulePath) || null;
52
+ }
53
+
54
+ static clearRegistry() {
55
+ moduleTypeRegistry.clear();
56
+ }
57
+
58
+ check(ast) {
59
+ this.errors = [];
60
+ this.visitProgram(ast);
61
+
62
+ // Register this module's export type if we have a module path
63
+ if (this.modulePath && Object.keys(this.moduleExports).length > 0) {
64
+ TypeChecker.registerModuleType(this.modulePath, this.createObjectType(this.moduleExports));
65
+ }
66
+
67
+ return this.errors;
68
+ }
69
+
70
+ // Scope management
71
+ pushScope() {
72
+ this.scopes.push(new Map());
73
+ }
74
+
75
+ popScope() {
76
+ this.scopes.pop();
77
+ }
78
+
79
+ defineVariable(name, typeInfo) {
80
+ this.scopes[this.scopes.length - 1].set(name, typeInfo);
81
+ }
82
+
83
+ lookupVariable(name) {
84
+ for (let i = this.scopes.length - 1; i >= 0; i--) {
85
+ if (this.scopes[i].has(name)) {
86
+ return this.scopes[i].get(name);
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ addError(message, node) {
93
+ this.errors.push(new TypeError(message, node));
94
+ }
95
+
96
+ // Type utilities
97
+ createType(kind, properties = {}) {
98
+ return { kind, ...properties };
99
+ }
100
+
101
+ createObjectType(properties) {
102
+ return { kind: Type.Object, properties };
103
+ }
104
+
105
+ createArrayType(elementType) {
106
+ return { kind: Type.Array, elementType };
107
+ }
108
+
109
+ createFunctionType(params, returnType) {
110
+ return { kind: Type.Function, params, returnType };
111
+ }
112
+
113
+ typeToString(type) {
114
+ if (!type) return 'unknown';
115
+ if (typeof type === 'string') return type;
116
+ if (type.kind === Type.Array) {
117
+ return `${this.typeToString(type.elementType)}[]`;
118
+ }
119
+ if (type.kind === Type.Object && type.properties) {
120
+ const props = Object.entries(type.properties)
121
+ .map(([k, v]) => `${k}: ${this.typeToString(v)}`)
122
+ .join(', ');
123
+ return `{ ${props} }`;
124
+ }
125
+ if (type.kind === Type.Function) {
126
+ return `(${type.params.map(p => this.typeToString(p)).join(', ')}) => ${this.typeToString(type.returnType)}`;
127
+ }
128
+ return type.kind || 'unknown';
129
+ }
130
+
131
+ // Check if types are compatible
132
+ isCompatible(expected, actual) {
133
+ if (!expected || !actual) return true;
134
+ if (expected.kind === Type.Any || actual.kind === Type.Any) return true;
135
+ if (expected.kind === Type.Unknown || actual.kind === Type.Unknown) return true;
136
+ if (expected.kind === actual.kind) {
137
+ if (expected.kind === Type.Object) {
138
+ // Check that actual has all properties of expected
139
+ if (expected.properties && actual.properties) {
140
+ for (const [key, expectedType] of Object.entries(expected.properties)) {
141
+ if (!(key in actual.properties)) {
142
+ return false;
143
+ }
144
+ if (!this.isCompatible(expectedType, actual.properties[key])) {
145
+ return false;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ // AST Visitors
156
+ visitProgram(node) {
157
+ // First pass: collect function and enum declarations
158
+ for (const stmt of node.body) {
159
+ if (stmt.type === NodeType.FunctionDeclaration) {
160
+ this.registerFunction(stmt);
161
+ } else if (stmt.type === NodeType.EnumDeclaration) {
162
+ this.registerEnum(stmt);
163
+ }
164
+ }
165
+
166
+ // Second pass: type check all statements
167
+ for (const stmt of node.body) {
168
+ this.visitStatement(stmt);
169
+ }
170
+ }
171
+
172
+ registerFunction(node) {
173
+ const params = node.params.map(p => ({
174
+ name: p.name || p.argument,
175
+ type: this.createType(Type.Any),
176
+ }));
177
+
178
+ this.functions.set(node.name, {
179
+ params,
180
+ returnType: this.createType(Type.Unknown),
181
+ node,
182
+ });
183
+ }
184
+
185
+ registerEnum(node) {
186
+ const members = {};
187
+ for (const member of node.members) {
188
+ members[member.name] = this.createType(Type.Number);
189
+ }
190
+ this.enums.set(node.name, {
191
+ kind: Type.Enum,
192
+ name: node.name,
193
+ members,
194
+ });
195
+ // Also define the enum as a variable
196
+ this.defineVariable(node.name, this.createObjectType(members));
197
+ }
198
+
199
+ visitStatement(node) {
200
+ switch (node.type) {
201
+ case NodeType.DecDeclaration:
202
+ this.visitDecDeclaration(node);
203
+ break;
204
+ case NodeType.FunctionDeclaration:
205
+ this.visitFunctionDeclaration(node);
206
+ break;
207
+ case NodeType.EnumDeclaration:
208
+ // Already registered
209
+ break;
210
+ case NodeType.IfStatement:
211
+ this.visitIfStatement(node);
212
+ break;
213
+ case NodeType.WhileStatement:
214
+ this.visitWhileStatement(node);
215
+ break;
216
+ case NodeType.ForInStatement:
217
+ this.visitForInStatement(node);
218
+ break;
219
+ case NodeType.ReturnStatement:
220
+ this.visitReturnStatement(node);
221
+ break;
222
+ case NodeType.TryStatement:
223
+ this.visitTryStatement(node);
224
+ break;
225
+ case NodeType.ThrowStatement:
226
+ this.visitExpression(node.argument);
227
+ break;
228
+ case NodeType.PatternMatch:
229
+ this.visitPatternMatch(node);
230
+ break;
231
+ case NodeType.PrintStatement:
232
+ this.visitExpression(node.argument || node.expression);
233
+ break;
234
+ case NodeType.ExpressionStatement:
235
+ this.visitExpression(node.expression);
236
+ break;
237
+ case NodeType.BlockStatement:
238
+ this.pushScope();
239
+ for (const stmt of node.body) {
240
+ this.visitStatement(stmt);
241
+ }
242
+ this.popScope();
243
+ break;
244
+ case NodeType.DepStatement:
245
+ this.visitDepStatement(node);
246
+ break;
247
+ case NodeType.ArgDeclaration:
248
+ this.visitArgDeclaration(node);
249
+ break;
250
+ case NodeType.EnvDeclaration:
251
+ this.visitEnvDeclaration(node);
252
+ break;
253
+ case NodeType.JSBlock:
254
+ case NodeType.ShellBlock:
255
+ // JS/Shell blocks are opaque - no type checking inside
256
+ break;
257
+ case NodeType.TestBlock:
258
+ case NodeType.DescribeBlock:
259
+ case NodeType.ExpectStatement:
260
+ case NodeType.AssertStatement:
261
+ // Test constructs - visit their bodies if present
262
+ if (node.body && node.body.body) {
263
+ this.pushScope();
264
+ for (const stmt of node.body.body) {
265
+ this.visitStatement(stmt);
266
+ }
267
+ this.popScope();
268
+ }
269
+ break;
270
+ case NodeType.BreakStatement:
271
+ case NodeType.ContinueStatement:
272
+ // No type checking needed
273
+ break;
274
+ }
275
+ }
276
+
277
+ visitDepStatement(node) {
278
+ // Get the expected module type from registry
279
+ const expectedModuleType = TypeChecker.getModuleType(node.path);
280
+
281
+ // If we have overrides, validate them against the expected module interface
282
+ if (node.overrides && expectedModuleType) {
283
+ const overrideType = this.visitExpression(node.overrides);
284
+
285
+ if (overrideType && overrideType.kind === Type.Object && overrideType.properties) {
286
+ // Check each override property
287
+ for (const [key, valueType] of Object.entries(overrideType.properties)) {
288
+ // Skip dotted paths (dependency overrides like "foo.bar": mockFn)
289
+ if (key.includes('.')) continue;
290
+
291
+ // Check if this is overriding an arg or an exported member
292
+ if (expectedModuleType.properties && key in expectedModuleType.properties) {
293
+ const expectedType = expectedModuleType.properties[key];
294
+ if (!this.isCompatible(expectedType, valueType)) {
295
+ this.addError(
296
+ `Type mismatch for '${key}' in dependency '${node.path}': expected ${this.typeToString(expectedType)}, got ${this.typeToString(valueType)}`,
297
+ node
298
+ );
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ // Define the dependency alias in scope with the module's type
306
+ if (expectedModuleType) {
307
+ this.defineVariable(node.alias, expectedModuleType);
308
+ } else {
309
+ // Unknown module - use Any type
310
+ this.defineVariable(node.alias, this.createType(Type.Any));
311
+ }
312
+ }
313
+
314
+ visitArgDeclaration(node) {
315
+ let argType = this.createType(Type.Any);
316
+
317
+ // Infer type from default value if present
318
+ if (node.defaultValue) {
319
+ argType = this.visitExpression(node.defaultValue);
320
+ }
321
+
322
+ // Store arg type for module export
323
+ this.argTypes.set(node.name, {
324
+ type: argType,
325
+ required: node.required,
326
+ });
327
+
328
+ // Define the arg in scope
329
+ this.defineVariable(node.name, argType);
330
+
331
+ // Add to module exports so other modules can validate against it
332
+ this.moduleExports[node.name] = argType;
333
+ }
334
+
335
+ visitEnvDeclaration(node) {
336
+ // Env variables are always strings (or undefined if not set)
337
+ const envType = this.createType(Type.String);
338
+
339
+ // Define the env variable in scope
340
+ this.defineVariable(node.name, envType);
341
+ }
342
+
343
+ visitDecDeclaration(node) {
344
+ const initType = this.visitExpression(node.init);
345
+
346
+ // Track exposed declarations for module export type
347
+ if (node.exposed) {
348
+ this.moduleExports[node.name] = initType;
349
+ }
350
+
351
+ if (node.destructuring) {
352
+ if (node.pattern.type === NodeType.ObjectPattern) {
353
+ // Object destructuring
354
+ for (const prop of node.pattern.properties) {
355
+ let propType = this.createType(Type.Unknown);
356
+ if (initType && initType.kind === Type.Object && initType.properties) {
357
+ if (prop.key in initType.properties) {
358
+ propType = initType.properties[prop.key];
359
+ } else {
360
+ this.addError(
361
+ `Property '${prop.key}' does not exist on type ${this.typeToString(initType)}`,
362
+ node
363
+ );
364
+ }
365
+ }
366
+ this.defineVariable(prop.value, propType);
367
+ }
368
+ } else if (node.pattern.type === NodeType.ArrayPattern) {
369
+ // Array destructuring
370
+ for (const elem of node.pattern.elements) {
371
+ if (elem) {
372
+ let elemType = this.createType(Type.Unknown);
373
+ if (initType && initType.kind === Type.Array && initType.elementType) {
374
+ elemType = initType.elementType;
375
+ }
376
+ this.defineVariable(elem.name, elemType);
377
+ }
378
+ }
379
+ }
380
+ } else {
381
+ this.defineVariable(node.name, initType);
382
+ }
383
+ }
384
+
385
+ visitFunctionDeclaration(node) {
386
+ // Track exposed functions for module export type
387
+ if (node.exposed) {
388
+ const fnInfo = this.functions.get(node.name);
389
+ if (fnInfo) {
390
+ this.moduleExports[node.name] = this.createFunctionType(
391
+ fnInfo.params.map(p => p.type),
392
+ fnInfo.returnType
393
+ );
394
+ }
395
+ }
396
+
397
+ this.pushScope();
398
+
399
+ // Define parameters in scope
400
+ for (const param of node.params) {
401
+ const name = param.name || param.argument;
402
+ let paramType = this.createType(Type.Any);
403
+
404
+ // Infer type from default value if present
405
+ if (param.defaultValue) {
406
+ paramType = this.visitExpression(param.defaultValue);
407
+ }
408
+
409
+ this.defineVariable(name, paramType);
410
+ }
411
+
412
+ // Visit function body
413
+ if (node.body && node.body.body) {
414
+ for (const stmt of node.body.body) {
415
+ this.visitStatement(stmt);
416
+ }
417
+ }
418
+
419
+ this.popScope();
420
+ }
421
+
422
+ visitIfStatement(node) {
423
+ this.visitExpression(node.test);
424
+
425
+ this.pushScope();
426
+ if (node.consequent.body) {
427
+ for (const stmt of node.consequent.body) {
428
+ this.visitStatement(stmt);
429
+ }
430
+ }
431
+ this.popScope();
432
+
433
+ if (node.alternate) {
434
+ this.pushScope();
435
+ if (node.alternate.body) {
436
+ for (const stmt of node.alternate.body) {
437
+ this.visitStatement(stmt);
438
+ }
439
+ } else {
440
+ this.visitStatement(node.alternate);
441
+ }
442
+ this.popScope();
443
+ }
444
+ }
445
+
446
+ visitWhileStatement(node) {
447
+ this.visitExpression(node.test);
448
+ this.pushScope();
449
+ if (node.body.body) {
450
+ for (const stmt of node.body.body) {
451
+ this.visitStatement(stmt);
452
+ }
453
+ }
454
+ this.popScope();
455
+ }
456
+
457
+ visitForInStatement(node) {
458
+ this.pushScope();
459
+ const iterableType = this.visitExpression(node.iterable);
460
+
461
+ // Infer loop variable type from iterable
462
+ let elemType = this.createType(Type.Any);
463
+ if (iterableType) {
464
+ if (iterableType.kind === Type.Array && iterableType.elementType) {
465
+ elemType = iterableType.elementType;
466
+ } else if (iterableType.kind === Type.String) {
467
+ elemType = this.createType(Type.String);
468
+ }
469
+ }
470
+
471
+ this.defineVariable(node.variable, elemType);
472
+
473
+ if (node.body.body) {
474
+ for (const stmt of node.body.body) {
475
+ this.visitStatement(stmt);
476
+ }
477
+ }
478
+ this.popScope();
479
+ }
480
+
481
+ visitReturnStatement(node) {
482
+ if (node.argument) {
483
+ this.visitExpression(node.argument);
484
+ }
485
+ }
486
+
487
+ visitTryStatement(node) {
488
+ this.pushScope();
489
+ if (node.block.body) {
490
+ for (const stmt of node.block.body) {
491
+ this.visitStatement(stmt);
492
+ }
493
+ }
494
+ this.popScope();
495
+
496
+ if (node.handler) {
497
+ this.pushScope();
498
+ if (node.handler.param) {
499
+ this.defineVariable(node.handler.param, this.createType(Type.Object));
500
+ }
501
+ if (node.handler.body.body) {
502
+ for (const stmt of node.handler.body.body) {
503
+ this.visitStatement(stmt);
504
+ }
505
+ }
506
+ this.popScope();
507
+ }
508
+
509
+ if (node.finalizer) {
510
+ this.pushScope();
511
+ if (node.finalizer.body) {
512
+ for (const stmt of node.finalizer.body) {
513
+ this.visitStatement(stmt);
514
+ }
515
+ }
516
+ this.popScope();
517
+ }
518
+ }
519
+
520
+ visitPatternMatch(node) {
521
+ for (const matchCase of node.cases) {
522
+ this.visitExpression(matchCase.test);
523
+ if (matchCase.consequent.body) {
524
+ this.pushScope();
525
+ for (const stmt of matchCase.consequent.body) {
526
+ this.visitStatement(stmt);
527
+ }
528
+ this.popScope();
529
+ } else {
530
+ this.visitStatement(matchCase.consequent);
531
+ }
532
+ }
533
+ }
534
+
535
+ visitExpression(node) {
536
+ if (!node) return this.createType(Type.Unknown);
537
+
538
+ switch (node.type) {
539
+ case NodeType.Literal:
540
+ return this.visitLiteral(node);
541
+ case NodeType.Identifier:
542
+ return this.visitIdentifier(node);
543
+ case NodeType.BinaryExpression:
544
+ return this.visitBinaryExpression(node);
545
+ case NodeType.UnaryExpression:
546
+ return this.visitUnaryExpression(node);
547
+ case NodeType.CallExpression:
548
+ return this.visitCallExpression(node);
549
+ case NodeType.MemberExpression:
550
+ return this.visitMemberExpression(node);
551
+ case NodeType.ArrayExpression:
552
+ return this.visitArrayExpression(node);
553
+ case NodeType.ObjectExpression:
554
+ return this.visitObjectExpression(node);
555
+ case NodeType.ArrowFunctionExpression:
556
+ return this.visitArrowFunctionExpression(node);
557
+ case NodeType.ConditionalExpression:
558
+ return this.visitConditionalExpression(node);
559
+ case NodeType.AssignmentExpression:
560
+ return this.visitAssignmentExpression(node);
561
+ case NodeType.AwaitExpression:
562
+ return this.visitExpression(node.argument);
563
+ case NodeType.SpreadElement:
564
+ return this.visitExpression(node.argument);
565
+ case NodeType.RangeExpression:
566
+ return this.createArrayType(this.createType(Type.Number));
567
+ case NodeType.FlowExpression:
568
+ return this.visitFlowExpression(node);
569
+ case NodeType.RegexLiteral:
570
+ return this.createType(Type.Object); // RegExp is an object type
571
+ case NodeType.MatchExpression:
572
+ return this.visitMatchExpression(node);
573
+ default:
574
+ return this.createType(Type.Unknown);
575
+ }
576
+ }
577
+
578
+ visitLiteral(node) {
579
+ if (node.isNumber) return this.createType(Type.Number);
580
+ if (node.isString) return this.createType(Type.String);
581
+ if (typeof node.value === 'boolean') return this.createType(Type.Boolean);
582
+ if (node.value === null) return this.createType(Type.Null);
583
+ if (typeof node.value === 'number') return this.createType(Type.Number);
584
+ if (typeof node.value === 'string') return this.createType(Type.String);
585
+ return this.createType(Type.Unknown);
586
+ }
587
+
588
+ visitIdentifier(node) {
589
+ const varType = this.lookupVariable(node.name);
590
+ if (varType) return varType;
591
+
592
+ // Check if it's a function
593
+ if (this.functions.has(node.name)) {
594
+ const fn = this.functions.get(node.name);
595
+ return this.createFunctionType(fn.params.map(p => p.type), fn.returnType);
596
+ }
597
+
598
+ // Check if it's an enum
599
+ if (this.enums.has(node.name)) {
600
+ return this.enums.get(node.name);
601
+ }
602
+
603
+ // Built-in globals
604
+ const builtins = [
605
+ 'console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean',
606
+ 'Date', 'Promise', 'fetch', 'setTimeout', 'setInterval', 'clearTimeout',
607
+ 'clearInterval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'encodeURI',
608
+ 'decodeURI', 'encodeURIComponent', 'decodeURIComponent', 'Error', 'TypeError',
609
+ 'RangeError', 'SyntaxError', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet',
610
+ 'Symbol', 'Proxy', 'Reflect', 'Intl', 'undefined', 'null', 'NaN', 'Infinity',
611
+ 'globalThis', 'process', 'Buffer', 'require', 'module', 'exports', '__dirname',
612
+ '__filename', '_pipe', '_range', '_deepFreeze', 'true', 'false'
613
+ ];
614
+ if (builtins.includes(node.name)) {
615
+ return this.createType(Type.Any);
616
+ }
617
+
618
+ // Report undefined identifier error
619
+ this.addError(`Undefined identifier '${node.name}'`, node);
620
+
621
+ return this.createType(Type.Unknown);
622
+ }
623
+
624
+ visitBinaryExpression(node) {
625
+ const leftType = this.visitExpression(node.left);
626
+ const rightType = this.visitExpression(node.right);
627
+
628
+ const op = node.operator;
629
+
630
+ // Arithmetic operators
631
+ if (['+', '-', '*', '/', '%', '**'].includes(op)) {
632
+ // String concatenation
633
+ if (op === '+' && (leftType.kind === Type.String || rightType.kind === Type.String)) {
634
+ return this.createType(Type.String);
635
+ }
636
+ return this.createType(Type.Number);
637
+ }
638
+
639
+ // Comparison operators
640
+ if (['===', '!==', '==', '!=', '<', '>', '<=', '>=', 'is', 'is not'].includes(op)) {
641
+ return this.createType(Type.Boolean);
642
+ }
643
+
644
+ // Logical operators
645
+ if (['&&', '||'].includes(op)) {
646
+ return this.createType(Type.Boolean);
647
+ }
648
+
649
+ // Bitwise operators
650
+ if (['&', '|', '^', '<<', '>>'].includes(op)) {
651
+ return this.createType(Type.Number);
652
+ }
653
+
654
+ return this.createType(Type.Unknown);
655
+ }
656
+
657
+ visitUnaryExpression(node) {
658
+ this.visitExpression(node.argument);
659
+
660
+ if (node.operator === '!' || node.operator === 'not') {
661
+ return this.createType(Type.Boolean);
662
+ }
663
+ if (node.operator === '-' || node.operator === '~') {
664
+ return this.createType(Type.Number);
665
+ }
666
+
667
+ return this.createType(Type.Unknown);
668
+ }
669
+
670
+ visitCallExpression(node) {
671
+ const calleeType = this.visitExpression(node.callee);
672
+
673
+ // Check if callee is callable
674
+ if (calleeType && calleeType.kind !== Type.Function && calleeType.kind !== Type.Any && calleeType.kind !== Type.Unknown) {
675
+ this.addError(
676
+ `Type '${this.typeToString(calleeType)}' is not callable`,
677
+ node
678
+ );
679
+ }
680
+
681
+ // Visit arguments
682
+ for (const arg of node.arguments) {
683
+ this.visitExpression(arg);
684
+ }
685
+
686
+ // Return the function's return type if known
687
+ if (calleeType && calleeType.kind === Type.Function && calleeType.returnType) {
688
+ return calleeType.returnType;
689
+ }
690
+
691
+ // Check for known function calls
692
+ if (node.callee.type === NodeType.Identifier) {
693
+ const fnInfo = this.functions.get(node.callee.name);
694
+ if (fnInfo) {
695
+ return fnInfo.returnType;
696
+ }
697
+ }
698
+
699
+ // Handle method calls on known types
700
+ if (node.callee.type === NodeType.MemberExpression) {
701
+ const objType = this.visitExpression(node.callee.object);
702
+ const prop = node.callee.computed ? null : node.callee.property.name;
703
+
704
+ // Array methods
705
+ if (objType && objType.kind === Type.Array) {
706
+ if (['map', 'filter', 'find', 'some', 'every'].includes(prop)) {
707
+ if (prop === 'map') {
708
+ return this.createArrayType(this.createType(Type.Unknown));
709
+ }
710
+ if (prop === 'filter') {
711
+ return objType;
712
+ }
713
+ if (prop === 'find') {
714
+ return objType.elementType || this.createType(Type.Unknown);
715
+ }
716
+ if (['some', 'every'].includes(prop)) {
717
+ return this.createType(Type.Boolean);
718
+ }
719
+ }
720
+ if (['reduce'].includes(prop)) {
721
+ return this.createType(Type.Unknown);
722
+ }
723
+ if (['push', 'pop', 'shift', 'unshift', 'splice'].includes(prop)) {
724
+ return this.createType(Type.Unknown);
725
+ }
726
+ if (['join'].includes(prop)) {
727
+ return this.createType(Type.String);
728
+ }
729
+ if (['length'].includes(prop)) {
730
+ return this.createType(Type.Number);
731
+ }
732
+ }
733
+
734
+ // String methods
735
+ if (objType && objType.kind === Type.String) {
736
+ if (['split', 'toChars', 'toLines'].includes(prop)) {
737
+ return this.createArrayType(this.createType(Type.String));
738
+ }
739
+ if (['trim', 'toLowerCase', 'toUpperCase', 'slice', 'substring', 'replace', 'capitalize'].includes(prop)) {
740
+ return this.createType(Type.String);
741
+ }
742
+ if (['length', 'indexOf', 'lastIndexOf'].includes(prop)) {
743
+ return this.createType(Type.Number);
744
+ }
745
+ if (['includes', 'startsWith', 'endsWith', 'isEmpty', 'isBlank'].includes(prop)) {
746
+ return this.createType(Type.Boolean);
747
+ }
748
+ }
749
+ }
750
+
751
+ return this.createType(Type.Unknown);
752
+ }
753
+
754
+ visitMatchExpression(node) {
755
+ // Match expression: subject ~ /regex/ or subject ~ /regex/ => { body }
756
+ this.visitExpression(node.subject);
757
+ this.visitExpression(node.pattern);
758
+
759
+ if (node.body) {
760
+ // With body: return type depends on body
761
+ if (node.body.type === NodeType.BlockStatement) {
762
+ this.pushScope();
763
+ // Add $match to scope
764
+ this.defineVariable('$match', this.createArrayType(this.createType(Type.String)));
765
+ for (const stmt of node.body.body) {
766
+ this.visitStatement(stmt);
767
+ }
768
+ this.popScope();
769
+ } else {
770
+ return this.visitExpression(node.body);
771
+ }
772
+ return this.createType(Type.Unknown);
773
+ } else {
774
+ // Without body: returns string (first match) or null
775
+ return this.createType(Type.String);
776
+ }
777
+ }
778
+
779
+ visitMemberExpression(node) {
780
+ const objType = this.visitExpression(node.object);
781
+
782
+ // Handle computed access (array indexing)
783
+ if (node.computed) {
784
+ const indexType = this.visitExpression(node.property);
785
+
786
+ if (objType && objType.kind === Type.Array) {
787
+ return objType.elementType || this.createType(Type.Unknown);
788
+ }
789
+ if (objType && objType.kind === Type.String) {
790
+ return this.createType(Type.String);
791
+ }
792
+ return this.createType(Type.Unknown);
793
+ }
794
+
795
+ // Handle property access - property can be a string directly or an object
796
+ let propName;
797
+ if (typeof node.property === 'string') {
798
+ propName = node.property;
799
+ } else if (node.property && node.property.name) {
800
+ propName = node.property.name;
801
+ } else if (node.property && node.property.value) {
802
+ propName = node.property.value;
803
+ }
804
+
805
+ // Check for property on object type
806
+ if (objType && objType.kind === Type.Object && objType.properties) {
807
+ if (propName in objType.properties) {
808
+ return objType.properties[propName];
809
+ }
810
+ // Only error if we have a known object shape and the property doesn't exist
811
+ if (Object.keys(objType.properties).length > 0) {
812
+ this.addError(
813
+ `Property '${propName}' does not exist on type ${this.typeToString(objType)}`,
814
+ node
815
+ );
816
+ }
817
+ }
818
+
819
+ // Check for enum member access
820
+ if (objType && objType.kind === Type.Enum && objType.members) {
821
+ if (propName in objType.members) {
822
+ return objType.members[propName];
823
+ }
824
+ this.addError(
825
+ `Property '${propName}' does not exist on enum '${objType.name}'`,
826
+ node
827
+ );
828
+ }
829
+
830
+ // Built-in properties
831
+ if (objType) {
832
+ if (objType.kind === Type.Array && propName === 'length') {
833
+ return this.createType(Type.Number);
834
+ }
835
+ if (objType.kind === Type.String && propName === 'length') {
836
+ return this.createType(Type.Number);
837
+ }
838
+ }
839
+
840
+ return this.createType(Type.Unknown);
841
+ }
842
+
843
+ visitArrayExpression(node) {
844
+ if (node.elements.length === 0) {
845
+ return this.createArrayType(this.createType(Type.Unknown));
846
+ }
847
+
848
+ // Infer element type from first element
849
+ const firstElemType = this.visitExpression(node.elements[0]);
850
+
851
+ // Visit all elements
852
+ for (let i = 1; i < node.elements.length; i++) {
853
+ this.visitExpression(node.elements[i]);
854
+ }
855
+
856
+ return this.createArrayType(firstElemType);
857
+ }
858
+
859
+ visitObjectExpression(node) {
860
+ const properties = {};
861
+
862
+ for (const prop of node.properties) {
863
+ if (prop.type === NodeType.SpreadElement) {
864
+ const spreadType = this.visitExpression(prop.argument);
865
+ if (spreadType && spreadType.kind === Type.Object && spreadType.properties) {
866
+ Object.assign(properties, spreadType.properties);
867
+ }
868
+ } else {
869
+ // Key can be a string directly, or an object with name/value
870
+ let key;
871
+ if (typeof prop.key === 'string') {
872
+ key = prop.key;
873
+ } else if (prop.key && prop.key.name) {
874
+ key = prop.key.name;
875
+ } else if (prop.key && prop.key.value) {
876
+ key = prop.key.value;
877
+ }
878
+ const valueType = this.visitExpression(prop.value);
879
+ if (key) {
880
+ properties[key] = valueType;
881
+ }
882
+ }
883
+ }
884
+
885
+ return this.createObjectType(properties);
886
+ }
887
+
888
+ visitArrowFunctionExpression(node) {
889
+ this.pushScope();
890
+
891
+ // Define parameters
892
+ for (const param of node.params) {
893
+ const name = param.name || param.argument || param;
894
+ this.defineVariable(name, this.createType(Type.Any));
895
+ }
896
+
897
+ // Visit body
898
+ let returnType = this.createType(Type.Void);
899
+ if (node.body.type === NodeType.BlockStatement) {
900
+ for (const stmt of node.body.body) {
901
+ this.visitStatement(stmt);
902
+ }
903
+ } else {
904
+ returnType = this.visitExpression(node.body);
905
+ }
906
+
907
+ this.popScope();
908
+
909
+ return this.createFunctionType(
910
+ node.params.map(() => this.createType(Type.Any)),
911
+ returnType
912
+ );
913
+ }
914
+
915
+ visitConditionalExpression(node) {
916
+ this.visitExpression(node.test);
917
+ const consequentType = this.visitExpression(node.consequent);
918
+ const alternateType = this.visitExpression(node.alternate);
919
+
920
+ // Return the type if both branches have the same type
921
+ if (consequentType.kind === alternateType.kind) {
922
+ return consequentType;
923
+ }
924
+
925
+ return this.createType(Type.Unknown);
926
+ }
927
+
928
+ visitAssignmentExpression(node) {
929
+ const valueType = this.visitExpression(node.right);
930
+
931
+ // Update variable type if it's a simple identifier
932
+ if (node.left.type === NodeType.Identifier) {
933
+ this.defineVariable(node.left.name, valueType);
934
+ }
935
+
936
+ return valueType;
937
+ }
938
+
939
+ visitFlowExpression(node) {
940
+ let currentType = this.visitExpression(node.left);
941
+
942
+ // Flow expression has left and right, not initial and steps
943
+ if (node.right) {
944
+ this.visitExpression(node.right);
945
+ currentType = this.createType(Type.Unknown);
946
+ }
947
+
948
+ return currentType;
949
+ }
950
+ }