@barefootjs/jsx 0.33.1 → 0.33.3
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/dist/analyzer.d.ts +17 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/compiler.d.ts +21 -5
- package/dist/compiler.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +14 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +817 -457
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +7 -7
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +60 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/index.d.ts.map +1 -1
- package/dist/ir-to-client-js/prop-handling.d.ts +30 -0
- package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +5 -0
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts +36 -8
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts +26 -2
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/props-binding.d.ts +35 -0
- package/dist/props-binding.d.ts.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +165 -98
- package/src/__tests__/binding-scope-ratchet.test.ts +5 -1
- package/src/__tests__/child-component-ref-not-mirrored.test.ts +90 -0
- package/src/__tests__/client-js-generation.test.ts +11 -0
- package/src/__tests__/ir-to-client-js/imports.test.ts +107 -0
- package/src/__tests__/ir-to-client-js/merge-compiled-client-js-imports.test.ts +138 -0
- package/src/__tests__/issue-2723-prop-alias-reactivity.test.ts +124 -0
- package/src/__tests__/issue-2754-rest-spread-needs-slot.test.ts +85 -0
- package/src/__tests__/issue-2756-loop-row-honors-client-only.test.ts +173 -0
- package/src/__tests__/merge-template-imports.test.ts +41 -1
- package/src/__tests__/multi-component-shared-default-import.test.ts +55 -0
- package/src/__tests__/rewrite-props-object.test.ts +41 -4
- package/src/__tests__/root-key-relay.test.ts +170 -0
- package/src/__tests__/signal-getter-not-called.test.ts +149 -0
- package/src/__tests__/state-only-file-default-import.test.ts +47 -0
- package/src/analyzer.ts +36 -0
- package/src/compiler.ts +94 -104
- package/src/expression-parser.ts +26 -0
- package/src/index.ts +2 -2
- package/src/ir-to-client-js/collect-elements.ts +45 -30
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +6 -2
- package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +5 -2
- package/src/ir-to-client-js/emit-registration.ts +26 -7
- package/src/ir-to-client-js/generate-init.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +130 -20
- package/src/ir-to-client-js/imports.ts +178 -5
- package/src/ir-to-client-js/index.ts +11 -4
- package/src/ir-to-client-js/prop-handling.ts +83 -0
- package/src/ir-to-client-js/reactivity.ts +59 -0
- package/src/ir-to-client-js/rewrite-props-object.ts +50 -10
- package/src/ir-to-client-js/utils.ts +30 -2
- package/src/jsx-to-ir.ts +523 -49
- package/src/props-binding.ts +51 -0
- package/src/types.ts +48 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2767 follow-up: the state-only-file client-JS path (a `.tsx` with no
|
|
3
|
+
* JSX return but an exported `/* @client *\/` module signal) used to filter
|
|
4
|
+
* OUT every default- or namespace-imported specifier when deciding which
|
|
5
|
+
* external imports to preserve (`s => !s.isDefault && !s.isNamespace`,
|
|
6
|
+
* `compiler.ts`'s single-component early return) — not just render them
|
|
7
|
+
* wrong, but drop them entirely. A signal initializer that references a
|
|
8
|
+
* default- or namespace-imported helper compiled with zero diagnostics
|
|
9
|
+
* into client JS that throws `ReferenceError` in the browser, since the
|
|
10
|
+
* import never made it into the bundle at all.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, test, expect } from 'bun:test'
|
|
13
|
+
import { compileJSX } from '../compiler'
|
|
14
|
+
import { TestAdapter } from '../adapters/test-adapter'
|
|
15
|
+
|
|
16
|
+
const adapter = new TestAdapter()
|
|
17
|
+
|
|
18
|
+
describe('state-only file: default/namespace imports feeding a @client signal', () => {
|
|
19
|
+
test('preserves a default-imported helper referenced by the signal initializer', () => {
|
|
20
|
+
const source = `'use client'
|
|
21
|
+
import defaults from './defaults.json' with { type: 'json' }
|
|
22
|
+
import { createSignal } from '@barefootjs/client'
|
|
23
|
+
/* @client */
|
|
24
|
+
export const [count, setCount] = createSignal(defaults.start)
|
|
25
|
+
`
|
|
26
|
+
const result = compileJSX(source, 'store.tsx', { adapter })
|
|
27
|
+
expect(result.errors.filter(e => e.severity === 'error')).toEqual([])
|
|
28
|
+
const clientJs = result.files.find(f => f.type === 'clientJs')
|
|
29
|
+
expect(clientJs).toBeDefined()
|
|
30
|
+
expect(clientJs!.content).toContain("import defaults from './defaults.json'")
|
|
31
|
+
expect(clientJs!.content).not.toContain('import { defaults }')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('preserves a namespace-imported helper referenced by the signal initializer', () => {
|
|
35
|
+
const source = `'use client'
|
|
36
|
+
import * as util from './util'
|
|
37
|
+
import { createSignal } from '@barefootjs/client'
|
|
38
|
+
/* @client */
|
|
39
|
+
export const [count, setCount] = createSignal(util.base())
|
|
40
|
+
`
|
|
41
|
+
const result = compileJSX(source, 'store2.tsx', { adapter })
|
|
42
|
+
expect(result.errors.filter(e => e.severity === 'error')).toEqual([])
|
|
43
|
+
const clientJs = result.files.find(f => f.type === 'clientJs')
|
|
44
|
+
expect(clientJs).toBeDefined()
|
|
45
|
+
expect(clientJs!.content).toContain("import * as util from './util'")
|
|
46
|
+
})
|
|
47
|
+
})
|
package/src/analyzer.ts
CHANGED
|
@@ -4237,6 +4237,42 @@ export function listComponentFunctions(
|
|
|
4237
4237
|
ts.ScriptKind.TSX
|
|
4238
4238
|
)
|
|
4239
4239
|
|
|
4240
|
+
return listComponentFunctionsFromSourceFile(sourceFile)
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
/**
|
|
4244
|
+
* One parse, two structural facts about a component file — the component
|
|
4245
|
+
* names it exports (today's `listComponentFunctions` result) and the
|
|
4246
|
+
* PascalCase JSX tags it instantiates (`collectJsxComponentTags`'s
|
|
4247
|
+
* out-edges). Used by `@barefootjs/vite`'s `discoverComponents` to build the
|
|
4248
|
+
* component-instantiation graph that decides which SERVER files also need a
|
|
4249
|
+
* client bundle because they transitively own a `'use client'` descendant
|
|
4250
|
+
* (issue #2767) — a property no single-file compile can answer, since it
|
|
4251
|
+
* depends on the whole discovered corpus.
|
|
4252
|
+
*/
|
|
4253
|
+
export interface ComponentFileScan {
|
|
4254
|
+
/** Component names this file exports (same result as `listComponentFunctions`). */
|
|
4255
|
+
exports: string[]
|
|
4256
|
+
/** PascalCase JSX tag identifiers this file references. */
|
|
4257
|
+
referencedComponents: string[]
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
export function scanComponentFile(source: string, filePath: string): ComponentFileScan {
|
|
4261
|
+
const sourceFile = ts.createSourceFile(
|
|
4262
|
+
filePath,
|
|
4263
|
+
source,
|
|
4264
|
+
ts.ScriptTarget.Latest,
|
|
4265
|
+
true,
|
|
4266
|
+
ts.ScriptKind.TSX
|
|
4267
|
+
)
|
|
4268
|
+
|
|
4269
|
+
return {
|
|
4270
|
+
exports: listComponentFunctionsFromSourceFile(sourceFile),
|
|
4271
|
+
referencedComponents: [...collectJsxComponentTags(sourceFile)],
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
function listComponentFunctionsFromSourceFile(sourceFile: ts.SourceFile): string[] {
|
|
4240
4276
|
const componentNames: string[] = []
|
|
4241
4277
|
|
|
4242
4278
|
// 'use client' directive detection (controls whether multi-return JSX
|
package/src/compiler.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { stripClientBuiltinImports } from './builtins.ts'
|
|
|
19
19
|
import { generateClientJs, generateClientJsWithSourceMap, analyzeClientNeeds } from './ir-to-client-js/index.ts'
|
|
20
20
|
import { decideClientOnlyElision } from './ir-to-client-js/client-only-elision.ts'
|
|
21
21
|
import { emitModuleLevelDeclarations } from './ir-to-client-js/emit-module-level.ts'
|
|
22
|
-
import { RUNTIME_MODULE, detectUsedImports as detectUsedImportsFromCode, makeValueUsageTest } from './ir-to-client-js/imports.ts'
|
|
22
|
+
import { RUNTIME_MODULE, detectUsedImports as detectUsedImportsFromCode, makeValueUsageTest, renderUsedImportLines, mergeCompiledClientJsImports } from './ir-to-client-js/imports.ts'
|
|
23
23
|
import { setActiveComponentScope, computeFileScope } from './ir-to-client-js/component-scope.ts'
|
|
24
24
|
import { generateModuleExports, collectInlineExportedNames } from './module-exports.ts'
|
|
25
25
|
import { applyCssLayerPrefix, applyCssLayerPrefixToFile } from './css-layer-prefixer.ts'
|
|
@@ -43,11 +43,27 @@ export interface CompileOptionsWithAdapter extends CompileOptions {
|
|
|
43
43
|
* conflict-free block.
|
|
44
44
|
*
|
|
45
45
|
* Named value/type imports from the same source are folded into their first
|
|
46
|
-
* occurrence (preserving line order and first-seen symbol order);
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* occurrence (preserving line order and first-seen symbol order); a default
|
|
47
|
+
* or namespace import is likewise folded by source rather than deduplicated
|
|
48
|
+
* by exact line — every sibling component that shares a module-scope import
|
|
49
|
+
* declaration compiles it independently, so the same default binding can
|
|
50
|
+
* legally arrive as `import cfg from 'x'` from one component and `import
|
|
51
|
+
* cfg, { helper } from 'x'` from another once only the SECOND one also uses
|
|
52
|
+
* a named specifier from the same statement. Folding by source (default
|
|
53
|
+
* name from the first occurrence that has one, named specifiers unioned)
|
|
54
|
+
* collapses both into one `import cfg, { helper } from 'x'` line; the two
|
|
55
|
+
* exact-line-deduplicated strings would otherwise both survive and
|
|
56
|
+
* redeclare `cfg` (#2767 follow-up — this shape was unreachable before a
|
|
57
|
+
* default/namespace-importing server component could become a real client
|
|
58
|
+
* bundle at all). A namespace specifier can't combine with named ones on
|
|
59
|
+
* one line, so it's folded to its own line, keyed by (source, local name)
|
|
60
|
+
* — see `renderUsedImportLines`'s docstring for the same split rendering
|
|
61
|
+
* rule `collectExternalImports` uses.
|
|
62
|
+
*
|
|
63
|
+
* Every other import form (side-effect) is kept in place and de-duplicated
|
|
64
|
+
* by exact line. This ensures a symbol is never imported twice across
|
|
65
|
+
* sibling components — a redeclaration that Bun tolerates but stricter ESM
|
|
66
|
+
* parsers (the Deno runtime that renders SSR templates) reject.
|
|
51
67
|
*
|
|
52
68
|
* For a single-component file the output is identical to the input order;
|
|
53
69
|
* only repeated sibling imports collapse.
|
|
@@ -62,38 +78,70 @@ export interface CompileOptionsWithAdapter extends CompileOptions {
|
|
|
62
78
|
export function mergeTemplateImports(lines: string[]): string {
|
|
63
79
|
const result: string[] = []
|
|
64
80
|
const valueIdx = new Map<string, number>()
|
|
81
|
+
const valueDefault = new Map<string, string>()
|
|
65
82
|
const valueNames = new Map<string, Set<string>>()
|
|
66
83
|
const typeIdx = new Map<string, number>()
|
|
67
84
|
const typeNames = new Map<string, Set<string>>()
|
|
68
85
|
const seenOther = new Set<string>()
|
|
69
86
|
|
|
70
|
-
const
|
|
71
|
-
src
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
names: Map<string, Set<string>>,
|
|
75
|
-
render: (src: string, names: Set<string>) => string,
|
|
76
|
-
) => {
|
|
77
|
-
if (!idx.has(src)) {
|
|
78
|
-
idx.set(src, result.length)
|
|
79
|
-
names.set(src, new Set())
|
|
87
|
+
const foldType = (src: string, rawNames: string) => {
|
|
88
|
+
if (!typeIdx.has(src)) {
|
|
89
|
+
typeIdx.set(src, result.length)
|
|
90
|
+
typeNames.set(src, new Set())
|
|
80
91
|
result.push('')
|
|
81
92
|
}
|
|
82
|
-
const set =
|
|
93
|
+
const set = typeNames.get(src)!
|
|
83
94
|
for (const n of rawNames.split(',').map(s => s.trim()).filter(Boolean)) set.add(n)
|
|
84
|
-
result[
|
|
95
|
+
result[typeIdx.get(src)!] = `import type { ${[...set].join(', ')} } from '${src}'`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const foldValue = (src: string, defaultName: string | null, rawNames: string | null) => {
|
|
99
|
+
if (!valueIdx.has(src)) {
|
|
100
|
+
valueIdx.set(src, result.length)
|
|
101
|
+
valueNames.set(src, new Set())
|
|
102
|
+
result.push('')
|
|
103
|
+
}
|
|
104
|
+
if (defaultName && !valueDefault.has(src)) valueDefault.set(src, defaultName)
|
|
105
|
+
if (rawNames) {
|
|
106
|
+
const set = valueNames.get(src)!
|
|
107
|
+
for (const n of rawNames.split(',').map(s => s.trim()).filter(Boolean)) set.add(n)
|
|
108
|
+
}
|
|
109
|
+
result[valueIdx.get(src)!] = renderUsedImportLines(
|
|
110
|
+
src,
|
|
111
|
+
valueDefault.get(src) ?? null,
|
|
112
|
+
null,
|
|
113
|
+
[...valueNames.get(src)!],
|
|
114
|
+
).join('\n')
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
for (const raw of lines) {
|
|
88
118
|
const line = raw.trim()
|
|
89
119
|
if (!line) continue
|
|
90
120
|
const typeMatch = line.match(/^import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/)
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
121
|
+
const namedMatch = line.match(/^import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/)
|
|
122
|
+
const defaultNamedMatch = line.match(/^import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/)
|
|
123
|
+
const defaultOnlyMatch = line.match(/^import\s+([A-Za-z_$][\w$]*)\s*from\s*['"]([^'"]+)['"]\s*;?$/)
|
|
124
|
+
|
|
125
|
+
if (typeMatch) {
|
|
126
|
+
foldType(typeMatch[2], typeMatch[1])
|
|
127
|
+
} else if (namedMatch) {
|
|
128
|
+
foldValue(namedMatch[2], null, namedMatch[1])
|
|
129
|
+
} else if (defaultNamedMatch) {
|
|
130
|
+
foldValue(defaultNamedMatch[3], defaultNamedMatch[1], defaultNamedMatch[2])
|
|
131
|
+
} else if (defaultOnlyMatch) {
|
|
132
|
+
foldValue(defaultOnlyMatch[2], defaultOnlyMatch[1], null)
|
|
96
133
|
} else if (!seenOther.has(line)) {
|
|
134
|
+
// Covers namespace imports (`import * as X from 'src'`) and
|
|
135
|
+
// side-effect imports alike — deduplicated by exact line, same as
|
|
136
|
+
// before. A namespace import can't combine with named specifiers on
|
|
137
|
+
// one line (see `renderUsedImportLines`), so two components that
|
|
138
|
+
// both import the SAME namespace binding from the SAME source
|
|
139
|
+
// always emit byte-identical lines and collapse here; two DIFFERENT
|
|
140
|
+
// local namespace names for the same source (unusual — would require
|
|
141
|
+
// sibling components to alias the same module-scope `import * as`
|
|
142
|
+
// declaration differently, which isn't possible for one shared
|
|
143
|
+
// declaration) are kept as separate lines rather than silently
|
|
144
|
+
// merged.
|
|
97
145
|
seenOther.add(line)
|
|
98
146
|
result.push(line)
|
|
99
147
|
}
|
|
@@ -446,30 +494,9 @@ function compileMultipleComponents(
|
|
|
446
494
|
}
|
|
447
495
|
const clientJsOutputs = allOutputs.map(o => o.clientJs).filter(Boolean) as string[]
|
|
448
496
|
if (clientJsOutputs.length > 0) {
|
|
449
|
-
const importsBySource = new Map<string, Set<string>>()
|
|
450
|
-
const otherImports: string[] = []
|
|
451
|
-
const allCode: string[] = []
|
|
452
|
-
for (const js of clientJsOutputs) {
|
|
453
|
-
for (const line of js.split('\n')) {
|
|
454
|
-
if (line.startsWith('import ')) {
|
|
455
|
-
const match = line.match(/^import \{ ([^}]+) \} from ['"]([^'"]+)['"]$/)
|
|
456
|
-
if (match) {
|
|
457
|
-
const source = match[2]
|
|
458
|
-
if (!importsBySource.has(source)) importsBySource.set(source, new Set())
|
|
459
|
-
for (const n of match[1].split(',').map(n => n.trim())) importsBySource.get(source)!.add(n)
|
|
460
|
-
} else if (!otherImports.includes(line)) {
|
|
461
|
-
otherImports.push(line)
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
allCode.push(js.replace(/^import .+\n/gm, '').trim())
|
|
466
|
-
}
|
|
467
|
-
const mergedClientImports = [...importsBySource].map(([src, names]) =>
|
|
468
|
-
`import { ${[...names].sort().join(', ')} } from '${src}'`
|
|
469
|
-
)
|
|
470
497
|
files.push({
|
|
471
498
|
path: filePath.replace(/\.tsx?$/, '.client.js'),
|
|
472
|
-
content:
|
|
499
|
+
content: mergeCompiledClientJsImports(clientJsOutputs),
|
|
473
500
|
type: 'clientJs',
|
|
474
501
|
})
|
|
475
502
|
}
|
|
@@ -557,63 +584,14 @@ function compileMultipleComponents(
|
|
|
557
584
|
})
|
|
558
585
|
}
|
|
559
586
|
|
|
560
|
-
// Combine client JS if any
|
|
587
|
+
// Combine client JS if any — see `mergeCompiledClientJsImports`'s
|
|
588
|
+
// docstring for why this AST-based merge (not a text/regex line scan)
|
|
589
|
+
// is required here specifically (#2767 follow-up).
|
|
561
590
|
const clientJsOutputs = allOutputs.map(o => o.clientJs).filter(Boolean) as string[]
|
|
562
591
|
if (clientJsOutputs.length > 0) {
|
|
563
|
-
// Separate imports from code and merge imports by source
|
|
564
|
-
const importsBySource = new Map<string, Set<string>>()
|
|
565
|
-
const otherImports: string[] = []
|
|
566
|
-
const allCode: string[] = []
|
|
567
|
-
|
|
568
|
-
for (const js of clientJsOutputs) {
|
|
569
|
-
const lines = js.split('\n')
|
|
570
|
-
const codeLines: string[] = []
|
|
571
|
-
|
|
572
|
-
for (const line of lines) {
|
|
573
|
-
if (line.startsWith('import ')) {
|
|
574
|
-
// Parse named imports: import { a, b } from 'source'
|
|
575
|
-
const match = line.match(/^import \{ ([^}]+) \} from ['"]([^'"]+)['"]$/)
|
|
576
|
-
if (match) {
|
|
577
|
-
const names = match[1].split(',').map(n => n.trim())
|
|
578
|
-
const source = match[2]
|
|
579
|
-
if (!importsBySource.has(source)) {
|
|
580
|
-
importsBySource.set(source, new Set())
|
|
581
|
-
}
|
|
582
|
-
const set = importsBySource.get(source)!
|
|
583
|
-
for (const name of names) {
|
|
584
|
-
set.add(name)
|
|
585
|
-
}
|
|
586
|
-
} else {
|
|
587
|
-
// Other import styles (default, namespace, etc.)
|
|
588
|
-
if (!otherImports.includes(line)) {
|
|
589
|
-
otherImports.push(line)
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
} else {
|
|
593
|
-
codeLines.push(line)
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
allCode.push(codeLines.join('\n').trim())
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Generate merged imports
|
|
601
|
-
const mergedImports: string[] = []
|
|
602
|
-
for (const [source, names] of importsBySource) {
|
|
603
|
-
const sortedNames = [...names].sort()
|
|
604
|
-
mergedImports.push(`import { ${sortedNames.join(', ')} } from '${source}'`)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const combinedClientJs = [
|
|
608
|
-
...mergedImports,
|
|
609
|
-
...otherImports,
|
|
610
|
-
'',
|
|
611
|
-
...allCode.filter(Boolean),
|
|
612
|
-
].join('\n')
|
|
613
|
-
|
|
614
592
|
files.push({
|
|
615
593
|
path: filePath.replace(/\.tsx?$/, '.client.js'),
|
|
616
|
-
content:
|
|
594
|
+
content: mergeCompiledClientJsImports(clientJsOutputs),
|
|
617
595
|
type: 'clientJs',
|
|
618
596
|
})
|
|
619
597
|
}
|
|
@@ -735,7 +713,9 @@ export function compileJSX(
|
|
|
735
713
|
|
|
736
714
|
// Preserve non-runtime user imports whose specifiers are referenced
|
|
737
715
|
// in the generated body (e.g. an initializer that calls an imported
|
|
738
|
-
// helper: `createSignal(defaultValue())`).
|
|
716
|
+
// helper: `createSignal(defaultValue())`). A default- or namespace-
|
|
717
|
+
// imported helper needs its own import syntax, not named braces —
|
|
718
|
+
// see `renderUsedImportLines`'s docstring.
|
|
739
719
|
const externalImportLines: string[] = []
|
|
740
720
|
const isUsedAsValue = makeValueUsageTest(body)
|
|
741
721
|
for (const imp of ctx.imports) {
|
|
@@ -745,12 +725,22 @@ export function compileJSX(
|
|
|
745
725
|
externalImportLines.push(`import '${imp.source}'`)
|
|
746
726
|
continue
|
|
747
727
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
728
|
+
const usedNamed: string[] = []
|
|
729
|
+
let usedDefault: string | null = null
|
|
730
|
+
let usedNamespace: string | null = null
|
|
731
|
+
for (const s of imp.specifiers) {
|
|
732
|
+
if (s.isTypeOnly) continue
|
|
733
|
+
const localName = s.alias || s.name
|
|
734
|
+
if (!isUsedAsValue(localName)) continue
|
|
735
|
+
if (s.isDefault) {
|
|
736
|
+
usedDefault = localName
|
|
737
|
+
} else if (s.isNamespace) {
|
|
738
|
+
usedNamespace = localName
|
|
739
|
+
} else {
|
|
740
|
+
usedNamed.push(s.alias ? `${s.name} as ${s.alias}` : s.name)
|
|
741
|
+
}
|
|
753
742
|
}
|
|
743
|
+
externalImportLines.push(...renderUsedImportLines(imp.source, usedDefault, usedNamespace, usedNamed))
|
|
754
744
|
}
|
|
755
745
|
|
|
756
746
|
const allImports = [runtimeImportLine, ...externalImportLines].filter(Boolean).join('\n')
|
package/src/expression-parser.ts
CHANGED
|
@@ -317,6 +317,32 @@ export type SortComparator = {
|
|
|
317
317
|
keys: SortKey[]
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Runtime registries of the sort-comparator catalogue's finite dimensions —
|
|
322
|
+
* the denominators for the coverage ledger's sort floor
|
|
323
|
+
* (`packages/adapter-tests/src/__tests__/coverage-map.test.ts`). This closes
|
|
324
|
+
* the comparator half of the change-time coupling rule
|
|
325
|
+
* (`spec/subset-conformance.md`) mechanically: the exhaustiveness pins below
|
|
326
|
+
* make widening {@link SortKey} without listing the new member here a compile
|
|
327
|
+
* error, and the floor test then makes shipping a listed member with no
|
|
328
|
+
* covering fixture a test failure — same drift defence `PARSED_EXPR_KINDS`
|
|
329
|
+
* and `ARRAY_METHOD_NAMES` provide for their halves.
|
|
330
|
+
*/
|
|
331
|
+
export const SORT_KEY_TYPES = ['numeric', 'string', 'auto'] as const satisfies ReadonlyArray<SortKey['type']>
|
|
332
|
+
type MissingFromSortKeyTypes = Exclude<SortKey['type'], (typeof SORT_KEY_TYPES)[number]>
|
|
333
|
+
const _sortKeyTypeRegistryIsExhaustive: MissingFromSortKeyTypes extends never ? true : never = true
|
|
334
|
+
void _sortKeyTypeRegistryIsExhaustive
|
|
335
|
+
|
|
336
|
+
export const SORT_KEY_TARGETS = ['self', 'field'] as const satisfies ReadonlyArray<SortKey['key']['kind']>
|
|
337
|
+
type MissingFromSortKeyTargets = Exclude<SortKey['key']['kind'], (typeof SORT_KEY_TARGETS)[number]>
|
|
338
|
+
const _sortKeyTargetRegistryIsExhaustive: MissingFromSortKeyTargets extends never ? true : never = true
|
|
339
|
+
void _sortKeyTargetRegistryIsExhaustive
|
|
340
|
+
|
|
341
|
+
export const SORT_KEY_DIRECTIONS = ['asc', 'desc'] as const satisfies ReadonlyArray<SortKey['direction']>
|
|
342
|
+
type MissingFromSortKeyDirections = Exclude<SortKey['direction'], (typeof SORT_KEY_DIRECTIONS)[number]>
|
|
343
|
+
const _sortKeyDirectionRegistryIsExhaustive: MissingFromSortKeyDirections extends never ? true : never = true
|
|
344
|
+
void _sortKeyDirectionRegistryIsExhaustive
|
|
345
|
+
|
|
320
346
|
/**
|
|
321
347
|
* Flatten depth for `.flat(depth?)` (#1448 Tier C). A finite non-negative
|
|
322
348
|
* integer flattens that many levels (`.flat()` defaults to `1`; a `0` or
|
package/src/index.ts
CHANGED
|
@@ -66,7 +66,7 @@ export type {
|
|
|
66
66
|
} from './types.ts'
|
|
67
67
|
|
|
68
68
|
// Analyzer
|
|
69
|
-
export { analyzeComponent, listComponentFunctions, listComponentFunctions as listExportedComponents, createProgramForFile, needsTypeBasedDetection, REACTIVE_PRIMITIVES, BROWSER_ONLY_CLIENT_APIS, type AnalyzerContext } from './analyzer.ts'
|
|
69
|
+
export { analyzeComponent, listComponentFunctions, listComponentFunctions as listExportedComponents, scanComponentFile, createProgramForFile, needsTypeBasedDetection, REACTIVE_PRIMITIVES, BROWSER_ONLY_CLIENT_APIS, type AnalyzerContext, type ComponentFileScan } from './analyzer.ts'
|
|
70
70
|
export { createProgramForCorpus, type SharedProgramOptions } from './shared-program.ts'
|
|
71
71
|
|
|
72
72
|
// JSX to IR transformer
|
|
@@ -213,7 +213,7 @@ export { isValueReferenceIdentifier, collectValueReferencedNames } from './value
|
|
|
213
213
|
// Expression Parser
|
|
214
214
|
export { parseExpression, tsNodeToParsedExpr, asCallbackMethodCall, CALLBACK_METHODS, sortComparatorFromArrow, serializeParsedExpr, freeVarsInBody, freeIdentifiers, materializeGetterCalls, isSupported, isSupportedValue, exprToString, stringifyParsedExpr, identifierPath, parseBlockBody, parseBlockBodyTolerant, foldBlockToExpr, predicateTernaryToLogical, containsHigherOrder, extractArrowBodyExpression, parseStyleObjectEntries, hasUnsafeStyleValue, parseProviderObjectLiteral, type ProviderObjectMember, type FoldBlockOptions } from './expression-parser.ts'
|
|
215
215
|
export type { StyleObjectEntry } from './expression-parser.ts'
|
|
216
|
-
export { PARSED_EXPR_KINDS, ARRAY_METHOD_NAMES } from './expression-parser.ts'
|
|
216
|
+
export { PARSED_EXPR_KINDS, ARRAY_METHOD_NAMES, SORT_KEY_TYPES, SORT_KEY_TARGETS, SORT_KEY_DIRECTIONS } from './expression-parser.ts'
|
|
217
217
|
export type { ParsedExpr, ObjectLiteralProperty, ParsedStatement, SortComparator, SortKey, FlatDepth, SupportLevel, SupportResult, TemplatePart } from './expression-parser.ts'
|
|
218
218
|
export { buildLoopChainExpr } from './loop-chain.ts'
|
|
219
219
|
export type { LoopChainInputs } from './loop-chain.ts'
|
|
@@ -8,11 +8,12 @@ import { attrValueToString, freeIdsFromRefs, quotePropName, PROPS_PARAM } from '
|
|
|
8
8
|
import { classifyReactivity, decideWrapForAttr, decideWrapForChildProp, decideWrapFromAstFlags, collectEventHandlersFromIR, collectConditionalBranchEvents, collectConditionalBranchRefs, collectConditionalBranchChildComponents, collectLoopChildEventsWithNesting, collectLoopChildReactiveAttrs, collectLoopChildReactiveTexts, collectLoopChildRefs, emptyLoopChildBindings, buildLoopRowScope, anyNameIn } from './reactivity.ts'
|
|
9
9
|
import { irToHtmlTemplate, irToPlaceholderTemplate, irChildrenToJsExpr, buildLoopSkeletonTemplate, computeSkeletonSlotPaths, renderFlatMapClientBody, renderFlatMapProjectionClientBody, flatMapCallbackHasKeyedLeaf, type SkeletonSlotPaths } from './html-template.ts'
|
|
10
10
|
import { detectRootNamespaceWrapTag } from './control-flow/stringify/template-parse.ts'
|
|
11
|
-
import { expandDynamicPropValue, expandConstantForReactivity } from './prop-handling.ts'
|
|
11
|
+
import { expandDynamicPropValue, expandConstantForReactivity, resolveRestSpreadOrigin, resolveRestSpreadNames } from './prop-handling.ts'
|
|
12
12
|
import { extractFreeIdentifiersFromText } from './csr-substitute.ts'
|
|
13
13
|
import { walkIR, stopAt } from './walker.ts'
|
|
14
14
|
import { buildLoopChainExpr } from '../loop-chain.ts'
|
|
15
15
|
import { identifierPattern } from '../identifier-pattern.ts'
|
|
16
|
+
import { classifyDOMProp } from '@barefootjs/shared'
|
|
16
17
|
|
|
17
18
|
/** Expressions that render nothing (0 DOM nodes) — `&&` / `?:` empty branches. */
|
|
18
19
|
const EMPTY_RENDER_EXPRS = new Set(['null', 'undefined', 'false', "''", '""', '``'])
|
|
@@ -497,23 +498,15 @@ function isSingleElementJsxChildren(nodes: IRNode[]): boolean {
|
|
|
497
498
|
return nodes.length === 1 && nodes[0].type === 'element'
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
/** Build rest spread names from context (rest/props spreads handled by applyRestAttrs, not spreadAttrs). */
|
|
501
|
-
function buildRestSpreadNames(ctx: ClientJsContext): Set<string> {
|
|
502
|
-
const names = new Set<string>()
|
|
503
|
-
if (ctx.restPropsName) names.add(ctx.restPropsName)
|
|
504
|
-
if (ctx.propsObjectName) names.add(ctx.propsObjectName)
|
|
505
|
-
return names
|
|
506
|
-
}
|
|
507
|
-
|
|
508
501
|
/** Build propsExpr for a child component from its IR props. */
|
|
509
502
|
function buildComponentPropsExpr(props: IRProp[], ctx: ClientJsContext): string {
|
|
510
|
-
const restName = ctx.restPropsName
|
|
511
|
-
const propsObjName = ctx.propsObjectName
|
|
512
503
|
const knownSpreadProp = props.find(p => {
|
|
513
504
|
if (p.name !== '...' && !p.name.startsWith('...')) return false
|
|
514
505
|
if (p.value.kind !== 'spread' && p.value.kind !== 'expression') return false
|
|
515
506
|
const expr = p.value.kind === 'spread' ? p.value.expr : p.value.expr
|
|
516
|
-
|
|
507
|
+
// #2723: resolve through any `const x__alias = x` hop onto the
|
|
508
|
+
// rest/props binding — see `resolveRestSpreadOrigin`'s docstring.
|
|
509
|
+
return resolveRestSpreadOrigin(ctx, expr) !== null
|
|
517
510
|
})
|
|
518
511
|
const spreadSource = knownSpreadProp ? PROPS_PARAM : null
|
|
519
512
|
|
|
@@ -589,11 +582,32 @@ function collectReactiveChildProps(node: IRComponent, ctx: ClientJsContext): voi
|
|
|
589
582
|
for (const prop of node.props) {
|
|
590
583
|
if (prop.name === '...' || prop.name.startsWith('...')) continue
|
|
591
584
|
if (prop.value.kind === 'jsx-children') continue
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
585
|
+
// `classifyDOMProp` is the single source of truth for "how does this prop
|
|
586
|
+
// reach the DOM?" (`packages/shared/src/dom-prop.ts`). `applyRestAttrs`
|
|
587
|
+
// reads it to build its own attribute set — everything whose kind is NOT
|
|
588
|
+
// `ref` / `event` / `skip`. This mirror is the compile-time twin of that
|
|
589
|
+
// loop, so it takes the same three exclusions from the same classifier
|
|
590
|
+
// rather than re-deciding them by hand (#2749: the hand-rolled `on[A-Z]`
|
|
591
|
+
// check covered events but not `ref`, so a `ref` on a child-component
|
|
592
|
+
// call site was mirrored as `setAttribute('ref', String(<fn source>))` —
|
|
593
|
+
// an attribute SSR never emits, failing the SSR-vs-hydrated snapshot and
|
|
594
|
+
// shadowing the real binding `initChild` was already passing correctly).
|
|
595
|
+
//
|
|
596
|
+
// Two small widenings come with reading the shared classifier, both
|
|
597
|
+
// intended. `skip` also covers a `children` prop reaching here as an
|
|
598
|
+
// expression rather than as the `jsx-children` variant handled above —
|
|
599
|
+
// `setAttribute('children', …)` was never a sensible mirror. And the
|
|
600
|
+
// classifier's event test requires an actual A-Z at index 2, where the
|
|
601
|
+
// old `name[2] === name[2].toUpperCase()` also matched non-letters
|
|
602
|
+
// (`on-foo`, `on1x`), which are not DOM events and now mirror normally.
|
|
603
|
+
//
|
|
604
|
+
// Why not also exclude `innerHTML` here: `dangerouslySetInnerHTML` as a
|
|
605
|
+
// reactive CHILD-COMPONENT prop is a separate, unmeasured shape — the
|
|
606
|
+
// element-level path is what the `dangerous-inner-html*` fixtures cover.
|
|
607
|
+
// Narrowing this mirror to the three kinds `applyRestAttrs` excludes keeps
|
|
608
|
+
// the change to the classification `ref` was already missing.
|
|
609
|
+
const domKind = classifyDOMProp(prop.name).kind
|
|
610
|
+
if (domKind === 'ref' || domKind === 'event' || domKind === 'skip') continue
|
|
597
611
|
// Only `expression` / `template` variants drive reactive prop forwarding.
|
|
598
612
|
if (prop.value.kind !== 'expression' && prop.value.kind !== 'template') continue
|
|
599
613
|
const valueExpr = attrValueToString(prop.value)!
|
|
@@ -762,7 +776,7 @@ export function collectElements(
|
|
|
762
776
|
// drops the parent's slot suffix automatically. Each iteration
|
|
763
777
|
// owns a distinct scope identified by `data-key`, mirroring the
|
|
764
778
|
// SSR template's renderChild emit.
|
|
765
|
-
staticItemTemplate = irToHtmlTemplate(l.children[0],
|
|
779
|
+
staticItemTemplate = irToHtmlTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, undefined, undefined)
|
|
766
780
|
}
|
|
767
781
|
} else if (l.children[0] && !projectionInner) {
|
|
768
782
|
// Pass loopParams so expressions are wrapped at generation time,
|
|
@@ -771,8 +785,8 @@ export function collectElements(
|
|
|
771
785
|
// in the emitted template literal are rewritten to `__bfItem()[1].color`.
|
|
772
786
|
const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }]
|
|
773
787
|
template = useElementReconciliation
|
|
774
|
-
? irToPlaceholderTemplate(l.children[0],
|
|
775
|
-
: irToHtmlTemplate(l.children[0],
|
|
788
|
+
? irToPlaceholderTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpec)
|
|
789
|
+
: irToHtmlTemplate(l.children[0], resolveRestSpreadNames(ctx), 0, loopParamSpec)
|
|
776
790
|
// Static-array loops emit a `forEach((param, idx) => ...)` whose body
|
|
777
791
|
// references the destructured param directly — `__bfItem()` is not in
|
|
778
792
|
// scope there. Build a second template that skips the loop-param
|
|
@@ -788,8 +802,8 @@ export function collectElements(
|
|
|
788
802
|
// markers, so SSR's parent-anchored shape and CSR's random-id
|
|
789
803
|
// shape both resolve through the same lookup.
|
|
790
804
|
staticItemTemplate = useElementReconciliation
|
|
791
|
-
? irToPlaceholderTemplate(l.children[0],
|
|
792
|
-
: irToHtmlTemplate(l.children[0],
|
|
805
|
+
? irToPlaceholderTemplate(l.children[0], resolveRestSpreadNames(ctx), 0)
|
|
806
|
+
: irToHtmlTemplate(l.children[0], resolveRestSpreadNames(ctx), 0)
|
|
793
807
|
} else if (!useElementReconciliation && !l.bodyIsMultiRoot && !l.bodyIsItemConditional) {
|
|
794
808
|
// Hoisted shared-template fast path (perf): only for the plain
|
|
795
809
|
// `mapArray` shape — single-root, dynamic array, no element
|
|
@@ -855,13 +869,13 @@ export function collectElements(
|
|
|
855
869
|
flatMapClient: projectionInner
|
|
856
870
|
? {
|
|
857
871
|
params: l.index ? `(${l.param}, ${l.index})` : `(${l.param})`,
|
|
858
|
-
body: renderFlatMapProjectionClientBody(projectionInner,
|
|
872
|
+
body: renderFlatMapProjectionClientBody(projectionInner, resolveRestSpreadNames(ctx)),
|
|
859
873
|
keyed: projectionInner.key !== null,
|
|
860
874
|
}
|
|
861
875
|
: l.flatMapCallback
|
|
862
876
|
? {
|
|
863
877
|
params: l.flatMapCallback.params,
|
|
864
|
-
body: renderFlatMapClientBody(l.flatMapCallback,
|
|
878
|
+
body: renderFlatMapClientBody(l.flatMapCallback, resolveRestSpreadNames(ctx)),
|
|
865
879
|
keyed: flatMapCallbackHasKeyedLeaf(l.flatMapCallback),
|
|
866
880
|
}
|
|
867
881
|
: undefined,
|
|
@@ -964,9 +978,10 @@ function collectFromElement(element: IRElement, ctx: ClientJsContext, insideCond
|
|
|
964
978
|
// Always use PROPS_PARAM as the source since the init function parameter is PROPS_PARAM.
|
|
965
979
|
if (attr.name === '...' && attr.value) {
|
|
966
980
|
const spreadVal = attrValueToString(attr.value) ?? ''
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
981
|
+
// #2723: resolve through any `const x__alias = x` hop onto the
|
|
982
|
+
// rest/props binding — see `resolveRestSpreadOrigin`'s docstring.
|
|
983
|
+
const spreadOrigin = spreadVal ? resolveRestSpreadOrigin(ctx, spreadVal) : null
|
|
984
|
+
if (spreadOrigin !== null) {
|
|
970
985
|
// `applyRestAttrs(_el, _p, exclude)` is handed the FULL props
|
|
971
986
|
// object (`PROPS_PARAM`), not a computed JS rest binding, and the
|
|
972
987
|
// runtime filters by SOURCE KEY (`source[key]`). So `exclude` must
|
|
@@ -985,7 +1000,7 @@ function collectFromElement(element: IRElement, ctx: ClientJsContext, insideCond
|
|
|
985
1000
|
// caller-keyed (#2524 CSR half) — so the exclude list must use
|
|
986
1001
|
// the caller-facing key too, not the local binding name.
|
|
987
1002
|
const consumedKeys =
|
|
988
|
-
|
|
1003
|
+
spreadOrigin === 'rest' ? ctx.propsParams.map(p => p.sourceName ?? p.name) : []
|
|
989
1004
|
const staticAttrKeys = element.attrs
|
|
990
1005
|
.filter(a => a.name !== '...')
|
|
991
1006
|
.map(a => a.name)
|
|
@@ -1135,7 +1150,7 @@ function collectBranchLoops(
|
|
|
1135
1150
|
siblingOffsets: Map<IRLoop, IRNode[]>,
|
|
1136
1151
|
): BranchLoop[] {
|
|
1137
1152
|
const loops: BranchLoop[] = []
|
|
1138
|
-
const restNames = ctx ?
|
|
1153
|
+
const restNames = ctx ? resolveRestSpreadNames(ctx) : undefined
|
|
1139
1154
|
|
|
1140
1155
|
walkIR<string | null>(node, null, {
|
|
1141
1156
|
// Don't recurse into nested conditionals / if-statements.
|
|
@@ -1258,7 +1273,7 @@ function buildConditionalMetadata(
|
|
|
1258
1273
|
ctx: ClientJsContext,
|
|
1259
1274
|
siblingOffsets: Map<IRLoop, IRNode[]>,
|
|
1260
1275
|
): ConditionalElement {
|
|
1261
|
-
const restNames =
|
|
1276
|
+
const restNames = resolveRestSpreadNames(ctx)
|
|
1262
1277
|
// Use loopDepth=-1 so the first loop encountered inside the branch emits
|
|
1263
1278
|
// data-key (depth 0) for its items, matching the mapArray item template
|
|
1264
1279
|
// and event dispatcher convention. Matches irToComponentTemplate/generateCsrTemplate.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* <indent>}) }
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import { keyAttrName, profileBindingId, varSlotId } from '../../utils.ts'
|
|
34
|
+
import { keyAttrName, mapArrayKeyArgs, profileBindingId, varSlotId } from '../../utils.ts'
|
|
35
35
|
import { emitComponentAndEventSetup } from '../shared.ts'
|
|
36
36
|
import { emitAttrUpdate } from '../../emit-reactive.ts'
|
|
37
37
|
import { emitMultiRootTemplateCloneLines, namespaceWrapForTemplate } from './template-parse.ts'
|
|
@@ -157,7 +157,11 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
157
157
|
bodyIsMultiRoot: emit.bodyIsMultiRoot,
|
|
158
158
|
})
|
|
159
159
|
lines.push(`${indent} return __innerEl${uid}`)
|
|
160
|
-
|
|
160
|
+
// #2753 Shape B: the runtime's own fallback stamp (a row whose renderItem
|
|
161
|
+
// didn't already set a key attribute — see `map-array.ts`) has no depth
|
|
162
|
+
// concept, so a nested keyed loop must tell it which name to check/write
|
|
163
|
+
// instead of the default `data-key`.
|
|
164
|
+
lines.push(`${indent}}, '${inner.markerId}'${mapArrayKeyArgs(profileBindingId(pc, inner.slotId), !!emit.wrappedKey, inner.keyDepth)}) }`)
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* every nesting depth.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { varSlotId, DATA_BF_PH, keyAttrName, profileBindingId } from '../../utils.ts'
|
|
14
|
+
import { varSlotId, DATA_BF_PH, keyAttrName, mapArrayKeyArgs, profileBindingId } from '../../utils.ts'
|
|
15
15
|
import { emitComponentAndEventSetup } from '../shared.ts'
|
|
16
16
|
import { emitAttrUpdate } from '../../emit-reactive.ts'
|
|
17
17
|
import { namespaceWrapForTemplate } from './template-parse.ts'
|
|
@@ -180,7 +180,10 @@ export function stringifyBranchInnerLoops(
|
|
|
180
180
|
stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc)
|
|
181
181
|
}
|
|
182
182
|
lines.push(`${indent} return __bel${uid}`)
|
|
183
|
-
|
|
183
|
+
// #2753 Shape B: see the identical comment in `inner-loop.ts` —
|
|
184
|
+
// `keyDepth` is always 1 for a branch-arm inner loop, so this only ever
|
|
185
|
+
// widens the trailing args when the loop is also keyed.
|
|
186
|
+
lines.push(`${indent}}, '${inner.markerId}'${mapArrayKeyArgs(profileBindingId(pc, inner.slotId), !!inner.wrappedKey, inner.keyDepth)}) }`)
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
|