@xnoxs/flux-lang 3.1.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 (56) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +1089 -0
  3. package/bin/flux.js +1397 -0
  4. package/dist/flux.cjs.js +6664 -0
  5. package/dist/flux.esm.js +6674 -0
  6. package/dist/flux.min.js +263 -0
  7. package/index.d.ts +202 -0
  8. package/index.js +26 -0
  9. package/package.json +77 -0
  10. package/scripts/build.js +76 -0
  11. package/src/bundler.js +216 -0
  12. package/src/checker.js +322 -0
  13. package/src/codegen.js +785 -0
  14. package/src/css-preprocessor.js +399 -0
  15. package/src/formatter.js +140 -0
  16. package/src/jsx.js +480 -0
  17. package/src/lexer.js +518 -0
  18. package/src/linter.js +758 -0
  19. package/src/mangler.js +280 -0
  20. package/src/parser.js +1671 -0
  21. package/src/self/bundler.flux +167 -0
  22. package/src/self/bundler.js +187 -0
  23. package/src/self/checker.flux +249 -0
  24. package/src/self/checker.js +338 -0
  25. package/src/self/codegen.flux +555 -0
  26. package/src/self/codegen.js +784 -0
  27. package/src/self/css-preprocessor.flux +373 -0
  28. package/src/self/css-preprocessor.js +387 -0
  29. package/src/self/formatter.flux +93 -0
  30. package/src/self/formatter.js +114 -0
  31. package/src/self/jsx.flux +430 -0
  32. package/src/self/jsx.js +396 -0
  33. package/src/self/lexer.flux +529 -0
  34. package/src/self/lexer.js +709 -0
  35. package/src/self/lexer.stage2.js +700 -0
  36. package/src/self/linter.flux +515 -0
  37. package/src/self/linter.js +804 -0
  38. package/src/self/mangler.flux +253 -0
  39. package/src/self/mangler.js +348 -0
  40. package/src/self/parser.flux +1146 -0
  41. package/src/self/parser.js +1571 -0
  42. package/src/self/sourcemap.flux +66 -0
  43. package/src/self/sourcemap.js +72 -0
  44. package/src/self/stdlib.flux +356 -0
  45. package/src/self/stdlib.js +396 -0
  46. package/src/self/test-runner.flux +201 -0
  47. package/src/self/test-runner.js +132 -0
  48. package/src/self/transpiler.flux +123 -0
  49. package/src/self/transpiler.js +83 -0
  50. package/src/self/type-checker.flux +821 -0
  51. package/src/self/type-checker.js +1106 -0
  52. package/src/sourcemap.js +82 -0
  53. package/src/stdlib.js +436 -0
  54. package/src/test-runner.js +239 -0
  55. package/src/transpiler.js +172 -0
  56. package/src/type-checker.js +1206 -0
