@kudzujs/core 0.5.1 → 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 +20 -7
- package/framework/build.mjs +171 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -264,19 +264,32 @@ const rows = items.map(item =>
|
|
|
264
264
|
return <ul>{rows}</ul>
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
The root may also be a same-file row component
|
|
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({
|
|
271
|
-
|
|
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>
|
|
272
280
|
}
|
|
273
281
|
|
|
274
|
-
const rows = items.map(item => <ItemRow
|
|
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
|
+
/>)
|
|
275
288
|
```
|
|
276
289
|
|
|
277
|
-
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.
|
|
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.
|
|
278
291
|
|
|
279
|
-
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 same-file row component, and `key={item.<field>}`.
|
|
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>`.
|
|
280
293
|
|
|
281
294
|
## Normal JavaScript
|
|
282
295
|
|
|
@@ -448,7 +461,7 @@ The list starts with 1,000 keyed items, then updates every label, reverses the o
|
|
|
448
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 |
|
|
449
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 |
|
|
450
463
|
|
|
451
|
-
An intrinsic-root versus 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
|
|
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.
|
|
452
465
|
|
|
453
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.
|
|
454
467
|
|
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,21 +471,66 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
466
471
|
if (uses.length) listLocalUses.set(uses[0], parts)
|
|
467
472
|
}
|
|
468
473
|
}
|
|
469
|
-
const
|
|
474
|
+
const rawRenderedLists = []
|
|
470
475
|
const collectRenderedLists = node => {
|
|
471
476
|
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
472
477
|
const parts = listLocalUses.get(node) ?? keyedListParts(node.expression, settersForNode(node, settersByFunction))
|
|
473
|
-
if (parts) {
|
|
474
|
-
if (keyedListParentTag(node) === "table") throw new Error("Keyed table rows must be wrapped in <tbody>, <thead>, or <tfoot>")
|
|
475
|
-
validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions, functions)
|
|
476
|
-
renderedLists.set(node, parts)
|
|
477
|
-
}
|
|
478
|
+
if (parts) rawRenderedLists.push({ node, parts })
|
|
478
479
|
}
|
|
479
480
|
ts.forEachChild(node, collectRenderedLists)
|
|
480
481
|
}
|
|
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
|
+
}
|
|
482
529
|
|
|
483
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
|
+
|
|
484
534
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
485
535
|
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral(modulePath(node.moduleSpecifier.text)), node.attributes)
|
|
486
536
|
}
|
|
@@ -639,35 +689,13 @@ function keyedListParts(expression, setters) {
|
|
|
639
689
|
return { state, callback, root, item: callback.parameters[0].name.text, keyField: field }
|
|
640
690
|
}
|
|
641
691
|
|
|
642
|
-
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions
|
|
692
|
+
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions) {
|
|
643
693
|
const fail = (node, message) => {
|
|
644
694
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
645
695
|
throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
646
696
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const rootTag = ts.isJsxElement(root) ? root.openingElement.tagName : root.tagName
|
|
650
|
-
if (ts.isIdentifier(rootTag) && rootTag.text[0] === rootTag.text[0].toUpperCase()) {
|
|
651
|
-
const component = functions.get(rootTag.text)
|
|
652
|
-
if (!component) fail(root, `Keyed list component ${rootTag.text} must be declared in the same file`)
|
|
653
|
-
const uses = jsxTagUses(sourceFile, rootTag.text)
|
|
654
|
-
if (uses.length !== 1 || uses[0] !== root) fail(root, `Keyed list component ${rootTag.text} may only be used as this list root`)
|
|
655
|
-
const attributes = ts.isJsxElement(root) ? root.openingElement.attributes : root.attributes
|
|
656
|
-
let itemProp
|
|
657
|
-
for (const attribute of attributes.properties) {
|
|
658
|
-
if (ts.isJsxSpreadAttribute(attribute)) {
|
|
659
|
-
if (referencesIdentifier(attribute.expression, item)) fail(attribute, "Keyed list item spreads are not supported")
|
|
660
|
-
continue
|
|
661
|
-
}
|
|
662
|
-
if (attribute.name.getText() === "key" || !attribute.initializer || !ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression || !referencesIdentifier(attribute.initializer.expression, item)) continue
|
|
663
|
-
if (!ts.isIdentifier(attribute.initializer.expression) || attribute.initializer.expression.text !== item || itemProp) fail(attribute, `Keyed list component ${rootTag.text} must receive the whole item through one direct prop`)
|
|
664
|
-
itemProp = attribute.name.getText()
|
|
665
|
-
}
|
|
666
|
-
if (ts.isJsxElement(root) && root.children.some(child => referencesIdentifier(child, item))) fail(root, `Keyed list component ${rootTag.text} must receive the whole item through one direct prop`)
|
|
667
|
-
if (!itemProp) fail(root, `Keyed list component ${rootTag.text} must receive the whole item through one direct prop`)
|
|
668
|
-
item = componentItemParameter(component, itemProp, node => fail(node, `Keyed list component ${rootTag.text} must destructure its item prop`))
|
|
669
|
-
root = componentJsxRoot(component, node => fail(node, `Keyed list component ${rootTag.text} must return one JSX element`))
|
|
670
|
-
}
|
|
697
|
+
const root = parts.root
|
|
698
|
+
const item = parts.item
|
|
671
699
|
const validateElement = node => {
|
|
672
700
|
const tag = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName
|
|
673
701
|
if (!ts.isIdentifier(tag) || tag.text[0] !== tag.text[0].toLowerCase()) fail(node, "Keyed list items must use intrinsic JSX elements")
|
|
@@ -717,24 +745,120 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
717
745
|
visit(root)
|
|
718
746
|
}
|
|
719
747
|
|
|
720
|
-
function
|
|
721
|
-
if (component.
|
|
722
|
-
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
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}"`)
|
|
726
778
|
|
|
727
|
-
|
|
779
|
+
let returned
|
|
780
|
+
const calculations = []
|
|
728
781
|
if (!ts.isBlock(component.body)) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
|
732
796
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
if (!ts.
|
|
737
|
-
|
|
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
|
|
738
862
|
}
|
|
739
863
|
|
|
740
864
|
function jsxTagUses(root, name) {
|