@kudzujs/core 0.5.4 → 0.5.5
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 +2 -2
- package/framework/build.mjs +102 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -281,7 +281,7 @@ const rows = items.map(item =>
|
|
|
281
281
|
return <ul>{rows}</ul>
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
-
The root may also be a top-level same
|
|
284
|
+
The root may also be a top-level row component declared in the same file or imported from a relative TypeScript module. Default, named, aliased, and named re-export imports are resolved at build time. Kudzu specializes each call, so projected props, callback props, and simple local calculations compile to the same intrinsic list template:
|
|
285
285
|
|
|
286
286
|
```tsx
|
|
287
287
|
function ItemRow({ name, done, onRemove }: {
|
|
@@ -306,7 +306,7 @@ const rows = items.map(item => <ItemRow
|
|
|
306
306
|
|
|
307
307
|
The original component remains reusable across multiple lists and ordinary JSX. No component function or component runtime is shipped to the browser. Kudzu emits initial items as static HTML, then adds, removes, updates, styles, conditional branches, and moves keyed elements directly. The map may appear directly in JSX or in one top-level immutable `const` rendered once as a JSX child. Existing keys move without remounting, preserving uncontrolled descendant state. Direct `item.<field>` reads use compact markers; derived item expressions compile to external ESM evaluators. Single-level item-local `&&` and ternary JSX conditions patch only their bounded branch and mount or unmount its handlers. Item-local handlers use direct DOM listeners and receive the latest JSON-safe item for their key, including after updates, additions, and reorders. The item remains stored once in shared list state; handler descriptors carry a placeholder that the list runtime fills when mounting or updating the keyed root.
|
|
308
308
|
|
|
309
|
-
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. The current syntax requires a local-state `.map`, one identifier callback parameter, one intrinsic JSX root or top-level
|
|
309
|
+
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. The current syntax requires a local-state `.map`, one identifier callback parameter, one intrinsic JSX root or top-level local or relative-imported row component, and `key={item.<field>}`. Row components accept destructured projected props and top-level single-`const` calculations before one intrinsic return. A list alias may only be rendered once and cannot be read by other JavaScript. Derived expressions must be pure and synchronous: item reads, literals, operators, templates, approved read-only string/array methods, deterministic `Math` methods, and `String`/`Number`/`Boolean` conversion are supported. Component state, imported helpers used inside calculations, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Package or namespace row imports, same-file exported rows, reusable aliases, prop spreads/defaults/rest, children, nested item conditions, lists, or component tags, refs, and `dangerouslySetInnerHTML` remain unsupported. Keyed rows must be placed inside an explicit `<tbody>`, `<thead>`, or `<tfoot>`.
|
|
310
310
|
|
|
311
311
|
## Normal JavaScript
|
|
312
312
|
|
package/framework/build.mjs
CHANGED
|
@@ -29,10 +29,11 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
29
29
|
const cssFiles = projectFiles.filter(file => file.endsWith(".css")).sort()
|
|
30
30
|
if (!sourceFiles.length) throw new Error("No TypeScript files found in src/")
|
|
31
31
|
const sourceFileSet = new Set(sourceFiles)
|
|
32
|
+
const sourceIndex = new Map(await Promise.all(sourceFiles.map(async file => [file, await readFile(file, "utf8")])))
|
|
32
33
|
|
|
33
34
|
const handlerModules = []
|
|
34
35
|
for (const file of sourceFiles) {
|
|
35
|
-
const handlerModule = await compile(file, sourceFileSet, base)
|
|
36
|
+
const handlerModule = await compile(file, sourceFileSet, sourceIndex, base)
|
|
36
37
|
if (handlerModule) handlerModules.push(handlerModule)
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -369,8 +370,8 @@ function escapeHtml(value) {
|
|
|
369
370
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
370
371
|
}
|
|
371
372
|
|
|
372
|
-
async function compile(file, sourceFiles, base) {
|
|
373
|
-
const source =
|
|
373
|
+
async function compile(file, sourceFiles, sourceIndex, base) {
|
|
374
|
+
const source = sourceIndex.get(file)
|
|
374
375
|
const nativeHandlers = []
|
|
375
376
|
const reactiveBindings = []
|
|
376
377
|
const listExpressions = []
|
|
@@ -384,7 +385,7 @@ async function compile(file, sourceFiles, base) {
|
|
|
384
385
|
jsx: ts.JsxEmit.ReactJSX,
|
|
385
386
|
jsxImportSource: "@kudzujs/core"
|
|
386
387
|
},
|
|
387
|
-
transformers: { before: [createKudzuTransformer(nativeHandlers, reactiveBindings, listExpressions, assetPath(base, `assets/${handlerPath}`), file, sourceFiles, clientImports)] },
|
|
388
|
+
transformers: { before: [createKudzuTransformer(nativeHandlers, reactiveBindings, listExpressions, assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, clientImports)] },
|
|
388
389
|
reportDiagnostics: true
|
|
389
390
|
})
|
|
390
391
|
|
|
@@ -413,12 +414,22 @@ async function compile(file, sourceFiles, base) {
|
|
|
413
414
|
return { path: handlerPath, code: moduleResult.outputText, hasNativeHandlers: nativeHandlers.length > 0, clientImports: [...clientImports] }
|
|
414
415
|
}
|
|
415
416
|
|
|
416
|
-
function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpressions, handlerUrl, file, sourceFiles, clientImports) {
|
|
417
|
+
function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpressions, handlerUrl, file, sourceFiles, sourceIndex, clientImports) {
|
|
417
418
|
return context => sourceFile => {
|
|
418
419
|
const factory = context.factory
|
|
419
420
|
sourceFile = normalizeRenderControlFlow(sourceFile, factory, context)
|
|
420
421
|
ts.setParentRecursive(sourceFile, false)
|
|
421
422
|
const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
|
|
423
|
+
const importedSources = new Map()
|
|
424
|
+
const importedSource = target => {
|
|
425
|
+
let imported = importedSources.get(target)
|
|
426
|
+
if (!imported) {
|
|
427
|
+
imported = normalizeRenderControlFlow(parseSourceFile(target, sourceIndex.get(target)), factory, context)
|
|
428
|
+
ts.setParentRecursive(imported, false)
|
|
429
|
+
importedSources.set(target, imported)
|
|
430
|
+
}
|
|
431
|
+
return imported
|
|
432
|
+
}
|
|
422
433
|
const settersByFunction = new Map()
|
|
423
434
|
const functions = new Map()
|
|
424
435
|
const components = new Map()
|
|
@@ -520,8 +531,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
520
531
|
}
|
|
521
532
|
collectRenderedLists(sourceFile)
|
|
522
533
|
const fail = (node, message) => {
|
|
523
|
-
|
|
524
|
-
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
534
|
+
throw sourceNodeError(node, sourceFile, message)
|
|
525
535
|
}
|
|
526
536
|
const rejectUnsupportedRenderControl = node => {
|
|
527
537
|
if (ts.isIfStatement(node) && containsRenderControl(node, jsxLocalsByFunction.get(nearestFunction(node)) ?? new Set())) {
|
|
@@ -540,13 +550,19 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
540
550
|
const componentSpecializations = new WeakMap()
|
|
541
551
|
const specializedDeclarations = new WeakSet()
|
|
542
552
|
for (const name of listComponentNames) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (
|
|
553
|
+
let component = components.get(name)
|
|
554
|
+
const local = Boolean(component)
|
|
555
|
+
if (!component) {
|
|
556
|
+
const binding = importBindings.get(name)
|
|
557
|
+
if (!binding || binding.kind === "namespace") fail(sourceFile, `Keyed list component ${name} must be declared locally or imported from a relative TypeScript module`)
|
|
558
|
+
const imported = binding.kind === "default" ? "default" : binding.imported
|
|
559
|
+
component = { function: resolveComponentExport(binding.target, imported, importedSource, sourceFiles), declaration: undefined }
|
|
560
|
+
}
|
|
561
|
+
if (local && isExportedDeclaration(component.declaration)) fail(component.declaration, `Keyed list component ${name} cannot be exported`)
|
|
546
562
|
const calls = jsxTagUses(sourceFile, name)
|
|
547
|
-
if (identifierReferenceCount(sourceFile, name) !== calls.length) fail(component.declaration, `Keyed list component ${name} may only be referenced as JSX`)
|
|
563
|
+
if (local && identifierReferenceCount(sourceFile, name) !== calls.length) fail(component.declaration, `Keyed list component ${name} may only be referenced as JSX`)
|
|
548
564
|
for (const call of calls) componentSpecializations.set(call, specializeComponentCall(call, component.function, sourceFile, factory, context, fail))
|
|
549
|
-
specializedDeclarations.add(component.declaration)
|
|
565
|
+
if (local) specializedDeclarations.add(component.declaration)
|
|
550
566
|
}
|
|
551
567
|
const renderedLists = new WeakMap()
|
|
552
568
|
for (const { node, parts: originalParts } of rawRenderedLists) {
|
|
@@ -603,11 +619,13 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
603
619
|
if (componentSpecializations.has(node)) return ts.visitNode(componentSpecializations.get(node).root, visitor)
|
|
604
620
|
|
|
605
621
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
606
|
-
|
|
622
|
+
const target = resolveSourceImport(file, node.moduleSpecifier.text, sourceFiles)
|
|
623
|
+
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral(relativeModulePath(compiledPath(file), compiledPath(target))), node.attributes)
|
|
607
624
|
}
|
|
608
625
|
|
|
609
626
|
if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
610
|
-
|
|
627
|
+
const target = resolveSourceImport(file, node.moduleSpecifier.text, sourceFiles)
|
|
628
|
+
return factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, factory.createStringLiteral(relativeModulePath(compiledPath(file), compiledPath(target))), node.attributes)
|
|
611
629
|
}
|
|
612
630
|
|
|
613
631
|
if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === "useState" && node.initializer.arguments.length === 1) {
|
|
@@ -677,7 +695,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
677
695
|
}
|
|
678
696
|
}
|
|
679
697
|
|
|
680
|
-
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && !isContextProviderValue(node, contexts) && !/^on/i.test(node.name.
|
|
698
|
+
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && !isContextProviderValue(node, contexts) && !/^on/i.test(node.name.text) && !["key", "ref", "dangerouslysetinnerhtml"].includes(node.name.text.toLowerCase())) {
|
|
681
699
|
const expression = node.initializer.expression
|
|
682
700
|
const setters = settersForNode(node, settersByFunction)
|
|
683
701
|
const usedStates = referencedStateNames(expression, setters)
|
|
@@ -690,15 +708,16 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
690
708
|
}
|
|
691
709
|
}
|
|
692
710
|
|
|
693
|
-
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.
|
|
711
|
+
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
|
|
694
712
|
const setters = settersForNode(node, settersByFunction)
|
|
695
713
|
const event = compileEvent(node.initializer.expression, setters, functions, factory, nativeHandlers, handlerUrl, listEventItems.get(node), importBindings, clientImports)
|
|
696
714
|
if (event) {
|
|
697
715
|
usesBehavior = true
|
|
698
716
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
|
|
699
717
|
}
|
|
718
|
+
if (ts.isIdentifier(node.initializer.expression) && isDestructuredParameter(node.initializer.expression, nearestFunction(node))) return node
|
|
700
719
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
701
|
-
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${node.name.
|
|
720
|
+
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${node.name.text} must reference a function`)
|
|
702
721
|
}
|
|
703
722
|
|
|
704
723
|
return ts.visitEachChild(node, visitor, context)
|
|
@@ -854,8 +873,7 @@ function keyedListParts(expression, setters) {
|
|
|
854
873
|
|
|
855
874
|
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions) {
|
|
856
875
|
const fail = (node, message) => {
|
|
857
|
-
|
|
858
|
-
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
876
|
+
throw sourceNodeError(node, sourceFile, message)
|
|
859
877
|
}
|
|
860
878
|
const root = parts.root
|
|
861
879
|
const item = parts.item
|
|
@@ -869,7 +887,7 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
869
887
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) validateElement(node)
|
|
870
888
|
if (node !== root && ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "map" && containsJsx(node)) fail(node, "Nested keyed lists are not supported")
|
|
871
889
|
if (ts.isJsxSpreadAttribute(node) && referencesIdentifier(node.expression, item)) fail(node, "Keyed list item spreads are not supported")
|
|
872
|
-
if (ts.isJsxAttribute(node) && /^on[A-Z]/.test(node.name.
|
|
890
|
+
if (ts.isJsxAttribute(node) && /^on[A-Z]/.test(node.name.text)) {
|
|
873
891
|
listEventItems.set(node, item)
|
|
874
892
|
return
|
|
875
893
|
}
|
|
@@ -888,9 +906,9 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
888
906
|
return
|
|
889
907
|
}
|
|
890
908
|
const field = directProperty(expression, item)
|
|
891
|
-
const isRootKey = ts.isJsxAttribute(node.parent) && node.parent.name.
|
|
909
|
+
const isRootKey = ts.isJsxAttribute(node.parent) && node.parent.name.text === "key"
|
|
892
910
|
if (field && ["__proto__", "constructor", "prototype"].includes(field)) fail(node, `Keyed list item property "${field}" is not supported`)
|
|
893
|
-
if (field && ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.
|
|
911
|
+
if (field && ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.text.toLowerCase())) fail(node, `Keyed list item ${node.parent.name.text} is not supported`)
|
|
894
912
|
if (isRootKey) return
|
|
895
913
|
if (field) {
|
|
896
914
|
listValues.set(node.expression, { field })
|
|
@@ -898,7 +916,7 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
898
916
|
}
|
|
899
917
|
if (referencesIdentifier(expression, item)) {
|
|
900
918
|
validateListExpression(expression, item, node, fail)
|
|
901
|
-
if (ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.
|
|
919
|
+
if (ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.text.toLowerCase())) fail(node, `Keyed list item ${node.parent.name.text} is not supported`)
|
|
902
920
|
listValues.set(node.expression, { item })
|
|
903
921
|
return
|
|
904
922
|
}
|
|
@@ -971,6 +989,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
971
989
|
|
|
972
990
|
function substituteClone(root, substitutions, factory, context) {
|
|
973
991
|
const visit = (node, shadowed = new Set()) => {
|
|
992
|
+
if (ts.isTypeNode(node)) return cloneAst(node, factory, context)
|
|
974
993
|
if (ts.isShorthandPropertyAssignment(node) && substitutions.has(node.name.text) && !shadowed.has(node.name.text)) {
|
|
975
994
|
return factory.createPropertyAssignment(cloneAst(node.name, factory, context), cloneAst(substitutions.get(node.name.text), factory, context))
|
|
976
995
|
}
|
|
@@ -1026,6 +1045,10 @@ function isFunctionLike(node) {
|
|
|
1026
1045
|
return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)
|
|
1027
1046
|
}
|
|
1028
1047
|
|
|
1048
|
+
function isDestructuredParameter(identifier, fn) {
|
|
1049
|
+
return fn?.parameters.some(parameter => ts.isObjectBindingPattern(parameter.name) && parameter.name.elements.some(element => ts.isIdentifier(element.name) && element.name.text === identifier.text)) ?? false
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1029
1052
|
function isExportedDeclaration(node) {
|
|
1030
1053
|
const statement = ts.isVariableDeclaration(node) ? node.parent?.parent : node
|
|
1031
1054
|
return statement?.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
|
|
@@ -1057,6 +1080,7 @@ const assignmentOperators = new Set([
|
|
|
1057
1080
|
|
|
1058
1081
|
function validateListExpression(expression, item, source, fail) {
|
|
1059
1082
|
const visit = node => {
|
|
1083
|
+
if (ts.isTypeNode(node)) return
|
|
1060
1084
|
if (ts.isElementAccessExpression(node) && referencesIdentifier(node.expression, item)) {
|
|
1061
1085
|
const key = node.argumentExpression
|
|
1062
1086
|
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(source, "Derived keyed list item computed properties require a direct string or numeric literal key")
|
|
@@ -1399,6 +1423,60 @@ function clientImportBindings(sourceFile, file, sourceFiles) {
|
|
|
1399
1423
|
return bindings
|
|
1400
1424
|
}
|
|
1401
1425
|
|
|
1426
|
+
function resolveComponentExport(file, exportName, getSource, sourceFiles, trail = []) {
|
|
1427
|
+
const key = `${file}:${exportName}`
|
|
1428
|
+
if (trail.includes(key)) throw new Error(`Imported keyed list component re-export cycle: ${[...trail, key].map(entry => relative(root, entry.slice(0, entry.lastIndexOf(":")))).join(" -> ")}`)
|
|
1429
|
+
const sourceFile = getSource(file)
|
|
1430
|
+
const nextTrail = [...trail, key]
|
|
1431
|
+
|
|
1432
|
+
for (const statement of sourceFile.statements) {
|
|
1433
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
1434
|
+
const isDefault = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)
|
|
1435
|
+
const isExported = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
1436
|
+
if (exportName === "default" && isDefault || exportName !== "default" && isExported && statement.name?.text === exportName) return statement
|
|
1437
|
+
}
|
|
1438
|
+
if (ts.isVariableStatement(statement) && statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword) && exportName !== "default") {
|
|
1439
|
+
const declaration = statement.declarationList.declarations.find(entry => ts.isIdentifier(entry.name) && entry.name.text === exportName)
|
|
1440
|
+
if (declaration?.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return declaration.initializer
|
|
1441
|
+
}
|
|
1442
|
+
if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) {
|
|
1443
|
+
const component = localComponentDeclaration(sourceFile, statement.expression.text)
|
|
1444
|
+
if (component) return component
|
|
1445
|
+
}
|
|
1446
|
+
if (ts.isExportDeclaration(statement) && ts.isNamedExports(statement.exportClause)) {
|
|
1447
|
+
const entry = statement.exportClause.elements.find(element => !element.isTypeOnly && element.name.text === exportName)
|
|
1448
|
+
if (!entry) continue
|
|
1449
|
+
const imported = (entry.propertyName ?? entry.name).text
|
|
1450
|
+
if (statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
1451
|
+
if (!statement.moduleSpecifier.text.startsWith(".")) throw sourceNodeError(statement, sourceFile, "Imported keyed list components must use relative TypeScript re-exports")
|
|
1452
|
+
const target = resolveSourceImport(file, statement.moduleSpecifier.text, sourceFiles)
|
|
1453
|
+
return resolveComponentExport(target, imported, getSource, sourceFiles, nextTrail)
|
|
1454
|
+
}
|
|
1455
|
+
const component = localComponentDeclaration(sourceFile, imported)
|
|
1456
|
+
if (component) return component
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
throw new Error(`${relative(root, file)} does not export a statically analyzable keyed list component named ${JSON.stringify(exportName)}`)
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function localComponentDeclaration(sourceFile, name) {
|
|
1463
|
+
for (const statement of sourceFile.statements) {
|
|
1464
|
+
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name) return statement
|
|
1465
|
+
if (ts.isVariableStatement(statement)) {
|
|
1466
|
+
const declaration = statement.declarationList.declarations.find(entry => ts.isIdentifier(entry.name) && entry.name.text === name)
|
|
1467
|
+
if (declaration?.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return declaration.initializer
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return undefined
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function sourceNodeError(node, fallbackSource, message) {
|
|
1474
|
+
const original = ts.getOriginalNode(node)
|
|
1475
|
+
const sourceFile = original.getSourceFile?.()?.fileName ? original.getSourceFile() : fallbackSource
|
|
1476
|
+
const position = sourceFile.getLineAndCharacterOfPosition(original.getStart(sourceFile))
|
|
1477
|
+
return new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1402
1480
|
function printClientImports(entries, handlerPath) {
|
|
1403
1481
|
const unique = new Map(entries.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry]))
|
|
1404
1482
|
const groups = Map.groupBy(unique.values(), entry => entry.target)
|
|
@@ -1474,7 +1552,7 @@ function resolveSourceImport(importer, specifier, sourceFiles) {
|
|
|
1474
1552
|
const stem = /\.(?:js|jsx|ts|tsx)$/.test(extension) ? base.slice(0, -extension.length) : base
|
|
1475
1553
|
const candidates = extension === ".ts" || extension === ".tsx"
|
|
1476
1554
|
? [base]
|
|
1477
|
-
: [`${stem}.ts`, `${stem}.tsx
|
|
1555
|
+
: [`${stem}.ts`, `${stem}.tsx`, join(stem, "index.ts"), join(stem, "index.tsx")]
|
|
1478
1556
|
const matches = candidates.filter(candidate => sourceFiles.has(candidate))
|
|
1479
1557
|
if (matches.length !== 1) throw new Error(`${relative(root, importer)} Relative import ${JSON.stringify(specifier)} must resolve to one TypeScript file in src/`)
|
|
1480
1558
|
return matches[0]
|
|
@@ -1667,11 +1745,6 @@ function numericExpression(factory, value, negative) {
|
|
|
1667
1745
|
return negative ? factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, literal) : literal
|
|
1668
1746
|
}
|
|
1669
1747
|
|
|
1670
|
-
function modulePath(value) {
|
|
1671
|
-
if (/\.(?:ts|tsx|js|jsx)$/.test(value)) return value.replace(/\.(?:ts|tsx|js|jsx)$/, ".mjs")
|
|
1672
|
-
return `${value}.mjs`
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
1748
|
function compiledPath(file) {
|
|
1676
1749
|
return join(workDirectory, relative(sourceDirectory, file)).replace(/\.(?:ts|tsx)$/, ".mjs")
|
|
1677
1750
|
}
|