@slim-lang/core 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +666 -0
  2. package/package.json +55 -0
  3. package/packages/slim/.spm +7 -0
  4. package/packages/slim/converters/main.slim +106 -0
  5. package/packages/slim/helpers/array.slim +25 -0
  6. package/packages/slim/helpers/path.slim +3 -0
  7. package/packages/slim/helpers/request.slim +102 -0
  8. package/packages/slim/helpers/string.slim +27 -0
  9. package/packages/slim/main.slim +42 -0
  10. package/packages/slim/parse/main.slim +25 -0
  11. package/packages/slim/server/main.slim +423 -0
  12. package/packages/slim/time/main.slim +66 -0
  13. package/packages/slim/types/common.slim +6 -0
  14. package/packages/slim/types/formats.slim +23 -0
  15. package/packages/slim/types/hash.slim +6 -0
  16. package/packages/slim/types/mails.slim +3 -0
  17. package/packages/slim/types/numerical.slim +9 -0
  18. package/packages/slim/types/time.slim +3 -0
  19. package/run-dev-slim.js +133 -0
  20. package/run-slim.js +20 -0
  21. package/src/bin/api/github_auth.js +89 -0
  22. package/src/bin/api/github_get.js +139 -0
  23. package/src/bin/api/github_req.js +455 -0
  24. package/src/bin/api/lock.js +37 -0
  25. package/src/bin/api/spm.js +103 -0
  26. package/src/bin/api/storage.js +30 -0
  27. package/src/bin/cli.js +404 -0
  28. package/src/bin/config.default.json +5 -0
  29. package/src/bin/helpers.js +147 -0
  30. package/src/bin/parsers/spm.js +174 -0
  31. package/src/bin/spm.js +519 -0
  32. package/src/checker.js +926 -0
  33. package/src/compile.js +230 -0
  34. package/src/external/classErrors.js +202 -0
  35. package/src/external/client.js +38 -0
  36. package/src/external/core.js +861 -0
  37. package/src/external/defaults.js +25 -0
  38. package/src/external/helpers.js +541 -0
  39. package/src/external/slim-globals.d.ts +65 -0
  40. package/src/external/types.js +38 -0
  41. package/src/format.js +81 -0
  42. package/src/handlers/errorHandler.js +43 -0
  43. package/src/handlers/parser/components.js +250 -0
  44. package/src/handlers/parserHandler.js +793 -0
  45. package/src/jsdoc.js +273 -0
  46. package/src/lexer.js +174 -0
  47. package/src/modulePaths.js +74 -0
  48. package/src/parser.js +818 -0
  49. package/src/repl.js +32 -0
  50. package/src/sourcemap.js +0 -0
  51. package/src/test-runner.js +62 -0
  52. package/src/transform.js +765 -0