@@ -0,0 +1,515 @@
1
+ // ============================================================
2
+ // Flux Self-Hosted Linter
3
+ // src/self/linter.flux — written in Flux, compiled by stage-0
4
+ //
5
+ // AST-level lint rules:
6
+ // RULE 1 — unused-var : val/var declared but never read
7
+ // RULE 2 — unreachable : code after return/throw/break/continue
8
+ // RULE 3 — shadow-val : inner val/var shadows outer binding
9
+ // ============================================================
10
+
11
+ import { lexerize } from './lexer'
12
+ import { makeParser } from './parser'
13
+
14
+ val BUILTIN_NAMES = new Set([
15
+ 'self','this','arguments','exports','module','require','__dirname',
16
+ '__filename','process','console','Math','JSON','Object','Array',
17
+ 'String','Number','Boolean','Promise','Error','Symbol','Map','Set',
18
+ 'WeakMap','WeakSet','Proxy','Reflect','undefined','null','true','false',
19
+ 'NaN','Infinity','parseInt','parseFloat','isNaN','isFinite','setTimeout',
20
+ 'clearTimeout','setInterval','clearInterval','fetch','globalThis',
21
+ 'print','truncate','range','sum','first','last','zip','flatten',
22
+ 'groupBy','unique','sortBy','chunk',
23
+ ])
24
+
25
+ fn isIgnoredName(name):
26
+ if BUILTIN_NAMES.has(name): return true
27
+ if name.startsWith('_'): return true
28
+ return false
29
+
30
+ // ── LintIssue ────────────────────────────────────────────────
31
+ export class LintIssue:
32
+ rule: string
33
+ severity: string
34
+ message: string
35
+ hint: any
36
+ line: any
37
+ col: any
38
+
39
+ // ── LintScope ────────────────────────────────────────────────
40
+ export class LintScope:
41
+ parent: any
42
+ kind: string
43
+ vars: any
44
+
45
+ fn define(name, kind, loc):
46
+ val shadowOf = self.lookupOwn(name) ?? self.lookupParent(name)
47
+ val entry = { name, kind, loc, used: false, shadowOf }
48
+ self.vars.set(name, entry)
49
+ return entry
50
+
51
+ fn lookupOwn(name):
52
+ return self.vars.get(name) ?? null
53
+
54
+ fn lookupParent(name):
55
+ var s = self.parent
56
+ while s:
57
+ if s.vars.has(name): return s.vars.get(name)
58
+ s = s.parent
59
+ return null
60
+
61
+ fn lookup(name):
62
+ return self.lookupOwn(name) ?? self.lookupParent(name)
63
+
64
+ fn markUsed(name):
65
+ val entry = self.lookup(name)
66
+ if entry: entry.used = true
67
+
68
+ fn ownEntries():
69
+ return [...self.vars.values()]
70
+
71
+ fn makeLintScope(parent, kind):
72
+ return new LintScope(parent, kind, new Map())
73
+
74
+ fn makeLintIssue(rule, severity, message, hint, loc):
75
+ val li = new LintIssue(rule, severity, message, null, null, null)
76
+ li.hint = hint ?? null
77
+ li.line = loc ? loc.line : null
78
+ li.col = loc ? loc.col : null
79
+ return li
80
+
81
+ // ── Linter ───────────────────────────────────────────────────
82
+ export class Linter:
83
+ issues: any[]
84
+
85
+ fn issue(rule, severity, message, hint, loc):
86
+ self.issues.push(makeLintIssue(rule, severity, message, hint, loc))
87
+
88
+ fn lint(ast):
89
+ self.issues = []
90
+ val scope = makeLintScope(null, 'module')
91
+ self.walkStmts(ast.body, scope, { inFn: false })
92
+ return self.issues
93
+
94
+ fn isTerminator(node):
95
+ if not node: return false
96
+ if node.type == 'ReturnStmt' or node.type == 'ThrowStmt': return true
97
+ if node.type == 'BreakStmt' or node.type == 'ContinueStmt': return true
98
+ return false
99
+
100
+ fn terminatorName(node):
101
+ if node.type == 'ReturnStmt': return 'return'
102
+ if node.type == 'ThrowStmt': return 'throw'
103
+ if node.type == 'BreakStmt': return 'break'
104
+ if node.type == 'ContinueStmt': return 'continue'
105
+ return 'terminator'
106
+
107
+ fn checkUnused(scope):
108
+ for entry in scope.ownEntries():
109
+ if entry.used: continue
110
+ if isIgnoredName(entry.name): continue
111
+ if entry._exported: continue
112
+ self.issue('unused-var', 'warn',
113
+ "'" + entry.name + "' is declared but never used",
114
+ "Prefix with '_' (e.g. '_" + entry.name + "') to silence this warning, or remove the declaration",
115
+ entry.loc)
116
+
117
+ fn walkStmts(stmts, scope, ctx):
118
+ if not stmts or stmts.length == 0: return
119
+ var i = 0
120
+ while i < stmts.length:
121
+ val node = stmts[i]
122
+ if i > 0 and self.isTerminator(stmts[i - 1]):
123
+ if node and node.loc:
124
+ self.issue('unreachable', 'error',
125
+ "Unreachable code after '" + self.terminatorName(stmts[i - 1]) + "'",
126
+ 'Remove or move the unreachable statement', node.loc)
127
+ break
128
+ self.walkStmt(node, scope, ctx)
129
+ i = i + 1
130
+ self.checkUnused(scope)
131
+
132
+ fn walkStmt(node, scope, ctx):
133
+ if not node: return
134
+ if node.type == 'VarDecl':
135
+ if node.init: self.walkExpr(node.init, scope, ctx)
136
+ scope.define(node.name, node.kind, node.loc)
137
+ return
138
+ if node.type == 'DestructureDecl':
139
+ if node.init: self.walkExpr(node.init, scope, ctx)
140
+ if node.patternType == 'object':
141
+ for p in node.pattern: scope.define(p.alias ?? p.name, node.kind, node.loc)
142
+ else:
143
+ for p in node.pattern: if p: scope.define(p.name, node.kind, node.loc)
144
+ return
145
+ if node.type == 'FnDecl':
146
+ if node.name:
147
+ val entry = scope.define(node.name, 'val', node.loc)
148
+ entry.used = true
149
+ val inner = makeLintScope(scope, 'fn')
150
+ for p in node.params:
151
+ val pe = inner.define(p.name, 'val', node.loc)
152
+ pe.used = true
153
+ if node.inline:
154
+ self.walkExpr(node.body, inner, { ...ctx, inFn: true })
155
+ else:
156
+ self.walkStmts(node.body, inner, { ...ctx, inFn: true })
157
+ return
158
+ if node.type == 'ClassDecl':
159
+ val ce = scope.define(node.name, 'val', node.loc)
160
+ ce.used = true
161
+ val cls = makeLintScope(scope, 'class')
162
+ for f in (node.fields ?? []):
163
+ val fe = cls.define(f.name, 'var', node.loc)
164
+ fe.used = true
165
+ for m in (node.methods ?? []): self.walkStmt(m, cls, ctx)
166
+ return
167
+ if node.type == 'TypeDecl':
168
+ for v in node.variants:
169
+ val ve = scope.define(v.name, 'val', node.loc)
170
+ ve.used = true
171
+ return
172
+ if node.type == 'InterfaceDecl' or node.type == 'EnumDecl':
173
+ val ie = scope.define(node.name, 'val', node.loc)
174
+ ie.used = true
175
+ return
176
+ if node.type == 'IfStmt':
177
+ self.walkExpr(node.cond, scope, ctx)
178
+ val thenScope = makeLintScope(scope, 'block')
179
+ self.walkStmts(node.then, thenScope, ctx)
180
+ for ei in (node.elseifs ?? []):
181
+ self.walkExpr(ei.cond, scope, ctx)
182
+ val eis = makeLintScope(scope, 'block')
183
+ self.walkStmts(ei.body, eis, ctx)
184
+ if node.else_:
185
+ val elseScope = makeLintScope(scope, 'block')
186
+ self.walkStmts(node.else_, elseScope, ctx)
187
+ return
188
+ if node.type == 'ForInStmt':
189
+ self.walkExpr(node.iter, scope, ctx)
190
+ val forScope = makeLintScope(scope, 'block')
191
+ val loopVar = forScope.define(node.var, 'val', node.loc)
192
+ loopVar.used = true
193
+ self.walkStmts(node.body, forScope, { ...ctx, inLoop: true })
194
+ return
195
+ if node.type == 'WhileStmt':
196
+ self.walkExpr(node.cond, scope, ctx)
197
+ val ws = makeLintScope(scope, 'block')
198
+ self.walkStmts(node.body, ws, { ...ctx, inLoop: true })
199
+ return
200
+ if node.type == 'DoWhileStmt':
201
+ val ds = makeLintScope(scope, 'block')
202
+ self.walkStmts(node.body, ds, { ...ctx, inLoop: true })
203
+ self.walkExpr(node.cond, scope, ctx)
204
+ return
205
+ if node.type == 'MatchStmt':
206
+ self.walkExpr(node.subject, scope, ctx)
207
+ for arm in node.arms:
208
+ val armScope = makeLintScope(scope, 'block')
209
+ if arm.pattern and arm.pattern.type == 'VariantPat':
210
+ for b in arm.pattern.bindings:
211
+ val be = armScope.define(b, 'val', node.loc)
212
+ be.used = true
213
+ if arm.guard: self.walkExpr(arm.guard, armScope, ctx)
214
+ if arm.inline:
215
+ var expr = arm.body and arm.body[0] ? arm.body[0] : null
216
+ if arm.body and arm.body[0] and arm.body[0].expr: expr = arm.body[0].expr
217
+ if expr: self.walkExpr(expr, armScope, ctx)
218
+ else:
219
+ self.walkStmts(arm.body ?? [], armScope, ctx)
220
+ return
221
+ if node.type == 'ReturnStmt':
222
+ if node.value: self.walkExpr(node.value, scope, ctx)
223
+ return
224
+ if node.type == 'ThrowStmt':
225
+ self.walkExpr(node.value, scope, ctx)
226
+ return
227
+ if node.type == 'TryCatchStmt':
228
+ val tryS = makeLintScope(scope, 'block')
229
+ self.walkStmts(node.tryBody, tryS, ctx)
230
+ if node.catchBody:
231
+ val catchS = makeLintScope(scope, 'block')
232
+ if node.catchParam:
233
+ val ce2 = catchS.define(node.catchParam, 'val', node.loc)
234
+ ce2.used = true
235
+ self.walkStmts(node.catchBody, catchS, ctx)
236
+ if node.finallyBody:
237
+ val finS = makeLintScope(scope, 'block')
238
+ self.walkStmts(node.finallyBody, finS, ctx)
239
+ return
240
+ if node.type == 'ImportDecl':
241
+ if node.defaultName:
242
+ val ie2 = scope.define(node.defaultName, 'val', node.loc)
243
+ ie2.used = true
244
+ if node.namespaceName:
245
+ val ne = scope.define(node.namespaceName, 'val', node.loc)
246
+ ne.used = true
247
+ for n in (node.names ?? []):
248
+ val nm = typeof n == 'string' ? n : n.alias
249
+ val ie3 = scope.define(nm, 'val', node.loc)
250
+ ie3.used = true
251
+ return
252
+ if node.type == 'ExportDecl':
253
+ var inner2 = node.decl
254
+ if node.isDefault: inner2 = { type: 'ExprStmt', expr: node.decl }
255
+ self.walkStmt(inner2, scope, ctx)
256
+ if node.decl and node.decl.name:
257
+ val ee = scope.lookup(node.decl.name)
258
+ if ee: ee.used = true; ee._exported = true
259
+ return
260
+ if node.type == 'ExprStmt':
261
+ self.walkExpr(node.expr, scope, ctx)
262
+
263
+ fn walkExpr(node, scope, ctx):
264
+ if not node: return
265
+ if node.type == 'Identifier':
266
+ scope.markUsed(node.name)
267
+ return
268
+ if node.type == 'AssignExpr':
269
+ self.walkExpr(node.target, scope, ctx)
270
+ self.walkExpr(node.value, scope, ctx)
271
+ return
272
+ if node.type == 'UpdateExpr':
273
+ self.walkExpr(node.operand, scope, ctx)
274
+ return
275
+ if node.type == 'BinaryExpr' or node.type == 'PipeExpr':
276
+ self.walkExpr(node.left, scope, ctx)
277
+ self.walkExpr(node.right, scope, ctx)
278
+ return
279
+ if node.type == 'UnaryExpr' or node.type == 'AwaitExpr' or node.type == 'TypeofExpr':
280
+ self.walkExpr(node.operand, scope, ctx)
281
+ return
282
+ if node.type == 'TernaryExpr':
283
+ self.walkExpr(node.cond, scope, ctx)
284
+ self.walkExpr(node.then, scope, ctx)
285
+ self.walkExpr(node.else_, scope, ctx)
286
+ return
287
+ if node.type == 'CallExpr' or node.type == 'OptCallExpr':
288
+ self.walkExpr(node.callee, scope, ctx)
289
+ for a in (node.args ?? []): self.walkExpr(a, scope, ctx)
290
+ return
291
+ if node.type == 'MemberExpr' or node.type == 'OptMemberExpr':
292
+ self.walkExpr(node.obj, scope, ctx)
293
+ return
294
+ if node.type == 'IndexExpr' or node.type == 'OptIndexExpr':
295
+ self.walkExpr(node.obj, scope, ctx)
296
+ self.walkExpr(node.idx, scope, ctx)
297
+ return
298
+ if node.type == 'NewExpr':
299
+ if node.callee: self.walkExpr(node.callee, scope, ctx)
300
+ for a in (node.args ?? []): self.walkExpr(a, scope, ctx)
301
+ return
302
+ if node.type == 'ArrayExpr':
303
+ for itm in (node.items ?? []): if itm: self.walkExpr(itm, scope, ctx)
304
+ return
305
+ if node.type == 'ObjectExpr':
306
+ for pair in (node.pairs ?? []): if pair.value: self.walkExpr(pair.value, scope, ctx)
307
+ return
308
+ if node.type == 'SpreadExpr':
309
+ self.walkExpr(node.expr, scope, ctx)
310
+ return
311
+ if node.type == 'LambdaExpr':
312
+ val lInner = makeLintScope(scope, 'fn')
313
+ for p in (node.params ?? []):
314
+ val pe = lInner.define(p.name, 'val', null)
315
+ pe.used = true
316
+ self.walkExpr(node.body, lInner, { ...ctx, inFn: true })
317
+ return
318
+ if node.type == 'FnDecl':
319
+ if node.name:
320
+ val fentry = scope.define(node.name, 'val', node.loc)
321
+ fentry.used = true
322
+ val fInner = makeLintScope(scope, 'fn')
323
+ for p in (node.params ?? []):
324
+ val pe2 = fInner.define(p.name, 'val', null)
325
+ pe2.used = true
326
+ if node.inline:
327
+ self.walkExpr(node.body, fInner, { ...ctx, inFn: true })
328
+ else:
329
+ self.walkStmts(node.body, fInner, { ...ctx, inFn: true })
330
+ return
331
+ if node.type == 'TemplateLit':
332
+ self.walkTemplateParts(node.parts ?? [], scope, ctx)
333
+ return
334
+ if (node.type == 'CastExpr' or node.type == 'AsConstExpr' or
335
+ node.type == 'SatisfiesExpr' or node.type == 'IsExpr' or node.type == 'NonNullExpr'):
336
+ self.walkExpr(node.expr, scope, ctx)
337
+ return
338
+ if node.type == 'RangeExpr':
339
+ self.walkExpr(node.start, scope, ctx)
340
+ self.walkExpr(node.end, scope, ctx)
341
+
342
+ fn walkTemplateParts(parts, scope, ctx):
343
+ for p in parts:
344
+ if not p or p.type != 'expr': continue
345
+ try:
346
+ val tokens = lexerize(p.value).tokenize()
347
+ val expr = makeParser(tokens).parseExpr()
348
+ self.walkExpr(expr, scope, ctx)
349
+ catch(err3):
350
+ val re = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g
351
+ var m = re.exec(p.value)
352
+ while m:
353
+ scope.markUsed(m[1])
354
+ m = re.exec(p.value)
355
+
356
+ // ── ShadowChecker ────────────────────────────────────────────
357
+ export class ShadowChecker:
358
+ issues: any[]
359
+
360
+ fn check(ast):
361
+ self.issues = []
362
+ self.scWalkStmts(ast.body, makeLintScope(null, 'module'))
363
+ return self.issues
364
+
365
+ fn scWarn(name, inner, _outer):
366
+ self.issues.push(makeLintIssue('shadow-val', 'warn',
367
+ "'" + name + "' shadows an outer declaration",
368
+ "Rename one of the '" + name + "' variables to avoid confusion",
369
+ inner))
370
+
371
+ fn scDefine(scope, name, loc):
372
+ val outer = scope.lookupParent(name)
373
+ if outer and not isIgnoredName(name):
374
+ self.scWarn(name, loc, outer.loc)
375
+ scope.define(name, 'val', loc)
376
+
377
+ fn scWalkStmts(stmts, scope):
378
+ if not stmts: return
379
+ for node in stmts: self.scWalkStmt(node, scope)
380
+
381
+ fn scWalkStmt(node, scope):
382
+ if not node: return
383
+ if node.type == 'VarDecl':
384
+ if node.init: self.scWalkExpr(node.init, scope)
385
+ self.scDefine(scope, node.name, node.loc)
386
+ return
387
+ if node.type == 'DestructureDecl':
388
+ if node.init: self.scWalkExpr(node.init, scope)
389
+ if node.patternType == 'object':
390
+ for p in node.pattern: self.scDefine(scope, p.alias ?? p.name, node.loc)
391
+ else:
392
+ for p in node.pattern: if p: self.scDefine(scope, p.name, node.loc)
393
+ return
394
+ if node.type == 'FnDecl':
395
+ if node.name: self.scDefine(scope, node.name, node.loc)
396
+ val inner = makeLintScope(scope, 'fn')
397
+ for p in (node.params ?? []): self.scDefine(inner, p.name, node.loc)
398
+ if node.inline: self.scWalkExpr(node.body, inner)
399
+ else: self.scWalkStmts(node.body, inner)
400
+ return
401
+ if node.type == 'ClassDecl':
402
+ self.scDefine(scope, node.name, node.loc)
403
+ val cls = makeLintScope(scope, 'class')
404
+ for m in (node.methods ?? []): self.scWalkStmt(m, cls)
405
+ return
406
+ if node.type == 'IfStmt':
407
+ self.scWalkExpr(node.cond, scope)
408
+ self.scWalkStmts(node.then, makeLintScope(scope, 'block'))
409
+ for ei in (node.elseifs ?? []):
410
+ self.scWalkExpr(ei.cond, scope)
411
+ self.scWalkStmts(ei.body, makeLintScope(scope, 'block'))
412
+ if node.else_: self.scWalkStmts(node.else_, makeLintScope(scope, 'block'))
413
+ return
414
+ if node.type == 'ForInStmt':
415
+ self.scWalkExpr(node.iter, scope)
416
+ val fs2 = makeLintScope(scope, 'block')
417
+ self.scDefine(fs2, node.var, node.loc)
418
+ self.scWalkStmts(node.body, fs2)
419
+ return
420
+ if node.type == 'WhileStmt':
421
+ self.scWalkExpr(node.cond, scope)
422
+ self.scWalkStmts(node.body, makeLintScope(scope, 'block'))
423
+ return
424
+ if node.type == 'DoWhileStmt':
425
+ self.scWalkStmts(node.body, makeLintScope(scope, 'block'))
426
+ self.scWalkExpr(node.cond, scope)
427
+ return
428
+ if node.type == 'MatchStmt':
429
+ self.scWalkExpr(node.subject, scope)
430
+ for arm in (node.arms ?? []):
431
+ val as2 = makeLintScope(scope, 'block')
432
+ if arm.pattern and arm.pattern.type == 'VariantPat':
433
+ for b in arm.pattern.bindings: self.scDefine(as2, b, node.loc)
434
+ if arm.guard: self.scWalkExpr(arm.guard, as2)
435
+ if arm.inline:
436
+ var scExpr = arm.body and arm.body[0] ? arm.body[0] : null
437
+ if arm.body and arm.body[0] and arm.body[0].expr: scExpr = arm.body[0].expr
438
+ if scExpr: self.scWalkExpr(scExpr, as2)
439
+ else:
440
+ self.scWalkStmts(arm.body ?? [], as2)
441
+ return
442
+ if node.type == 'ReturnStmt':
443
+ if node.value: self.scWalkExpr(node.value, scope)
444
+ return
445
+ if node.type == 'ThrowStmt': self.scWalkExpr(node.value, scope); return
446
+ if node.type == 'ExprStmt': self.scWalkExpr(node.expr, scope); return
447
+ if node.type == 'TryCatchStmt':
448
+ self.scWalkStmts(node.tryBody, makeLintScope(scope, 'block'))
449
+ if node.catchBody:
450
+ val cs = makeLintScope(scope, 'block')
451
+ if node.catchParam: self.scDefine(cs, node.catchParam, node.loc)
452
+ self.scWalkStmts(node.catchBody, cs)
453
+ if node.finallyBody: self.scWalkStmts(node.finallyBody, makeLintScope(scope, 'block'))
454
+ return
455
+ if node.type == 'ImportDecl':
456
+ if node.defaultName: self.scDefine(scope, node.defaultName, node.loc)
457
+ if node.namespaceName: self.scDefine(scope, node.namespaceName, node.loc)
458
+ for n in (node.names ?? []):
459
+ self.scDefine(scope, typeof n == 'string' ? n : n.alias, node.loc)
460
+ return
461
+ if node.type == 'ExportDecl':
462
+ self.scWalkStmt(node.isDefault ? { type: 'ExprStmt', expr: node.decl } : node.decl, scope)
463
+
464
+ fn scWalkExpr(node, scope):
465
+ if not node: return
466
+ if node.type == 'LambdaExpr':
467
+ val inner = makeLintScope(scope, 'fn')
468
+ for p in (node.params ?? []): self.scDefine(inner, p.name, null)
469
+ self.scWalkExpr(node.body, inner)
470
+ return
471
+ if node.type == 'FnDecl':
472
+ val inner2 = makeLintScope(scope, 'fn')
473
+ for p in (node.params ?? []): self.scDefine(inner2, p.name, null)
474
+ if node.inline: self.scWalkExpr(node.body, inner2)
475
+ else: self.scWalkStmts(node.body, inner2)
476
+ return
477
+ if node.type == 'BinaryExpr' or node.type == 'PipeExpr':
478
+ self.scWalkExpr(node.left, scope); self.scWalkExpr(node.right, scope); return
479
+ if (node.type == 'UnaryExpr' or node.type == 'AwaitExpr' or node.type == 'TypeofExpr' or
480
+ node.type == 'CastExpr' or node.type == 'AsConstExpr' or node.type == 'SatisfiesExpr' or
481
+ node.type == 'IsExpr' or node.type == 'NonNullExpr' or node.type == 'SpreadExpr'):
482
+ self.scWalkExpr(node.operand ?? node.expr, scope); return
483
+ if node.type == 'TernaryExpr':
484
+ self.scWalkExpr(node.cond, scope)
485
+ self.scWalkExpr(node.then, scope)
486
+ self.scWalkExpr(node.else_, scope); return
487
+ if node.type == 'CallExpr' or node.type == 'OptCallExpr':
488
+ self.scWalkExpr(node.callee, scope)
489
+ for a in (node.args ?? []): self.scWalkExpr(a, scope); return
490
+ if node.type == 'MemberExpr' or node.type == 'OptMemberExpr':
491
+ self.scWalkExpr(node.obj, scope); return
492
+ if node.type == 'IndexExpr' or node.type == 'OptIndexExpr':
493
+ self.scWalkExpr(node.obj, scope); self.scWalkExpr(node.idx, scope); return
494
+ if node.type == 'NewExpr':
495
+ if node.callee: self.scWalkExpr(node.callee, scope)
496
+ for a in (node.args ?? []): self.scWalkExpr(a, scope); return
497
+ if node.type == 'ArrayExpr':
498
+ for itm in (node.items ?? []): if itm: self.scWalkExpr(itm, scope); return
499
+ if node.type == 'ObjectExpr':
500
+ for pair in (node.pairs ?? []): if pair.value: self.scWalkExpr(pair.value, scope); return
501
+ if node.type == 'AssignExpr':
502
+ self.scWalkExpr(node.target, scope); self.scWalkExpr(node.value, scope); return
503
+ if node.type == 'UpdateExpr':
504
+ self.scWalkExpr(node.operand, scope); return
505
+ if node.type == 'RangeExpr':
506
+ self.scWalkExpr(node.start, scope); self.scWalkExpr(node.end, scope)
507
+
508
+ export fn lint(ast):
509
+ val linter = new Linter([])
510
+ val shadowChecker = new ShadowChecker([])
511
+ val unusedAndUnreachable = linter.lint(ast)
512
+ val shadows = shadowChecker.check(ast)
513
+ val combined = [...unusedAndUnreachable, ...shadows]
514
+ combined.sort((a, b) -> (a.line ?? 0) - (b.line ?? 0))
515
+ return combined