@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.
- package/README.md +666 -0
- package/package.json +55 -0
- package/packages/slim/.spm +7 -0
- package/packages/slim/converters/main.slim +106 -0
- package/packages/slim/helpers/array.slim +25 -0
- package/packages/slim/helpers/path.slim +3 -0
- package/packages/slim/helpers/request.slim +102 -0
- package/packages/slim/helpers/string.slim +27 -0
- package/packages/slim/main.slim +42 -0
- package/packages/slim/parse/main.slim +25 -0
- package/packages/slim/server/main.slim +423 -0
- package/packages/slim/time/main.slim +66 -0
- package/packages/slim/types/common.slim +6 -0
- package/packages/slim/types/formats.slim +23 -0
- package/packages/slim/types/hash.slim +6 -0
- package/packages/slim/types/mails.slim +3 -0
- package/packages/slim/types/numerical.slim +9 -0
- package/packages/slim/types/time.slim +3 -0
- package/run-dev-slim.js +133 -0
- package/run-slim.js +20 -0
- package/src/bin/api/github_auth.js +89 -0
- package/src/bin/api/github_get.js +139 -0
- package/src/bin/api/github_req.js +455 -0
- package/src/bin/api/lock.js +37 -0
- package/src/bin/api/spm.js +103 -0
- package/src/bin/api/storage.js +30 -0
- package/src/bin/cli.js +404 -0
- package/src/bin/config.default.json +5 -0
- package/src/bin/helpers.js +147 -0
- package/src/bin/parsers/spm.js +174 -0
- package/src/bin/spm.js +519 -0
- package/src/checker.js +926 -0
- package/src/compile.js +230 -0
- package/src/external/classErrors.js +202 -0
- package/src/external/client.js +38 -0
- package/src/external/core.js +861 -0
- package/src/external/defaults.js +25 -0
- package/src/external/helpers.js +541 -0
- package/src/external/slim-globals.d.ts +65 -0
- package/src/external/types.js +38 -0
- package/src/format.js +81 -0
- package/src/handlers/errorHandler.js +43 -0
- package/src/handlers/parser/components.js +250 -0
- package/src/handlers/parserHandler.js +793 -0
- package/src/jsdoc.js +273 -0
- package/src/lexer.js +174 -0
- package/src/modulePaths.js +74 -0
- package/src/parser.js +818 -0
- package/src/repl.js +32 -0
- package/src/sourcemap.js +0 -0
- package/src/test-runner.js +62 -0
- package/src/transform.js +765 -0
package/src/transform.js
ADDED
|
@@ -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
|
+
}
|