@kudzujs/core 0.5.0 → 0.5.2
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 +27 -2
- package/framework/build.mjs +206 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -264,9 +264,32 @@ const rows = items.map(item =>
|
|
|
264
264
|
return <ul>{rows}</ul>
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
The root may also be a top-level same-file row component. Kudzu specializes each call at build time, so projected props, callback props, and simple local calculations compile to the same intrinsic list template:
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
```tsx
|
|
270
|
+
function ItemRow({ name, done, onRemove }: {
|
|
271
|
+
name: string
|
|
272
|
+
done: boolean
|
|
273
|
+
onRemove: () => void
|
|
274
|
+
}) {
|
|
275
|
+
const className = done ? "done" : "active"
|
|
276
|
+
return <li className={className}>
|
|
277
|
+
{name}
|
|
278
|
+
<button onClick={() => onRemove()}>Remove</button>
|
|
279
|
+
</li>
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const rows = items.map(item => <ItemRow
|
|
283
|
+
key={item.id}
|
|
284
|
+
name={item.name}
|
|
285
|
+
done={item.done}
|
|
286
|
+
onRemove={() => setItems(items.filter(entry => entry.id !== item.id))}
|
|
287
|
+
/>)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
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.
|
|
291
|
+
|
|
292
|
+
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 same-file 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, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Exported or imported row components, 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>`.
|
|
270
293
|
|
|
271
294
|
## Normal JavaScript
|
|
272
295
|
|
|
@@ -438,6 +461,8 @@ The list starts with 1,000 keyed items, then updates every label, reverses the o
|
|
|
438
461
|
| Svelte CSR | No | 12.9 KB | 33.1 KB | 828 ms | 5.8 ms | 38.9 ms | 4.0 ms | 5.9 ms | 54.6 ms |
|
|
439
462
|
| Qwik CSR | No | 22.2 KB | 64.1 KB | 594 ms | 9.1 ms | 22.2 ms | 30.8 ms | 19.0 ms | 81.1 ms |
|
|
440
463
|
|
|
464
|
+
An intrinsic-root versus projected-prop row-component A/B build produced byte-for-byte identical `dist` output: 5,175 B JS gzip and 61,731 B total. Seven interleaved clean builds measured 467 ms and 455 ms. Browser operation medians totaled 23.7 ms and 23.9 ms respectively; because the deployed HTML and JavaScript are identical, the 0.2 ms difference is measurement variance rather than component runtime overhead.
|
|
465
|
+
|
|
441
466
|
Astro is the hand-authored native DOM baseline in the interactive fixtures. React, Vue, Svelte, and Qwik used client-rendered fixtures, while Kudzu and Astro emitted initial HTML; Qwik therefore did not exercise its SSR resumability advantage. Kudzu's keyed-list operations total 23.2 ms, 10.7 ms behind the hand-authored Astro baseline and 7.1 ms ahead of React across all four operations.
|
|
442
467
|
|
|
443
468
|
Benchmark snapshot collected on July 22, 2026 with Node 24.14.0 on an Intel i5-9500. These results compare the selected one-page fixtures, not ecosystem maturity, browser interaction speed beyond the listed operations, or each framework's full rendering options. Build times vary with machine load and filesystem cache.
|
package/framework/build.mjs
CHANGED
|
@@ -395,6 +395,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
395
395
|
const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
|
|
396
396
|
const settersByFunction = new Map()
|
|
397
397
|
const functions = new Map()
|
|
398
|
+
const components = new Map()
|
|
398
399
|
const jsxLocalDeclarations = new Map()
|
|
399
400
|
const jsxLocalsByFunction = new Map()
|
|
400
401
|
const listLocalDeclarations = new WeakSet()
|
|
@@ -420,9 +421,13 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
420
421
|
}
|
|
421
422
|
}
|
|
422
423
|
}
|
|
423
|
-
if (ts.isFunctionDeclaration(node) && node.name)
|
|
424
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
425
|
+
functions.set(node.name.text, node)
|
|
426
|
+
if (node.parent === sourceFile) components.set(node.name.text, { function: node, declaration: node })
|
|
427
|
+
}
|
|
424
428
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
|
425
429
|
functions.set(node.name.text, node.initializer)
|
|
430
|
+
if (node.parent?.parent?.parent === sourceFile) components.set(node.name.text, { function: node.initializer, declaration: node })
|
|
426
431
|
}
|
|
427
432
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isTopLevelConst(node)) {
|
|
428
433
|
const owner = nearestFunction(node)
|
|
@@ -466,8 +471,66 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
466
471
|
if (uses.length) listLocalUses.set(uses[0], parts)
|
|
467
472
|
}
|
|
468
473
|
}
|
|
474
|
+
const rawRenderedLists = []
|
|
475
|
+
const collectRenderedLists = node => {
|
|
476
|
+
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
477
|
+
const parts = listLocalUses.get(node) ?? keyedListParts(node.expression, settersForNode(node, settersByFunction))
|
|
478
|
+
if (parts) rawRenderedLists.push({ node, parts })
|
|
479
|
+
}
|
|
480
|
+
ts.forEachChild(node, collectRenderedLists)
|
|
481
|
+
}
|
|
482
|
+
collectRenderedLists(sourceFile)
|
|
483
|
+
const fail = (node, message) => {
|
|
484
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
485
|
+
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
486
|
+
}
|
|
487
|
+
const listComponentNames = new Set(rawRenderedLists.flatMap(({ parts }) => {
|
|
488
|
+
const tag = jsxTagName(parts.root)
|
|
489
|
+
return tag && ts.isIdentifier(tag) && tag.text[0] === tag.text[0].toUpperCase() ? [tag.text] : []
|
|
490
|
+
}))
|
|
491
|
+
const componentSpecializations = new WeakMap()
|
|
492
|
+
const specializedDeclarations = new WeakSet()
|
|
493
|
+
for (const name of listComponentNames) {
|
|
494
|
+
const component = components.get(name)
|
|
495
|
+
if (!component) fail(sourceFile, `Keyed list component ${name} must be declared at the top level in the same file`)
|
|
496
|
+
if (isExportedDeclaration(component.declaration)) fail(component.declaration, `Keyed list component ${name} cannot be exported`)
|
|
497
|
+
const calls = jsxTagUses(sourceFile, name)
|
|
498
|
+
if (identifierReferenceCount(sourceFile, name) !== calls.length) fail(component.declaration, `Keyed list component ${name} may only be referenced as JSX`)
|
|
499
|
+
for (const call of calls) componentSpecializations.set(call, specializeComponentCall(call, component.function, sourceFile, factory, context, fail))
|
|
500
|
+
specializedDeclarations.add(component.declaration)
|
|
501
|
+
}
|
|
502
|
+
const renderedLists = new WeakMap()
|
|
503
|
+
for (const { node, parts: originalParts } of rawRenderedLists) {
|
|
504
|
+
if (keyedListParentTag(node) === "table") throw new Error("Keyed table rows must be wrapped in <tbody>, <thead>, or <tfoot>")
|
|
505
|
+
const specialization = componentSpecializations.get(originalParts.root)
|
|
506
|
+
const root = specialization?.root ?? originalParts.root
|
|
507
|
+
const callback = root === originalParts.root ? originalParts.callback : factory.updateArrowFunction(
|
|
508
|
+
originalParts.callback,
|
|
509
|
+
originalParts.callback.modifiers,
|
|
510
|
+
originalParts.callback.typeParameters,
|
|
511
|
+
originalParts.callback.parameters,
|
|
512
|
+
originalParts.callback.type,
|
|
513
|
+
originalParts.callback.equalsGreaterThanToken,
|
|
514
|
+
root
|
|
515
|
+
)
|
|
516
|
+
if (callback !== originalParts.callback) {
|
|
517
|
+
ts.setParentRecursive(callback, false)
|
|
518
|
+
callback.parent = originalParts.callback.parent
|
|
519
|
+
}
|
|
520
|
+
const parts = { ...originalParts, root, callback }
|
|
521
|
+
for (const calculation of specialization?.calculations ?? []) {
|
|
522
|
+
ts.setParentRecursive(calculation, false)
|
|
523
|
+
calculation.parent = callback
|
|
524
|
+
validateListExpression(calculation, parts.item, originalParts.root, fail)
|
|
525
|
+
}
|
|
526
|
+
validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions)
|
|
527
|
+
renderedLists.set(node, parts)
|
|
528
|
+
}
|
|
469
529
|
|
|
470
530
|
const visitor = node => {
|
|
531
|
+
if (specializedDeclarations.has(node)) return node
|
|
532
|
+
if (componentSpecializations.has(node)) return ts.visitNode(componentSpecializations.get(node).root, visitor)
|
|
533
|
+
|
|
471
534
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
472
535
|
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral(modulePath(node.moduleSpecifier.text)), node.attributes)
|
|
473
536
|
}
|
|
@@ -523,10 +586,8 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
523
586
|
}
|
|
524
587
|
|
|
525
588
|
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
526
|
-
const listParts =
|
|
589
|
+
const listParts = renderedLists.get(node)
|
|
527
590
|
if (listParts) {
|
|
528
|
-
if (keyedListParentTag(node) === "table") throw new Error("Keyed table rows must be wrapped in <tbody>, <thead>, or <tfoot>")
|
|
529
|
-
validateKeyedList(listParts, sourceFile, settersForNode(node, settersByFunction), listValues, listEventItems, listConditions)
|
|
530
591
|
usesBehavior = true
|
|
531
592
|
usesList = true
|
|
532
593
|
return factory.updateJsxExpression(node, factory.createCallExpression(factory.createIdentifier("__kList"), undefined, [
|
|
@@ -628,11 +689,13 @@ function keyedListParts(expression, setters) {
|
|
|
628
689
|
return { state, callback, root, item: callback.parameters[0].name.text, keyField: field }
|
|
629
690
|
}
|
|
630
691
|
|
|
631
|
-
function validateKeyedList(parts, sourceFile,
|
|
692
|
+
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions) {
|
|
632
693
|
const fail = (node, message) => {
|
|
633
694
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
634
695
|
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
635
696
|
}
|
|
697
|
+
const root = parts.root
|
|
698
|
+
const item = parts.item
|
|
636
699
|
const validateElement = node => {
|
|
637
700
|
const tag = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName
|
|
638
701
|
if (!ts.isIdentifier(tag) || tag.text[0] !== tag.text[0].toLowerCase()) fail(node, "Keyed list items must use intrinsic JSX elements")
|
|
@@ -641,10 +704,10 @@ function validateKeyedList(parts, sourceFile, setters, listValues, listEventItem
|
|
|
641
704
|
const visit = node => {
|
|
642
705
|
if (ts.isJsxFragment(node)) fail(node, "Fragments are not supported in keyed lists")
|
|
643
706
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) validateElement(node)
|
|
644
|
-
if (node !==
|
|
645
|
-
if (ts.isJsxSpreadAttribute(node) && referencesIdentifier(node.expression,
|
|
707
|
+
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")
|
|
708
|
+
if (ts.isJsxSpreadAttribute(node) && referencesIdentifier(node.expression, item)) fail(node, "Keyed list item spreads are not supported")
|
|
646
709
|
if (ts.isJsxAttribute(node) && /^on[A-Z]/.test(node.name.getText())) {
|
|
647
|
-
listEventItems.set(node,
|
|
710
|
+
listEventItems.set(node, item)
|
|
648
711
|
return
|
|
649
712
|
}
|
|
650
713
|
if (ts.isJsxExpression(node) && node.expression) {
|
|
@@ -652,16 +715,16 @@ function validateKeyedList(parts, sourceFile, setters, listValues, listEventItem
|
|
|
652
715
|
const condition = conditionalParts(expression)
|
|
653
716
|
if (condition && containsJsx(expression)) {
|
|
654
717
|
if (conditionDepth) fail(node, "Nested item conditions are not supported in keyed lists")
|
|
655
|
-
if (!referencesIdentifier(condition.condition,
|
|
656
|
-
validateListExpression(condition.condition,
|
|
657
|
-
listConditions.set(node.expression, { ...condition, item
|
|
718
|
+
if (!referencesIdentifier(condition.condition, item)) fail(node, "Keyed list item conditions must read the item")
|
|
719
|
+
validateListExpression(condition.condition, item, node, fail)
|
|
720
|
+
listConditions.set(node.expression, { ...condition, item })
|
|
658
721
|
conditionDepth++
|
|
659
722
|
visit(condition.truthy)
|
|
660
723
|
visit(condition.falsy)
|
|
661
724
|
conditionDepth--
|
|
662
725
|
return
|
|
663
726
|
}
|
|
664
|
-
const field = directProperty(expression,
|
|
727
|
+
const field = directProperty(expression, item)
|
|
665
728
|
const isRootKey = ts.isJsxAttribute(node.parent) && node.parent.name.getText() === "key"
|
|
666
729
|
if (field && ["__proto__", "constructor", "prototype"].includes(field)) fail(node, `Keyed list item property "${field}" is not supported`)
|
|
667
730
|
if (field && ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.getText().toLowerCase())) fail(node, `Keyed list item ${node.parent.name.getText()} is not supported`)
|
|
@@ -670,16 +733,143 @@ function validateKeyedList(parts, sourceFile, setters, listValues, listEventItem
|
|
|
670
733
|
listValues.set(node.expression, { field })
|
|
671
734
|
return
|
|
672
735
|
}
|
|
673
|
-
if (referencesIdentifier(expression,
|
|
674
|
-
validateListExpression(expression,
|
|
736
|
+
if (referencesIdentifier(expression, item)) {
|
|
737
|
+
validateListExpression(expression, item, node, fail)
|
|
675
738
|
if (ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.getText().toLowerCase())) fail(node, `Keyed list item ${node.parent.name.getText()} is not supported`)
|
|
676
|
-
listValues.set(node.expression, { item
|
|
739
|
+
listValues.set(node.expression, { item })
|
|
677
740
|
return
|
|
678
741
|
}
|
|
679
742
|
}
|
|
680
743
|
ts.forEachChild(node, visit)
|
|
681
744
|
}
|
|
682
|
-
visit(
|
|
745
|
+
visit(root)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function specializeComponentCall(call, component, sourceFile, factory, context, fail) {
|
|
749
|
+
if (component.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || component.asteriskToken) fail(component, "Keyed list components must be synchronous")
|
|
750
|
+
if (component.parameters.length !== 1 || !ts.isObjectBindingPattern(component.parameters[0].name)) fail(component, "Keyed list components must use one destructured props parameter")
|
|
751
|
+
if (ts.isJsxElement(call) && call.children.some(child => !ts.isJsxText(child) || child.text.trim())) fail(call, "Keyed list component children are not supported")
|
|
752
|
+
const callAttributes = ts.isJsxElement(call) ? call.openingElement.attributes : call.attributes
|
|
753
|
+
const props = new Map()
|
|
754
|
+
let key
|
|
755
|
+
for (const attribute of callAttributes.properties) {
|
|
756
|
+
if (ts.isJsxSpreadAttribute(attribute)) fail(attribute, "Keyed list component prop spreads are not supported")
|
|
757
|
+
const name = attribute.name.getText()
|
|
758
|
+
if (props.has(name) || name === "key" && key) fail(attribute, `Duplicate keyed list component prop "${name}"`)
|
|
759
|
+
const value = !attribute.initializer
|
|
760
|
+
? factory.createTrue()
|
|
761
|
+
: ts.isStringLiteral(attribute.initializer)
|
|
762
|
+
? factory.createStringLiteral(attribute.initializer.text)
|
|
763
|
+
: ts.isJsxExpression(attribute.initializer) && attribute.initializer.expression
|
|
764
|
+
? attribute.initializer.expression
|
|
765
|
+
: factory.createIdentifier("undefined")
|
|
766
|
+
if (name === "key") key = attribute
|
|
767
|
+
else props.set(name, value)
|
|
768
|
+
}
|
|
769
|
+
const substitutions = new Map()
|
|
770
|
+
const acceptedProps = new Set()
|
|
771
|
+
for (const element of component.parameters[0].name.elements) {
|
|
772
|
+
if (element.dotDotDotToken || element.initializer || !ts.isIdentifier(element.name)) fail(element, "Keyed list component props cannot use rest, defaults, or nested destructuring")
|
|
773
|
+
const prop = (element.propertyName ?? element.name).getText()
|
|
774
|
+
acceptedProps.add(prop)
|
|
775
|
+
substitutions.set(element.name.text, props.get(prop) ?? factory.createIdentifier("undefined"))
|
|
776
|
+
}
|
|
777
|
+
for (const prop of props.keys()) if (!acceptedProps.has(prop)) fail(call, `Unknown keyed list component prop "${prop}"`)
|
|
778
|
+
|
|
779
|
+
let returned
|
|
780
|
+
const calculations = []
|
|
781
|
+
if (!ts.isBlock(component.body)) {
|
|
782
|
+
returned = component.body
|
|
783
|
+
} else {
|
|
784
|
+
const statements = [...component.body.statements]
|
|
785
|
+
const last = statements.pop()
|
|
786
|
+
if (!last || !ts.isReturnStatement(last) || !last.expression) fail(component.body, "Keyed list component must end with one JSX return")
|
|
787
|
+
for (const statement of statements) {
|
|
788
|
+
if (!ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0 || statement.declarationList.declarations.length !== 1) fail(statement, "Keyed list component locals must be single const declarations")
|
|
789
|
+
const declaration = statement.declarationList.declarations[0]
|
|
790
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, "Keyed list component locals must be initialized identifiers")
|
|
791
|
+
const calculation = substituteClone(declaration.initializer, substitutions, factory, context)
|
|
792
|
+
calculations.push(calculation)
|
|
793
|
+
substitutions.set(declaration.name.text, calculation)
|
|
794
|
+
}
|
|
795
|
+
returned = last.expression
|
|
796
|
+
}
|
|
797
|
+
let root = unwrapExpression(substituteClone(returned, substitutions, factory, context))
|
|
798
|
+
if (!ts.isJsxElement(root) && !ts.isJsxSelfClosingElement(root)) fail(returned, "Keyed list component must return one JSX element")
|
|
799
|
+
const tag = jsxTagName(root)
|
|
800
|
+
if (!ts.isIdentifier(tag) || tag.text[0] !== tag.text[0].toLowerCase()) fail(returned, "Keyed list component must directly return an intrinsic JSX element")
|
|
801
|
+
const rootAttributes = ts.isJsxElement(root) ? root.openingElement.attributes : root.attributes
|
|
802
|
+
if (rootAttributes.properties.some(attribute => ts.isJsxAttribute(attribute) && attribute.name.text === "key")) fail(root, "Keyed list component intrinsic root cannot declare key")
|
|
803
|
+
if (key) root = addJsxAttribute(root, cloneAst(key, factory, context), factory)
|
|
804
|
+
ts.setParentRecursive(root, false)
|
|
805
|
+
root.parent = call.parent
|
|
806
|
+
return { root, calculations }
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function substituteClone(root, substitutions, factory, context) {
|
|
810
|
+
const visit = (node, shadowed = new Set()) => {
|
|
811
|
+
if (ts.isShorthandPropertyAssignment(node) && substitutions.has(node.name.text) && !shadowed.has(node.name.text)) {
|
|
812
|
+
return factory.createPropertyAssignment(cloneAst(node.name, factory, context), cloneAst(substitutions.get(node.name.text), factory, context))
|
|
813
|
+
}
|
|
814
|
+
if (ts.isIdentifier(node) && substitutions.has(node.text) && !shadowed.has(node.text) && isReferenceIdentifier(node) && !isJsxSyntaxIdentifier(node)) {
|
|
815
|
+
return cloneAst(substitutions.get(node.text), factory, context)
|
|
816
|
+
}
|
|
817
|
+
const nextShadowed = isFunctionLike(node)
|
|
818
|
+
? new Set([...shadowed, ...node.parameters.flatMap(parameter => bindingNames(parameter.name))])
|
|
819
|
+
: shadowed
|
|
820
|
+
const clone = factory.cloneNode(node)
|
|
821
|
+
ts.setTextRange(clone, node)
|
|
822
|
+
ts.setOriginalNode(clone, node)
|
|
823
|
+
return ts.visitEachChild(clone, child => visit(child, nextShadowed), context)
|
|
824
|
+
}
|
|
825
|
+
return visit(root)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function cloneAst(root, factory, context) {
|
|
829
|
+
const visit = node => {
|
|
830
|
+
const clone = factory.cloneNode(node)
|
|
831
|
+
ts.setTextRange(clone, node)
|
|
832
|
+
ts.setOriginalNode(clone, node)
|
|
833
|
+
return ts.visitEachChild(clone, visit, context)
|
|
834
|
+
}
|
|
835
|
+
return visit(root)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function addJsxAttribute(root, attribute, factory) {
|
|
839
|
+
if (ts.isJsxSelfClosingElement(root)) {
|
|
840
|
+
return factory.updateJsxSelfClosingElement(root, root.tagName, root.typeArguments, factory.updateJsxAttributes(root.attributes, [attribute, ...root.attributes.properties]))
|
|
841
|
+
}
|
|
842
|
+
const opening = factory.updateJsxOpeningElement(root.openingElement, root.openingElement.tagName, root.openingElement.typeArguments, factory.updateJsxAttributes(root.openingElement.attributes, [attribute, ...root.openingElement.attributes.properties]))
|
|
843
|
+
return factory.updateJsxElement(root, opening, root.children, root.closingElement)
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function jsxTagName(node) {
|
|
847
|
+
return ts.isJsxElement(node) ? node.openingElement.tagName : ts.isJsxSelfClosingElement(node) ? node.tagName : undefined
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function isJsxSyntaxIdentifier(node) {
|
|
851
|
+
const parent = node.parent
|
|
852
|
+
return (ts.isJsxOpeningElement(parent) || ts.isJsxClosingElement(parent) || ts.isJsxSelfClosingElement(parent)) && parent.tagName === node || ts.isJsxAttribute(parent) && parent.name === node
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function isFunctionLike(node) {
|
|
856
|
+
return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function isExportedDeclaration(node) {
|
|
860
|
+
const statement = ts.isVariableDeclaration(node) ? node.parent?.parent : node
|
|
861
|
+
return statement?.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function jsxTagUses(root, name) {
|
|
865
|
+
const uses = []
|
|
866
|
+
const visit = node => {
|
|
867
|
+
const tag = ts.isJsxElement(node) ? node.openingElement.tagName : ts.isJsxSelfClosingElement(node) ? node.tagName : undefined
|
|
868
|
+
if (tag && ts.isIdentifier(tag) && tag.text === name) uses.push(node)
|
|
869
|
+
ts.forEachChild(node, visit)
|
|
870
|
+
}
|
|
871
|
+
visit(root)
|
|
872
|
+
return uses
|
|
683
873
|
}
|
|
684
874
|
|
|
685
875
|
const pureListMethods = new Set(["at", "charAt", "charCodeAt", "concat", "endsWith", "includes", "indexOf", "join", "lastIndexOf", "padEnd", "padStart", "repeat", "replace", "replaceAll", "slice", "startsWith", "substring", "toLowerCase", "toUpperCase", "trim", "trimEnd", "trimStart"])
|