@@ -0,0 +1,765 @@
1
+ import { parse } from "@babel/parser"
2
+ import _traverse from "@babel/traverse"
3
+ import _generate from "@babel/generator"
4
+ import remapping from "@jridgewell/remapping"
5
+ import { preprocess } from "./parser.js"
6
+ import { emitJsDoc, emitDeclarations, jsdocComment } from "./jsdoc.js"
7
+ import { checkTypes, formatDiagnostics } from "./checker.js"
8
+ import * as t from "@babel/types"
9
+ import path from "node:path"
10
+ import { getDistPath, resolveSlimImport, resolveSlimSource } from "./modulePaths.js"
11
+ import {
12
+ PRE_SOURCE,
13
+ buildPreMap,
14
+ computeLineStarts,
15
+ mapPreToOriginal
16
+ } from "./sourcemap.js"
17
+
18
+ const traverse = _traverse.default ?? _traverse
19
+ const generate = _generate.default ?? _generate
20
+
21
+ function isRuntimeCall(node, name) {
22
+ return t.isCallExpression(node) && t.isIdentifier(node.callee, { name })
23
+ }
24
+
25
+ function getTypeReferenceName(typeText) {
26
+ const withoutArray = typeText.replace(/(?:\[\])+$/, "")
27
+ const withoutGeneric = withoutArray.replace(/<.*>$/, "")
28
+ return withoutGeneric.split("::")[0]
29
+ }
30
+
31
+ // Builtin type names are always matched by label; they must never be resolved
32
+ // through a same-named binding in scope (e.g. a `const string` variable would
33
+ // otherwise shadow the primitive `string` type).
34
+ const BUILTIN_TYPE_NAMES = new Set([
35
+ "int", "float", "number", "string", "bool", "null", "undefined",
36
+ "object", "array", "function", "any", "element"
37
+ ])
38
+
39
+ function buildTypeSpec(typeText, path_, importedNames = new Set()) {
40
+ typeText = typeText.trim()
41
+ if (typeText.endsWith("?")) {
42
+ typeText = typeText.slice(0, -1).trim() + " | null | undefined"
43
+ }
44
+
45
+ const isIntersection = typeText.includes("&") && !typeText.includes("|")
46
+ const separator = isIntersection ? "&" : "|"
47
+
48
+ const refs = typeText.split(separator).map(part => part.trim()).filter(Boolean).map((label) => {
49
+ const referenceName = getTypeReferenceName(label)
50
+ const binding = referenceName ? path_.scope.getBinding(referenceName) : null
51
+ const args = [t.stringLiteral(label)]
52
+
53
+ if ((binding || importedNames.has(referenceName)) && !BUILTIN_TYPE_NAMES.has(referenceName)) {
54
+ args.push(t.arrowFunctionExpression([], t.identifier(referenceName)))
55
+ }
56
+
57
+ return t.callExpression(t.identifier("__type_ref__"), args)
58
+ })
59
+
60
+ const combinator = isIntersection ? "__type_spec_all__" : "__type_spec__"
61
+ return t.callExpression(t.identifier(combinator), refs)
62
+ }
63
+
64
+ function typeLabelFromArgument(node) {
65
+ return t.isStringLiteral(node) ? node.value : null
66
+ }
67
+
68
+ function buildTypedCheck(binding, value) {
69
+ return t.callExpression(t.identifier("__typed_variable_check__"), [
70
+ t.cloneNode(binding.slot),
71
+ value,
72
+ t.stringLiteral(binding.name)
73
+ ])
74
+ }
75
+
76
+ function getMemberName(member) {
77
+ if (t.isPrivateName(member.property)) return `#${member.property.id.name}`
78
+ if (!member.computed && t.isIdentifier(member.property)) return member.property.name
79
+ if (member.computed && t.isStringLiteral(member.property)) return member.property.value
80
+ return null
81
+ }
82
+
83
+ function instrumentRuntimeTypes(ast, sourceFile, importedNames) {
84
+ const typedBindings = new Map()
85
+ const staticFields = new Map()
86
+ let bindingCount = 0
87
+
88
+ function registerBinding(path_, name) {
89
+ const binding = path_.scope.getBinding(name)
90
+ if (!binding) return null
91
+
92
+ let info = typedBindings.get(binding)
93
+ if (!info) {
94
+ info = {
95
+ name,
96
+ binding,
97
+ slot: path_.scope.generateUidIdentifier(`slimType${bindingCount++}`)
98
+ }
99
+ typedBindings.set(binding, info)
100
+ }
101
+
102
+ return info
103
+ }
104
+
105
+ function getStaticField(path_, member) {
106
+ const fieldName = getMemberName(member)
107
+ let classBinding = null
108
+
109
+ if (t.isIdentifier(member.object)) {
110
+ classBinding = path_.scope.getBinding(member.object.name)
111
+ } else if (t.isThisExpression(member.object)) {
112
+ const method = path_.findParent(parent => parent.isClassMethod() || parent.isClassPrivateMethod())
113
+ const classPath = path_.findParent(parent => parent.isClassDeclaration())
114
+ if (method?.node.static && classPath?.node.id) {
115
+ classBinding = classPath.scope.getBinding(classPath.node.id.name)
116
+ }
117
+ }
118
+
119
+ return fieldName ? {
120
+ name: fieldName,
121
+ definition: staticFields.get(classBinding)?.get(fieldName)
122
+ } : null
123
+ }
124
+
125
+ traverse(ast, {
126
+ VariableDeclarator(path_) {
127
+ const { id, init } = path_.node
128
+ if (!t.isIdentifier(id) || !isRuntimeCall(init, "__typed_variable__")) return
129
+
130
+ const typeText = typeLabelFromArgument(init.arguments[1])
131
+ const binding = registerBinding(path_, id.name)
132
+ if (!typeText || !binding) return
133
+
134
+ init.arguments[1] = buildTypeSpec(typeText, path_, importedNames)
135
+ init.arguments[2] = t.cloneNode(binding.slot)
136
+ init.arguments[3] = t.stringLiteral(binding.name)
137
+ },
138
+
139
+ ClassProperty(path_) {
140
+ const { node } = path_
141
+ if (!node.static || !isRuntimeCall(node.value, "__typed_variable__")) return
142
+
143
+ const typeText = typeLabelFromArgument(node.value.arguments[1])
144
+ const fieldName = getMemberName({ property: node.key, computed: node.computed })
145
+ const classPath = path_.findParent(parent => parent.isClassDeclaration())
146
+ const className = classPath?.node.id?.name
147
+ const classBinding = className ? classPath.scope.getBinding(className) : null
148
+ if (!typeText || !fieldName || !classBinding) return
149
+
150
+ const fields = staticFields.get(classBinding) ?? new Map()
151
+ const displayName = `${className}.${fieldName}`
152
+ fields.set(fieldName, { displayName })
153
+ staticFields.set(classBinding, fields)
154
+
155
+ node.value = t.callExpression(t.identifier("__typed_static_field__"), [
156
+ t.thisExpression(),
157
+ t.stringLiteral(fieldName),
158
+ node.value.arguments[0],
159
+ buildTypeSpec(typeText, path_, importedNames),
160
+ t.stringLiteral(displayName)
161
+ ])
162
+ },
163
+
164
+ ExpressionStatement(path_) {
165
+ const call = path_.node.expression
166
+ if (!isRuntimeCall(call, "__declare_return__")) return
167
+
168
+ const [label, owner] = call.arguments
169
+ const fn = path_.getFunctionParent()
170
+ if (!t.isStringLiteral(label) || !fn) { path_.remove(); return }
171
+
172
+ const specification = buildTypeSpec(label.value, path_, importedNames)
173
+ const fnName = t.isStringLiteral(owner) ? owner.value : "function"
174
+
175
+ fn.traverse({
176
+ Function(inner) { inner.skip() },
177
+ ReturnStatement(statement) {
178
+ statement.node.argument = t.callExpression(t.identifier("__typed_return__"), [
179
+ statement.node.argument ?? t.identifier("undefined"),
180
+ t.cloneNode(specification),
181
+ t.stringLiteral(fnName)
182
+ ])
183
+ }
184
+ })
185
+
186
+ path_.remove()
187
+ },
188
+
189
+ CallExpression(path_) {
190
+ const { node } = path_
191
+
192
+ if (isRuntimeCall(node, "__typed_parameter__")) {
193
+ const [value, expected, displayName, optional, message] = node.arguments
194
+ if (!t.isIdentifier(value)) return
195
+
196
+ const typeText = typeLabelFromArgument(expected)
197
+ const binding = registerBinding(path_, value.name)
198
+ if (!typeText || !binding) return
199
+
200
+ node.arguments = [
201
+ value,
202
+ buildTypeSpec(typeText, path_, importedNames),
203
+ t.cloneNode(binding.slot),
204
+ t.isStringLiteral(displayName) ? displayName : t.stringLiteral(binding.name),
205
+ optional ?? t.booleanLiteral(false),
206
+ message ?? t.stringLiteral(`argument "${binding.name}" has an invalid type`)
207
+ ]
208
+ return
209
+ }
210
+
211
+ if (isRuntimeCall(node, "__def_struct__")) {
212
+ const schema = node.arguments[1]
213
+ if (!t.isObjectExpression(schema)) return
214
+
215
+ const specifications = []
216
+ for (const property of schema.properties) {
217
+ if (!t.isObjectProperty(property)) continue
218
+ const field = t.isIdentifier(property.key)
219
+ ? property.key.name
220
+ : t.isStringLiteral(property.key)
221
+ ? property.key.value
222
+ : null
223
+ const typeText = typeLabelFromArgument(property.value)
224
+ if (!field || !typeText) continue
225
+
226
+ specifications.push(t.objectProperty(
227
+ t.stringLiteral(field.replace(/^\*/, "")),
228
+ buildTypeSpec(typeText, path_, importedNames)
229
+ ))
230
+ }
231
+
232
+ if (specifications.length > 0) {
233
+ node.arguments[2] = t.objectExpression(specifications)
234
+ }
235
+ return
236
+ }
237
+
238
+ if (isRuntimeCall(node, "__type_def__")) {
239
+ const properties = node.arguments[2]
240
+ if (!t.isObjectExpression(properties)) return
241
+
242
+ const oneLine = properties.properties.some(property =>
243
+ t.isObjectProperty(property) &&
244
+ (t.isIdentifier(property.key, { name: "type" }) ||
245
+ t.isStringLiteral(property.key, { value: "type" })) &&
246
+ t.isStringLiteral(property.value, { value: "one-line-expr" })
247
+ )
248
+
249
+ if (oneLine && t.isIdentifier(node.arguments[1])) {
250
+ node.arguments[1] = buildTypeSpec(node.arguments[1].name, path_, importedNames)
251
+ }
252
+
253
+ for (const property of properties.properties) {
254
+ if (!t.isObjectProperty(property)) continue
255
+ const key = t.isIdentifier(property.key)
256
+ ? property.key.name
257
+ : t.isStringLiteral(property.key)
258
+ ? property.key.value
259
+ : null
260
+ if (key !== "extends" || !t.isStringLiteral(property.value)) continue
261
+ property.value = buildTypeSpec(property.value.value, path_, importedNames)
262
+ }
263
+ }
264
+ }
265
+ })
266
+
267
+ const slotsByScope = new Map()
268
+ for (const info of typedBindings.values()) {
269
+ const scope = info.binding.scope
270
+ const slots = slotsByScope.get(scope) ?? []
271
+ slots.push(info.slot)
272
+ slotsByScope.set(scope, slots)
273
+ }
274
+
275
+ for (const [scope, slots] of slotsByScope) {
276
+ const declaration = t.variableDeclaration(
277
+ "let",
278
+ slots.map(slot => t.variableDeclarator(t.cloneNode(slot), t.objectExpression([])))
279
+ )
280
+ const scopePath = scope.path
281
+
282
+ if (scopePath.isProgram()) {
283
+ declaration._slimTypeSlots = true
284
+ scopePath.unshiftContainer("body", declaration)
285
+ } else if (scopePath.isBlockStatement()) {
286
+ scopePath.unshiftContainer("body", declaration)
287
+ } else if (scopePath.isFunction()) {
288
+ scopePath.get("body").unshiftContainer("body", declaration)
289
+ } else if (scopePath.isForStatement() && t.isVariableDeclaration(scopePath.node.init)) {
290
+ scopePath.node.init.declarations.unshift(...declaration.declarations)
291
+ } else {
292
+ throw new Error(`Slim cannot create a runtime type scope for "${slots[0].name}"`)
293
+ }
294
+ }
295
+
296
+ traverse(ast, {
297
+ AssignmentExpression(path_) {
298
+ const { node } = path_
299
+ if (t.isMemberExpression(node.left)) {
300
+ const staticField = getStaticField(path_, node.left)
301
+ const fieldName = staticField?.name
302
+ const field = staticField?.definition
303
+ if (!field) return
304
+
305
+ const check = value => t.callExpression(t.identifier("__typed_static_field_check__"), [
306
+ t.cloneNode(node.left.object),
307
+ t.stringLiteral(fieldName),
308
+ value,
309
+ t.stringLiteral(field.displayName)
310
+ ])
311
+
312
+ if (node.operator === "=") {
313
+ node.right = check(node.right)
314
+ return
315
+ }
316
+
317
+ if (["&&=", "||=", "??="].includes(node.operator)) {
318
+ const operator = node.operator.slice(0, -1)
319
+ const assignment = t.assignmentExpression(
320
+ "=",
321
+ t.cloneNode(node.left),
322
+ check(node.right)
323
+ )
324
+ path_.replaceWith(t.logicalExpression(operator, t.cloneNode(node.left), assignment))
325
+ path_.skip()
326
+ return
327
+ }
328
+
329
+ const operator = node.operator.slice(0, -1)
330
+ node.operator = "="
331
+ node.right = check(t.binaryExpression(operator, t.cloneNode(node.left), node.right))
332
+ return
333
+ }
334
+
335
+ if (!t.isIdentifier(node.left)) return
336
+ const binding = typedBindings.get(path_.scope.getBinding(node.left.name))
337
+ if (!binding) return
338
+
339
+ if (node.operator === "=") {
340
+ node.right = buildTypedCheck(binding, node.right)
341
+ return
342
+ }
343
+
344
+ if (["&&=", "||=", "??="].includes(node.operator)) {
345
+ const operator = node.operator.slice(0, -1)
346
+ const assignment = t.assignmentExpression(
347
+ "=",
348
+ t.cloneNode(node.left),
349
+ buildTypedCheck(binding, node.right)
350
+ )
351
+ path_.replaceWith(t.logicalExpression(operator, t.cloneNode(node.left), assignment))
352
+ path_.skip()
353
+ return
354
+ }
355
+
356
+ const operator = node.operator.slice(0, -1)
357
+ node.operator = "="
358
+ node.right = buildTypedCheck(
359
+ binding,
360
+ t.binaryExpression(operator, t.cloneNode(node.left), node.right)
361
+ )
362
+ },
363
+
364
+ UpdateExpression(path_) {
365
+ const { node } = path_
366
+ if (t.isMemberExpression(node.argument)) {
367
+ const staticField = getStaticField(path_, node.argument)
368
+ const fieldName = staticField?.name
369
+ const field = staticField?.definition
370
+ if (!field) return
371
+
372
+ const previous = path_.scope.generateUidIdentifier("typedPrevious")
373
+ const nextValue = t.binaryExpression(
374
+ node.operator === "++" ? "+" : "-",
375
+ t.cloneNode(node.argument),
376
+ t.numericLiteral(1)
377
+ )
378
+ const check = t.callExpression(t.identifier("__typed_static_field_check__"), [
379
+ t.cloneNode(node.argument.object),
380
+ t.stringLiteral(fieldName),
381
+ nextValue,
382
+ t.stringLiteral(field.displayName)
383
+ ])
384
+ const assignment = t.assignmentExpression("=", t.cloneNode(node.argument), check)
385
+ const result = node.prefix ? t.cloneNode(node.argument) : t.cloneNode(previous)
386
+
387
+ path_.replaceWith(t.callExpression(
388
+ t.arrowFunctionExpression([], t.blockStatement([
389
+ t.variableDeclaration("const", [t.variableDeclarator(previous, t.cloneNode(node.argument))]),
390
+ t.expressionStatement(assignment),
391
+ t.returnStatement(result)
392
+ ])),
393
+ []
394
+ ))
395
+ path_.skip()
396
+ return
397
+ }
398
+
399
+ if (!t.isIdentifier(node.argument)) return
400
+
401
+ const binding = typedBindings.get(path_.scope.getBinding(node.argument.name))
402
+ if (!binding) return
403
+
404
+ const previous = path_.scope.generateUidIdentifier("typedPrevious")
405
+ const nextValue = t.binaryExpression(
406
+ node.operator === "++" ? "+" : "-",
407
+ t.cloneNode(node.argument),
408
+ t.numericLiteral(1)
409
+ )
410
+ const assignment = t.assignmentExpression(
411
+ "=",
412
+ t.cloneNode(node.argument),
413
+ buildTypedCheck(binding, nextValue)
414
+ )
415
+ const result = node.prefix ? t.cloneNode(node.argument) : t.cloneNode(previous)
416
+
417
+ path_.replaceWith(t.callExpression(
418
+ t.arrowFunctionExpression([], t.blockStatement([
419
+ t.variableDeclaration("const", [t.variableDeclarator(previous, t.cloneNode(node.argument))]),
420
+ t.expressionStatement(assignment),
421
+ t.returnStatement(result)
422
+ ])),
423
+ []
424
+ ))
425
+ path_.skip()
426
+ }
427
+ })
428
+ }
429
+
430
+ function resolvePath(raw, fromFile) {
431
+ return resolveSlimImport(raw, fromFile)
432
+ }
433
+
434
+ function getRuntimePath(sourceFile, entry) {
435
+ const distFile = getDistPath(sourceFile)
436
+ const runtimeAbs = path.resolve(`dist/external/${entry}`)
437
+ const rel = path.relative(path.dirname(distFile), runtimeAbs)
438
+ const relFixed = rel.replace(/\\/g, "/")
439
+ return relFixed.startsWith(".") ? relFixed : "./" + relFixed
440
+ }
441
+
442
+ // DOM globals select the full runtime; pure code uses the portable core.
443
+ const DOM_RUNTIME = new Set([
444
+ "htmlToVdom", "HTMLElement", "__html__",
445
+ "__flush_events__", "__bind_events__", "__lifecycle__",
446
+ "__create_host__", "__adopt_into__", "__define_element__"
447
+ ])
448
+
449
+ function usesDom(ast) {
450
+ let found = false
451
+
452
+ traverse(ast, {
453
+ Identifier(path_) {
454
+ if (!DOM_RUNTIME.has(path_.node.name)) return
455
+ found = true
456
+ path_.stop()
457
+ }
458
+ })
459
+
460
+ return found
461
+ }
462
+
463
+ // `use` mirrors `import`: `use { X } from Y` is a named import, `use * as X`
464
+ // a namespace import, and a bare `use X from Y` a default import — exactly like
465
+ // `import X from Y`. Named exports (including Slim's `export const`/`export func`)
466
+ // must therefore be brought in with braces: `use { X } from Y`.
467
+ //
468
+ // When `bareIsDefault` is false (the legacy `"uses": "named"` config style), a
469
+ // bare `use X from Y` instead lowers to a named import `import { X } from Y`.
470
+ function parseSpecifiers(name, bareIsDefault = true) {
471
+ const trimmed = name.trim()
472
+
473
+ const namespaceMatch = trimmed.match(/^\*\s+as\s+([\w$]+)$/)
474
+ if (namespaceMatch) {
475
+ return [t.importNamespaceSpecifier(t.identifier(namespaceMatch[1]))]
476
+ }
477
+
478
+ if (trimmed.startsWith("{")) {
479
+ const inner = trimmed.replace(/[{}]/g, "").trim()
480
+ return inner.split(",").map(part => {
481
+ const aliasParts = part.trim().split(/\s+as\s+/)
482
+ const imported = aliasParts[0].trim()
483
+ const local = (aliasParts[1] ?? aliasParts[0]).trim()
484
+ return t.importSpecifier(t.identifier(local), t.identifier(imported))
485
+ })
486
+ }
487
+
488
+ // `use X as Y from Z` renames a named export (there is no `import X as Y`).
489
+ const aliasMatch = trimmed.match(/^([\w$]+)\s+as\s+([\w$]+)$/)
490
+ if (aliasMatch) {
491
+ return [t.importSpecifier(t.identifier(aliasMatch[2]), t.identifier(aliasMatch[1]))]
492
+ }
493
+
494
+ if (!bareIsDefault) {
495
+ return [t.importSpecifier(t.identifier(trimmed), t.identifier(trimmed))]
496
+ }
497
+
498
+ return [t.importDefaultSpecifier(t.identifier(trimmed))]
499
+ }
500
+
501
+ function formatSyntaxError(err, originalCode, sourceFile, mapped) {
502
+ const loc = err.loc
503
+
504
+ if (!loc) {
505
+ const e = new Error(`SyntaxError: ${err.message}`)
506
+ e.slimSyntaxError = true
507
+ throw e
508
+ }
509
+
510
+ const preLineStarts = computeLineStarts(mapped.text)
511
+ const origLineStarts = computeLineStarts(originalCode)
512
+ const { line, column } = mapPreToOriginal(
513
+ mapped, preLineStarts, origLineStarts, loc.line - 1, loc.column
514
+ )
515
+
516
+ const lines = originalCode.split("\n")
517
+ const sourceLine = lines[line - 1] ?? ""
518
+ const indent = sourceLine.length - sourceLine.trimStart().length
519
+ const pointer = " ".repeat(Math.max(0, column - 1 - indent)) + "^"
520
+
521
+ const e = new Error([
522
+ `SyntaxError: ${err.reasonCode ?? "Unexpected token"}`,
523
+ ` at ${sourceFile}:${line}:${column}`,
524
+ "",
525
+ ` ${sourceLine.trim()}`,
526
+ ` ${pointer}`,
527
+ ].join("\n"))
528
+ e.slimSyntaxError = true
529
+ throw e
530
+ }
531
+
532
+ export function transform(code, sourceFile = "input.ps", options = {}) {
533
+ // `"uses": "named"` (or "legacy") restores the old bare-import style where a
534
+ // bare `use X from Y` is a named import; the default mirrors `import`.
535
+ const bareIsDefault = options.uses !== "named" && options.uses !== "legacy"
536
+ const asyncFunctions = new Set()
537
+ const imports = new Map()
538
+ const wildcards = []
539
+ const specifierSources = []
540
+
541
+ const { code: pre, mapped } = preprocess(code, sourceFile)
542
+
543
+ let ast
544
+ try {
545
+ ast = parse(pre, {
546
+ sourceType: "module",
547
+ plugins: ["jsx"]
548
+ })
549
+ } catch (err) {
550
+ if (err.code === "BABEL_PARSER_SYNTAX_ERROR") {
551
+ formatSyntaxError(err, code, sourceFile, mapped)
552
+ }
553
+ throw err
554
+ }
555
+
556
+ traverse(ast, {
557
+ FunctionDeclaration(path_) {
558
+ if (path_.node.async) {
559
+ asyncFunctions.add(path_.node.id?.name)
560
+ }
561
+ },
562
+ CallExpression(path_) {
563
+ const callee = path_.node.callee
564
+
565
+ if (t.isIdentifier(callee) && callee.name === "__use_all__") {
566
+ const [sourceNode] = path_.node.arguments
567
+ if (!t.isStringLiteral(sourceNode)) return
568
+ wildcards.push(resolvePath(sourceNode.value, sourceFile))
569
+ specifierSources.push({ spec: null, raw: sourceNode.value })
570
+ path_.remove()
571
+ return
572
+ }
573
+
574
+ if (t.isIdentifier(callee) && callee.name === "__use__") {
575
+ const [nameNode, sourceNode] = path_.node.arguments
576
+ if (!t.isStringLiteral(nameNode) || !t.isStringLiteral(sourceNode)) return
577
+ imports.set(nameNode.value, resolvePath(sourceNode.value, sourceFile))
578
+ specifierSources.push({ spec: nameNode.value, raw: sourceNode.value })
579
+ path_.remove()
580
+ }
581
+ },
582
+
583
+ ClassDeclaration(path_) {
584
+ if (!path_.node.superClass && imports.has("Component")) {
585
+ path_.node.superClass = t.identifier("Component")
586
+ }
587
+ },
588
+
589
+ ExpressionStatement(path_) {
590
+ const expr = path_.node.expression
591
+ if (
592
+ t.isCallExpression(expr) &&
593
+ t.isIdentifier(expr.callee) &&
594
+ asyncFunctions.has(expr.callee.name)
595
+ ) {
596
+ path_.node.expression = t.callExpression(
597
+ t.memberExpression(expr, t.identifier("catch")),
598
+ [t.identifier("__handle_async_error__")]
599
+ )
600
+ }
601
+ }
602
+ })
603
+
604
+ // Check annotations before lowering or writing output.
605
+ if (options.check !== false) {
606
+ const moduleImports = []
607
+ const moduleWildcards = []
608
+
609
+ for (const { spec, raw } of specifierSources) {
610
+ const slimSource = resolveSlimSource(raw, sourceFile)
611
+ if (!slimSource) continue
612
+
613
+ if (spec === null) {
614
+ moduleWildcards.push(slimSource)
615
+ continue
616
+ }
617
+
618
+ for (const specifier of parseSpecifiers(spec)) {
619
+ if (!t.isImportSpecifier(specifier)) continue
620
+ moduleImports.push({
621
+ local: specifier.local.name,
622
+ imported: t.isIdentifier(specifier.imported)
623
+ ? specifier.imported.name
624
+ : specifier.imported.value,
625
+ source: slimSource
626
+ })
627
+ }
628
+ }
629
+
630
+ const diagnostics = checkTypes(ast, {
631
+ mapped,
632
+ originalCode: code,
633
+ sourceFile,
634
+ imports: moduleImports,
635
+ wildcards: moduleWildcards
636
+ })
637
+ if (diagnostics.length > 0) {
638
+ const error = new Error(formatDiagnostics(diagnostics))
639
+ error.slimTypeErrors = diagnostics
640
+ throw error
641
+ }
642
+ }
643
+
644
+ const importedNames = new Set()
645
+ for (const name of imports.keys()) {
646
+ for (const specifier of parseSpecifiers(name, bareIsDefault)) {
647
+ importedNames.add(specifier.local.name)
648
+ }
649
+ }
650
+ const declarations = options.declarations ? emitDeclarations(ast) : null
651
+ const typedefs = options.jsdoc ? emitJsDoc(ast) : []
652
+
653
+ instrumentRuntimeTypes(ast, sourceFile, importedNames)
654
+
655
+ const runtimePath = getRuntimePath(sourceFile, usesDom(ast) ? "defaults.js" : "core.js")
656
+ const defaultImport = t.importDeclaration([], t.stringLiteral(runtimePath))
657
+
658
+ if (options.jsdoc) {
659
+ if (typedefs.length > 0) {
660
+ t.addComment(defaultImport, "leading", jsdocComment(typedefs), false)
661
+ }
662
+ t.addComment(defaultImport, "leading", " @ts-check", true)
663
+ }
664
+
665
+ const wildcardNodes = wildcards.flatMap((source) => {
666
+ const alias = "__" + source.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+|_+$/g, "") + "__"
667
+
668
+ return [
669
+ t.importDeclaration(
670
+ [t.importNamespaceSpecifier(t.identifier(alias))],
671
+ t.stringLiteral(source)
672
+ ),
673
+ t.expressionStatement(
674
+ t.callExpression(
675
+ t.memberExpression(t.identifier("Object"), t.identifier("assign")),
676
+ [t.identifier("globalThis"), t.identifier(alias)]
677
+ )
678
+ )
679
+ ]
680
+ })
681
+
682
+ const importNodes = [...imports.entries()].map(([name, source]) =>
683
+ t.importDeclaration(parseSpecifiers(name, bareIsDefault), t.stringLiteral(source))
684
+ )
685
+
686
+ const existingImports = ast.program.body.filter(n => t.isImportDeclaration(n))
687
+
688
+ const exportDeclarations = ast.program.body.filter(n =>
689
+ t.isExportNamedDeclaration(n) ||
690
+ t.isExportDefaultDeclaration(n) ||
691
+ t.isExportAllDeclaration(n)
692
+ )
693
+
694
+ const exportedNames = new Set()
695
+ for (const node of exportDeclarations) {
696
+ if (t.isExportNamedDeclaration(node) && !node.declaration && node.specifiers) {
697
+ for (const spec of node.specifiers) {
698
+ exportedNames.add(spec.local.name)
699
+ }
700
+ }
701
+ }
702
+
703
+ const slotDecls = []
704
+ const topLevel = []
705
+ const rest = []
706
+
707
+ for (const node of ast.program.body) {
708
+ if (t.isImportDeclaration(node)) continue
709
+ if (t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node) || t.isExportAllDeclaration(node)) continue
710
+
711
+ if (node._slimTypeSlots) {
712
+ slotDecls.push(node)
713
+ } else if (
714
+ (t.isFunctionDeclaration(node) || t.isClassDeclaration(node)) &&
715
+ node.id && exportedNames.has(node.id.name)
716
+ ) {
717
+ topLevel.push(node)
718
+ } else {
719
+ rest.push(node)
720
+ }
721
+ }
722
+
723
+ const tryBlock = t.tryStatement(
724
+ t.blockStatement(rest),
725
+ t.catchClause(
726
+ t.identifier("__err__"),
727
+ t.blockStatement([
728
+ t.expressionStatement(
729
+ t.callExpression(
730
+ t.identifier("__handle_sync_error__"),
731
+ [t.identifier("__err__")]
732
+ )
733
+ )
734
+ ])
735
+ )
736
+ )
737
+
738
+ ast.program.body = [
739
+ defaultImport,
740
+ ...wildcardNodes,
741
+ ...importNodes,
742
+ ...existingImports,
743
+ ...slotDecls,
744
+ ...topLevel,
745
+ ...exportDeclarations,
746
+ tryBlock
747
+ ]
748
+
749
+ const { code: output, map: generatedMap } = generate(
750
+ ast,
751
+ { sourceMaps: true, sourceFileName: PRE_SOURCE },
752
+ pre
753
+ )
754
+
755
+ const preMap = buildPreMap(mapped, code, sourceFile)
756
+ const finalMap = remapping(
757
+ generatedMap,
758
+ file => (file === PRE_SOURCE ? preMap : null)
759
+ )
760
+
761
+ const mapComment = `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(finalMap)).toString("base64")
762
+ }`
763
+
764
+ return { code: output + mapComment, declarations }
765
+ }