@kudzujs/core 0.7.3 → 0.7.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 +4 -2
- package/RELEASES.md +46 -0
- package/framework/README.md +3 -1
- package/framework/build.mjs +95 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Kudzu is designed so ordinary common React-shaped TSX can migrate with minimal s
|
|
|
10
10
|
|
|
11
11
|
> Experimental `0.7.x`: the compiler API and supported TSX surface may change.
|
|
12
12
|
|
|
13
|
-
**0.7.
|
|
13
|
+
**0.7.5:** Class composition migration. Direct `clsx` calls compile to ordinary reactive class expressions without shipping the package, and mixed React type imports erase cleanly. See [release notes](./RELEASES.md#075---class-composition-migration).
|
|
14
14
|
|
|
15
15
|
Documentation: [kudzujs.cloud/docs](https://kudzujs.cloud/docs)
|
|
16
16
|
|
|
@@ -74,7 +74,9 @@ export default function Header() {
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
Kudzu rewrites supported React imports to its compile-time APIs before evaluating the module; neither the React package nor a compatibility runtime enters the deploy output. Named or aliased `useState`, `useReducer`, `useEffect`, `useRef`, `createContext`, and `useContext` imports compile to their canonical forms. Default and namespace imports may call those APIs as direct members such as `React.useState`, and default, namespace, or named `Fragment` also works. `memo(Component)` is erased to a same-file component. Inline `useCallback(function, literalDependencies)` is erased to its function, while inline synchronous `useMemo` callbacks
|
|
77
|
+
Kudzu rewrites supported React imports to its compile-time APIs before evaluating the module; neither the React package nor a compatibility runtime enters the deploy output. Named or aliased `useState`, `useReducer`, `useEffect`, `useRef`, `createContext`, and `useContext` imports compile to their canonical forms. Default and namespace imports may call those APIs as direct members such as `React.useState`, and default, namespace, or named `Fragment` also works. `memo(Component)` is erased to a same-file component. Inline `useCallback(function, literalDependencies)` is erased to its function, while inline synchronous `useMemo` callbacks may return one expression over primitive literals/direct local state or an analyzable `filter`, `map`, `flatMap`, and `Array.from` collection pipeline. Scalar expressions inline into existing bindings; collection pipelines lower to existing keyed-list selectors and preserve row identity. Both hooks require inert literal dependency arrays and complete captured-state dependencies; memo locals cannot be duplicated or captured by nested functions. React classes and side-effect or dynamic React imports remain unsupported. A static route using these forms still emits zero JavaScript.
|
|
78
|
+
|
|
79
|
+
Direct default or named `clsx` imports compile away for string/number literals, literal arrays, literal object conditions, and conditional expressions. Kudzu lowers those calls to ordinary class expressions, so reactive classes reuse existing bindings without shipping `clsx`; spreads, computed object keys, arbitrary calls, and indirect references remain unsupported.
|
|
78
80
|
|
|
79
81
|
Create `src/pages/index.tsx`:
|
|
80
82
|
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.7.5 - Class composition migration
|
|
4
|
+
|
|
5
|
+
Kudzu 0.7.5 lets ordinary React source retain common direct `clsx` calls while compiling them to existing static and reactive class paths.
|
|
6
|
+
|
|
7
|
+
### New in 0.7.5
|
|
8
|
+
|
|
9
|
+
- Default and named `clsx` imports lower at build time for string and number literals, literal arrays, literal object conditions, and conditional expressions.
|
|
10
|
+
- Dynamic object conditions reuse existing reactive class bindings without serializing or shipping the `clsx` function.
|
|
11
|
+
- Static uses add no browser JavaScript, and the package import is erased from compiled modules.
|
|
12
|
+
- Mixed React imports such as `import { useState, type ReactNode } from "react"` now erase type-only specifiers before runtime module rewriting.
|
|
13
|
+
- The React/Vite fixture verifies initial class output, state-driven class updates in Chrome, package erasure, and mixed type imports.
|
|
14
|
+
|
|
15
|
+
### Boundary
|
|
16
|
+
|
|
17
|
+
`clsx` spreads, computed object keys, arbitrary calls, and indirect references remain unsupported. This is source lowering for a proven migration pattern, not package execution or a general React ecosystem runtime.
|
|
18
|
+
|
|
19
|
+
### Upgrade
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @kudzujs/core@^0.7.5
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 0.7.4 - Memoized collection pipelines
|
|
26
|
+
|
|
27
|
+
Kudzu 0.7.4 lets ordinary React/Vite source retain analyzable collection work inside `useMemo` while reusing the existing keyed-list selector and DOM identity model.
|
|
28
|
+
|
|
29
|
+
### New in 0.7.4
|
|
30
|
+
|
|
31
|
+
- Inline `useMemo` callbacks accept collection pipelines rooted in direct local array state.
|
|
32
|
+
- Existing `filter`, direct-property `flatMap`, and `Array.from` selector analysis is reused without a browser memo cache.
|
|
33
|
+
- Intermediate `.map()` calls lower to the existing `Array.from(source, mapper)` selector operation.
|
|
34
|
+
- Memo locals are removed from emitted server modules after their uses are inlined, so state signals never execute array methods during rendering.
|
|
35
|
+
- State updates preserve keyed row DOM identity while adding, removing, filtering, mapping, and reordering selected values.
|
|
36
|
+
- Collection callbacks must be synchronous arrows with plain `(item)` or `(item, index)` identifier parameters; async, rest, default, and optional parameters fail with source locations.
|
|
37
|
+
- The React/Vite fixture verifies `filter + map`, selector generation, browser updates, and retained identity for an existing row.
|
|
38
|
+
|
|
39
|
+
### Boundary
|
|
40
|
+
|
|
41
|
+
Memoized collections use the existing statically analyzable collection subset. Arbitrary callbacks, external captures, asynchronous transforms, getters, and general-purpose memo caching remain unsupported. React, a VDOM, hydration, and a retained browser component tree are not emitted.
|
|
42
|
+
|
|
43
|
+
### Upgrade
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @kudzujs/core@^0.7.4
|
|
47
|
+
```
|
|
48
|
+
|
|
3
49
|
## 0.7.3 - React memo normalization
|
|
4
50
|
|
|
5
51
|
Kudzu 0.7.3 accepts common React memo authoring forms while preserving build-time components, direct state bindings, static HTML, and capability-only JavaScript.
|
package/framework/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Kudzu specializes ordinary common React-shaped TSX so migrations need minimal source restructuring. Declarative components, collection pipelines, conditions, hooks, and handlers should be lowered at build time rather than replaced with application-owned imperative DOM code. This principle applies across migrations and is not Stay-specific; it does not imply a React package, VDOM, hydration, or ecosystem runtime.
|
|
4
4
|
|
|
5
|
-
Migration source may retain conventional `react` imports for supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression `useMemo`, and default, namespace, or named `Fragment`. `build.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
|
|
5
|
+
Migration source may retain conventional `react` imports for supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression or analyzable collection-pipeline `useMemo`, and default, namespace, or named `Fragment`. `build.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined into existing bindings and keyed-list selectors because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
|
|
6
|
+
|
|
7
|
+
Direct `clsx` calls over literal strings, numbers, arrays, object conditions, and conditional expressions are similarly lowered to ordinary concatenation and conditional expressions. The package import is erased, and dynamic classes continue through the existing binding compiler without serializing or shipping the `clsx` function.
|
|
6
8
|
|
|
7
9
|
- `build.mjs`: TSX compilation, static, `getStaticPaths`, and runtime-fallback routes, base paths, CSS collection, post-build hooks, behavior extraction, static HTML output, and the development server.
|
|
8
10
|
- `core.mjs`: server-side JSX rendering, state slots, context providers, behavior metadata, and serializable capture validation.
|
package/framework/build.mjs
CHANGED
|
@@ -1744,6 +1744,53 @@ function hasReactModuleReference(source, file) {
|
|
|
1744
1744
|
return found
|
|
1745
1745
|
}
|
|
1746
1746
|
|
|
1747
|
+
function normalizeClsxSyntax(sourceFile, factory, context) {
|
|
1748
|
+
const names = new Set()
|
|
1749
|
+
for (const statement of sourceFile.statements) {
|
|
1750
|
+
if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "clsx") continue
|
|
1751
|
+
if (statement.importClause?.name) names.add(statement.importClause.name.text)
|
|
1752
|
+
const bindings = statement.importClause?.namedBindings
|
|
1753
|
+
if (bindings && ts.isNamedImports(bindings)) for (const entry of bindings.elements) if (!entry.isTypeOnly && (entry.propertyName ?? entry.name).text === "clsx") names.add(entry.name.text)
|
|
1754
|
+
}
|
|
1755
|
+
if (!names.size) return sourceFile
|
|
1756
|
+
|
|
1757
|
+
const lower = node => {
|
|
1758
|
+
node = unwrapExpression(node)
|
|
1759
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return node
|
|
1760
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword) return factory.createStringLiteral("")
|
|
1761
|
+
if (ts.isConditionalExpression(node)) return factory.updateConditionalExpression(node, node.condition, node.questionToken, lower(node.whenTrue), node.colonToken, lower(node.whenFalse))
|
|
1762
|
+
if (ts.isArrayLiteralExpression(node)) return combine(node.elements.map(lower))
|
|
1763
|
+
if (ts.isObjectLiteralExpression(node)) return combine(node.properties.map(property => {
|
|
1764
|
+
if (!ts.isPropertyAssignment(property) || property.name && ts.isComputedPropertyName(property.name)) throw sourceNodeError(property, sourceFile, "clsx() object arguments require ordinary key/value properties")
|
|
1765
|
+
const name = property.name
|
|
1766
|
+
const value = name && (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) ? name.text : undefined
|
|
1767
|
+
if (value === undefined) throw sourceNodeError(property, sourceFile, "clsx() object keys must be identifiers or literals")
|
|
1768
|
+
return factory.createConditionalExpression(property.initializer, undefined, factory.createStringLiteral(value), undefined, factory.createStringLiteral(""))
|
|
1769
|
+
}))
|
|
1770
|
+
throw sourceNodeError(node, sourceFile, "clsx() arguments must be string/number literals, literal arrays, literal objects, or conditionals")
|
|
1771
|
+
}
|
|
1772
|
+
const combine = entries => entries.length ? entries.reduce((result, entry) => factory.createBinaryExpression(factory.createBinaryExpression(result, factory.createToken(ts.SyntaxKind.PlusToken), factory.createStringLiteral(" ")), factory.createToken(ts.SyntaxKind.PlusToken), entry)) : factory.createStringLiteral("")
|
|
1773
|
+
|
|
1774
|
+
const visitor = node => {
|
|
1775
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) return combine(node.arguments.map(lower))
|
|
1776
|
+
if (ts.isIdentifier(node) && names.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile) && !(ts.isCallExpression(node.parent) && node.parent.expression === node)) throw sourceNodeError(node, sourceFile, "clsx imports may only be called directly")
|
|
1777
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "clsx") {
|
|
1778
|
+
const clause = node.importClause
|
|
1779
|
+
if (!clause || clause.isTypeOnly) return node
|
|
1780
|
+
let bindings = clause.namedBindings
|
|
1781
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
1782
|
+
const elements = bindings.elements.filter(entry => entry.isTypeOnly || (entry.propertyName ?? entry.name).text !== "clsx")
|
|
1783
|
+
bindings = elements.length ? factory.updateNamedImports(bindings, elements) : undefined
|
|
1784
|
+
}
|
|
1785
|
+
const defaultName = clause.name && names.has(clause.name.text) ? undefined : clause.name
|
|
1786
|
+
if (!defaultName && !bindings) return undefined
|
|
1787
|
+
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, clause.isTypeOnly, defaultName, bindings), node.moduleSpecifier, node.attributes)
|
|
1788
|
+
}
|
|
1789
|
+
return ts.visitEachChild(node, visitor, context)
|
|
1790
|
+
}
|
|
1791
|
+
return ts.visitNode(sourceFile, visitor)
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1747
1794
|
function normalizeReactMigrationSyntax(sourceFile, factory, context) {
|
|
1748
1795
|
const supported = new Set(["createContext", "useContext", "useEffect", "useReducer", "useRef", "useState"])
|
|
1749
1796
|
const erased = new Set(["memo", "useCallback", "useMemo"])
|
|
@@ -1799,7 +1846,7 @@ function normalizeReactMigrationSyntax(sourceFile, factory, context) {
|
|
|
1799
1846
|
if (!isLocalConst(node)) throw sourceNodeError(node, sourceFile, "React useMemo() local values must use const declarations")
|
|
1800
1847
|
const callback = node.initializer.arguments[0]
|
|
1801
1848
|
if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
|
|
1802
|
-
const expression = reactMemoExpression(callback)
|
|
1849
|
+
const expression = lowerReactMemoCollectionExpression(reactMemoExpression(callback), factory)
|
|
1803
1850
|
const owner = nearestFunction(node)
|
|
1804
1851
|
if (owner && expression) {
|
|
1805
1852
|
const entries = memoLocals.get(owner) ?? new Map()
|
|
@@ -1835,6 +1882,15 @@ function normalizeReactMigrationSyntax(sourceFile, factory, context) {
|
|
|
1835
1882
|
const required = new Set()
|
|
1836
1883
|
const imported = new Set()
|
|
1837
1884
|
const visitor = node => {
|
|
1885
|
+
if (ts.isVariableStatement(node)) {
|
|
1886
|
+
const entries = memoLocals.get(nearestFunction(node))
|
|
1887
|
+
if (entries) {
|
|
1888
|
+
for (const declaration of node.declarationList.declarations) if (ts.isIdentifier(declaration.name) && entries.has(declaration.name.text) && declaration.initializer) ts.visitNode(declaration.initializer, visitor)
|
|
1889
|
+
const declarations = node.declarationList.declarations.filter(declaration => !ts.isIdentifier(declaration.name) || !entries.has(declaration.name.text))
|
|
1890
|
+
if (!declarations.length) return undefined
|
|
1891
|
+
if (declarations.length !== node.declarationList.declarations.length) return factory.updateVariableStatement(node, node.modifiers, factory.updateVariableDeclarationList(node.declarationList, declarations.map(declaration => ts.visitEachChild(declaration, visitor, context))))
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1838
1894
|
if (ts.isIdentifier(node) && isReferenceIdentifier(node)) {
|
|
1839
1895
|
const owner = nearestFunctionLike(node)
|
|
1840
1896
|
const entry = memoLocals.get(owner)?.get(node.text)
|
|
@@ -1865,14 +1921,19 @@ function normalizeReactMigrationSyntax(sourceFile, factory, context) {
|
|
|
1865
1921
|
if (callback.parameters.length || callback.asteriskToken || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(callback, sourceFile, "React useMemo() callback must be synchronous and parameterless")
|
|
1866
1922
|
const dependency = node.arguments[1].elements.find(entry => !isReactCallbackDependency(entry))
|
|
1867
1923
|
if (dependency) throw sourceNodeError(dependency, sourceFile, "React useMemo() dependencies must be identifiers or primitive literals")
|
|
1868
|
-
const expression = reactMemoExpression(callback)
|
|
1869
|
-
if (!expression || !isPureReactMemoExpression(expression)) throw sourceNodeError(callback.body, sourceFile, "React useMemo() callback must return one pure expression")
|
|
1924
|
+
const expression = lowerReactMemoCollectionExpression(reactMemoExpression(callback), factory)
|
|
1870
1925
|
const dependencies = new Set(node.arguments[1].elements.map(unwrapExpression).filter(ts.isIdentifier).map(entry => entry.text))
|
|
1871
1926
|
const owner = nearestFunction(node)
|
|
1872
1927
|
const states = owner ? ownerStateNames(owner) : new Set()
|
|
1873
|
-
const
|
|
1874
|
-
if (
|
|
1875
|
-
|
|
1928
|
+
const collectionState = expression && reactMemoCollectionState(expression, states, sourceFile)
|
|
1929
|
+
if (!expression || !collectionState && !isPureReactMemoExpression(expression)) throw sourceNodeError(callback.body, sourceFile, "React useMemo() callback must return one pure expression or analyzable collection pipeline")
|
|
1930
|
+
if (!collectionState) {
|
|
1931
|
+
const unsupported = [...reactMemoReferenceNames(expression)].find(reference => !states.has(reference))
|
|
1932
|
+
if (unsupported) throw sourceNodeError(expression, sourceFile, `React useMemo() pure expressions may only reference direct local state; found ${JSON.stringify(unsupported)}`)
|
|
1933
|
+
}
|
|
1934
|
+
const stale = collectionState
|
|
1935
|
+
? !dependencies.has(collectionState) ? collectionState : undefined
|
|
1936
|
+
: [...states].find(state => referenceIdentifiers(expression, state).length && !dependencies.has(state))
|
|
1876
1937
|
if (stale) throw sourceNodeError(node.arguments[1], sourceFile, `React useMemo() must list captured state ${JSON.stringify(stale)} as a dependency`)
|
|
1877
1938
|
return ts.visitNode(expression, visitor)
|
|
1878
1939
|
}
|
|
@@ -1889,6 +1950,7 @@ function normalizeReactMigrationSyntax(sourceFile, factory, context) {
|
|
|
1889
1950
|
const entries = []
|
|
1890
1951
|
for (const entry of bindings.elements) {
|
|
1891
1952
|
const name = (entry.propertyName ?? entry.name).text
|
|
1953
|
+
if (entry.isTypeOnly) continue
|
|
1892
1954
|
if (!entry.isTypeOnly && erased.has(name)) continue
|
|
1893
1955
|
if (!entry.isTypeOnly && supported.has(name)) {
|
|
1894
1956
|
if (imported.has(name)) continue
|
|
@@ -1941,6 +2003,27 @@ function reactMemoExpression(callback) {
|
|
|
1941
2003
|
return callback.body.statements[0].expression
|
|
1942
2004
|
}
|
|
1943
2005
|
|
|
2006
|
+
function lowerReactMemoCollectionExpression(expression, factory) {
|
|
2007
|
+
if (!expression) return undefined
|
|
2008
|
+
const visit = node => {
|
|
2009
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "map" && node.arguments.length === 1) {
|
|
2010
|
+
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Array"), "from"), undefined, [visit(node.expression.expression), node.arguments[0]])
|
|
2011
|
+
}
|
|
2012
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ["filter", "flatMap"].includes(node.expression.name.text)) {
|
|
2013
|
+
return factory.updateCallExpression(node, factory.updatePropertyAccessExpression(node.expression, visit(node.expression.expression), node.expression.name), node.typeArguments, node.arguments)
|
|
2014
|
+
}
|
|
2015
|
+
if (isArrayFromCall(node)) return factory.updateCallExpression(node, node.expression, node.typeArguments, [visit(node.arguments[0]), ...node.arguments.slice(1)])
|
|
2016
|
+
return node
|
|
2017
|
+
}
|
|
2018
|
+
return visit(expression)
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function reactMemoCollectionState(expression, states, sourceFile) {
|
|
2022
|
+
const setters = new Map([...states].map(state => [state, state]))
|
|
2023
|
+
const fail = (node, message) => { throw sourceNodeError(node, sourceFile, message) }
|
|
2024
|
+
return renderedCollectionSource(expression, setters, undefined, fail, new Set())?.state?.text
|
|
2025
|
+
}
|
|
2026
|
+
|
|
1944
2027
|
function reactMemoComponentExpression(identifier, sourceFile, factory, context) {
|
|
1945
2028
|
for (const statement of sourceFile.statements) {
|
|
1946
2029
|
if (ts.isFunctionDeclaration(statement) && statement.name?.text === identifier.text && statement.body) {
|
|
@@ -1979,6 +2062,8 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1979
2062
|
return context => sourceFile => {
|
|
1980
2063
|
const factory = context.factory
|
|
1981
2064
|
const hasLinkElements = /<link/i.test(sourceFile.text)
|
|
2065
|
+
sourceFile = normalizeClsxSyntax(sourceFile, factory, context)
|
|
2066
|
+
ts.setParentRecursive(sourceFile, false)
|
|
1982
2067
|
sourceFile = normalizeReactMigrationSyntax(sourceFile, factory, context)
|
|
1983
2068
|
ts.setParentRecursive(sourceFile, false)
|
|
1984
2069
|
sourceFile = normalizeRenderControlFlow(sourceFile, factory, context)
|
|
@@ -1990,7 +2075,9 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1990
2075
|
const importedSource = target => {
|
|
1991
2076
|
let imported = importedSources.get(target)
|
|
1992
2077
|
if (!imported) {
|
|
1993
|
-
imported =
|
|
2078
|
+
imported = normalizeClsxSyntax(parseSourceFile(target, sourceIndex.get(target)), factory, context)
|
|
2079
|
+
ts.setParentRecursive(imported, false)
|
|
2080
|
+
imported = normalizeReactMigrationSyntax(imported, factory, context)
|
|
1994
2081
|
ts.setParentRecursive(imported, false)
|
|
1995
2082
|
imported = normalizeRenderControlFlow(imported, factory, context)
|
|
1996
2083
|
ts.setParentRecursive(imported, false)
|
|
@@ -2898,7 +2985,7 @@ function isArrayFromCall(value) {
|
|
|
2898
2985
|
}
|
|
2899
2986
|
|
|
2900
2987
|
function collectionParameters(callback, label, fail) {
|
|
2901
|
-
if (!ts.isArrowFunction(callback) || callback.parameters.length < 1 || callback.parameters.length > 2 || callback.parameters.some(parameter => !ts.isIdentifier(parameter.name))) fail(callback, `${label} callback must be
|
|
2988
|
+
if (!ts.isArrowFunction(callback) || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || callback.parameters.length < 1 || callback.parameters.length > 2 || callback.parameters.some(parameter => !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.initializer || parameter.questionToken)) fail(callback, `${label} callback must be a synchronous arrow function with (item) or (item, index) identifier parameters`)
|
|
2902
2989
|
return { item: callback.parameters[0].name.text, index: callback.parameters[1]?.name.text }
|
|
2903
2990
|
}
|
|
2904
2991
|
|