@barefootjs/blade 0.1.0
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 +73 -0
- package/dist/adapter/analysis/component-tree.d.ts +26 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/blade-adapter.d.ts +537 -0
- package/dist/adapter/blade-adapter.d.ts.map +1 -0
- package/dist/adapter/boolean-result.d.ts +76 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +105 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +74 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +176 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/expr/operand.d.ts +25 -0
- package/dist/adapter/expr/operand.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +189060 -0
- package/dist/adapter/lib/blade-naming.d.ts +128 -0
- package/dist/adapter/lib/blade-naming.d.ts.map +1 -0
- package/dist/adapter/lib/constants.d.ts +21 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +48 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +27 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +84 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +34 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +72 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +27 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.d.ts +28 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189080 -0
- package/dist/conformance-pins.d.ts +12 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189079 -0
- package/package.json +67 -0
- package/php/composer.json +32 -0
- package/php/src/BladeBackend.php +197 -0
- package/php/src/naming.php +84 -0
- package/php/tests/test_render.php +159 -0
- package/src/__tests__/blade-adapter-unit.test.ts +392 -0
- package/src/__tests__/blade-adapter.test.ts +52 -0
- package/src/__tests__/blade-counter.test.ts +63 -0
- package/src/__tests__/blade-query-href.test.ts +113 -0
- package/src/__tests__/blade-spread-attrs.test.ts +235 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/blade-adapter.ts +1912 -0
- package/src/adapter/boolean-result.ts +168 -0
- package/src/adapter/emit-context.ts +117 -0
- package/src/adapter/expr/array-method.ts +345 -0
- package/src/adapter/expr/emitters.ts +636 -0
- package/src/adapter/expr/operand.ts +35 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/lib/blade-naming.ts +180 -0
- package/src/adapter/lib/constants.ts +33 -0
- package/src/adapter/lib/ir-scope.ts +83 -0
- package/src/adapter/lib/types.ts +30 -0
- package/src/adapter/memo/seed.ts +135 -0
- package/src/adapter/props/prop-classes.ts +66 -0
- package/src/adapter/spread/spread-codegen.ts +179 -0
- package/src/adapter/value/parsed-literal.ts +75 -0
- package/src/build.ts +37 -0
- package/src/conformance-pins.ts +86 -0
- package/src/index.ts +9 -0
- package/src/test-render.ts +782 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BladeAdapter — conditional-spread / nullish-omission / hyphenated-key
|
|
3
|
+
* regression tests (#textarea / #checkbox Phase 2b parity).
|
|
4
|
+
*
|
|
5
|
+
* Ported from `packages/adapter-twig/src/__tests__/twig-spread-attrs.test.ts`,
|
|
6
|
+
* translating each expected template string to Blade syntax. The shared
|
|
7
|
+
* adapter-conformance fixtures (`textarea`, `checkbox`) only exercise the
|
|
8
|
+
* falsy branch of the conditional spread and the unset branch of the
|
|
9
|
+
* optional attr, so these unit tests pin the truthy / present branches the
|
|
10
|
+
* fixtures can't reach.
|
|
11
|
+
*
|
|
12
|
+
* Translation notes beyond the mechanical Twig→Blade syntax swap (ternary
|
|
13
|
+
* unchanged; `bf.x(...)` → `$bf->x(...)`; `{'k': v}` hash → `['k' => v]`
|
|
14
|
+
* PHP array):
|
|
15
|
+
*
|
|
16
|
+
* 1. Array-literal keys are ALWAYS quoted in Blade too (`bladeHashKey`
|
|
17
|
+
* unconditionally single-quotes — a bareword key in a PHP array literal
|
|
18
|
+
* is a syntax error, not a variable lookup the way Twig's unquoted hash
|
|
19
|
+
* key would be, but this port quotes unconditionally anyway for the one
|
|
20
|
+
* uniform rule shared across the adapter family), so there is no "stays
|
|
21
|
+
* unquoted" case to assert for a bare-identifier-safe prop name like
|
|
22
|
+
* `size`; only that BOTH keys are correctly quoted (same note as the
|
|
23
|
+
* Twig/Jinja ports' file headers).
|
|
24
|
+
* 2. Twig's `is defined and is not null` null-guard on an `{% if %}` test
|
|
25
|
+
* becomes Blade's `isset($x)` — a SINGLE check (this file's port of the
|
|
26
|
+
* Twig adapter's divergence 6): PHP's `isset()` already returns `false`
|
|
27
|
+
* for both "never extracted into scope" and "extracted as null" in one
|
|
28
|
+
* call, verified empirically (see `blade-adapter.ts`'s file header).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { test, expect, describe } from 'bun:test'
|
|
32
|
+
import { compileJSX } from '@barefootjs/jsx'
|
|
33
|
+
import type { ComponentIR } from '@barefootjs/jsx'
|
|
34
|
+
import { BladeAdapter } from '../adapter'
|
|
35
|
+
|
|
36
|
+
function compileToIR(source: string, adapter?: BladeAdapter): ComponentIR {
|
|
37
|
+
const result = compileJSX(source.trimStart(), 'test.tsx', {
|
|
38
|
+
adapter: adapter ?? new BladeAdapter(),
|
|
39
|
+
outputIR: true,
|
|
40
|
+
})
|
|
41
|
+
const irFile = result.files.find(f => f.type === 'ir')
|
|
42
|
+
if (!irFile) throw new Error('No IR output')
|
|
43
|
+
return JSON.parse(irFile.content) as ComponentIR
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compileAndGenerate(source: string, adapter?: BladeAdapter) {
|
|
47
|
+
const a = adapter ?? new BladeAdapter()
|
|
48
|
+
const ir = compileToIR(source, a)
|
|
49
|
+
return a.generate(ir)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('BladeAdapter - conditional inline-object spread (textarea aria-describedby)', () => {
|
|
53
|
+
// `{...(cond ? { 'aria-describedby': cond } : {})}` lowers to a PHP inline
|
|
54
|
+
// ternary of arrays so the falsy `[]` branch OMITS the key
|
|
55
|
+
// ($bf->spread_attrs does not emit empty entries). The shared fixture only
|
|
56
|
+
// exercises the falsy branch; this pins the truthy one.
|
|
57
|
+
test('emits a PHP inline ternary of arrays through $bf->spread_attrs', () => {
|
|
58
|
+
const { template } = compileAndGenerate(`
|
|
59
|
+
function Box({ describedBy }: { describedBy?: string }) {
|
|
60
|
+
return <div {...(describedBy ? { 'aria-describedby': describedBy } : {})} />
|
|
61
|
+
}
|
|
62
|
+
`)
|
|
63
|
+
expect(template).toContain(
|
|
64
|
+
"$bf->spread_attrs(($bf->truthy($describedBy) ? ['aria-describedby' => $describedBy] : []))",
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('resolves the value reference and preserves the static key for a second prop', () => {
|
|
69
|
+
const { template } = compileAndGenerate(`
|
|
70
|
+
function Box({ label }: { label: string }) {
|
|
71
|
+
return <div {...(label ? { 'data-label': label } : {})} />
|
|
72
|
+
}
|
|
73
|
+
`)
|
|
74
|
+
expect(template).toContain(
|
|
75
|
+
"$bf->spread_attrs(($bf->truthy($label) ? ['data-label' => $label] : []))",
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('falls back to BF101 for a computed (non-static) object key', () => {
|
|
80
|
+
const adapter = new BladeAdapter()
|
|
81
|
+
const ir = compileToIR(`
|
|
82
|
+
function Box({ k, v }: { k?: string; v?: string }) {
|
|
83
|
+
return <div {...(v ? { [k]: v } : {})} />
|
|
84
|
+
}
|
|
85
|
+
`, adapter)
|
|
86
|
+
adapter.generate(ir)
|
|
87
|
+
const errs = (adapter as unknown as { errors: { code: string }[] }).errors
|
|
88
|
+
expect(errs.some(e => e.code === 'BF101')).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('BladeAdapter - local-const conditional-spread resolution (#checkbox icon)', () => {
|
|
93
|
+
// A FUNCTION-scope const holding a `cond ? {…} : {}` ternary, spread as a bare
|
|
94
|
+
// identifier (`{...attrs}`), resolves through the same Blade
|
|
95
|
+
// ternary-of-arrays lowering as the inline form. CheckIcon's
|
|
96
|
+
// `const sizeAttrs = size ? {…} : {}` is exactly this shape.
|
|
97
|
+
test('resolves a bare-identifier spread of a function-scope conditional const', () => {
|
|
98
|
+
const { template } = compileAndGenerate(`
|
|
99
|
+
function Box({ flag }: { flag?: boolean }) {
|
|
100
|
+
const attrs = flag ? { 'data-on': 'yes' } : {}
|
|
101
|
+
return <div {...attrs} />
|
|
102
|
+
}
|
|
103
|
+
`)
|
|
104
|
+
expect(template).toContain(
|
|
105
|
+
"$bf->spread_attrs(($bf->truthy($flag) ? ['data-on' => 'yes'] : []))",
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// A const that aliases another bare identifier must NOT be forwarded (loop
|
|
110
|
+
// guard): the resolver bails, so the spread falls through to the standard
|
|
111
|
+
// lowering emitting the bare `$attrs` variable.
|
|
112
|
+
test('does not forward a const that aliases another identifier (loop guard)', () => {
|
|
113
|
+
const { template } = compileAndGenerate(`
|
|
114
|
+
function Box({ other }: { other?: object }) {
|
|
115
|
+
const attrs = other
|
|
116
|
+
return <div {...attrs} />
|
|
117
|
+
}
|
|
118
|
+
`)
|
|
119
|
+
expect(template).toContain('$bf->spread_attrs($attrs)')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe('BladeAdapter - Record<staticKeys,scalar>[propKey] spread value (#checkbox icon)', () => {
|
|
124
|
+
// `const sizeMap: Record<IconSize, number> = { sm: 16, ... }` indexed by a
|
|
125
|
+
// prop inside a conditional-spread object value lowers to an inline
|
|
126
|
+
// bracket-indexed PHP array `[...][$key]` — the SAME bracket-index syntax
|
|
127
|
+
// JS itself uses (unlike Kolon, which had to steer around Perl's
|
|
128
|
+
// arrow-deref `->{$key}` to the bracket form). This is CheckIcon's
|
|
129
|
+
// `{ width: sizeMap[size], height: sizeMap[size] }` shape.
|
|
130
|
+
test('lowers an indexed module-const map to an inline bracket-indexed array', () => {
|
|
131
|
+
const { template } = compileAndGenerate(`
|
|
132
|
+
const sizeMap: Record<string, number> = { sm: 16, md: 20, lg: 24, xl: 32 }
|
|
133
|
+
function Box({ size }: { size?: string }) {
|
|
134
|
+
const attrs = size ? { width: sizeMap[size] } : {}
|
|
135
|
+
return <div {...attrs} />
|
|
136
|
+
}
|
|
137
|
+
`)
|
|
138
|
+
expect(template).toContain(
|
|
139
|
+
"['sm' => 16, 'md' => 20, 'lg' => 24, 'xl' => 32][$size]",
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('lowers string-valued record maps too', () => {
|
|
144
|
+
const { template } = compileAndGenerate(`
|
|
145
|
+
const labelMap: Record<string, string> = { a: 'Alpha', b: 'Beta' }
|
|
146
|
+
function Box({ k }: { k?: string }) {
|
|
147
|
+
const attrs = k ? { 'data-label': labelMap[k] } : {}
|
|
148
|
+
return <div {...attrs} />
|
|
149
|
+
}
|
|
150
|
+
`)
|
|
151
|
+
expect(template).toContain("['a' => 'Alpha', 'b' => 'Beta'][$k]")
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// A non-scalar record value (object) is out of shape: the spread object value
|
|
155
|
+
// can't lower, so the whole spread falls back to BF101.
|
|
156
|
+
test('refuses a non-scalar record value with BF101 (out-of-shape fallback)', () => {
|
|
157
|
+
const adapter = new BladeAdapter()
|
|
158
|
+
const ir = compileToIR(`
|
|
159
|
+
const sizeMap: Record<string, object> = { sm: { w: 1 } }
|
|
160
|
+
function Box({ size }: { size?: string }) {
|
|
161
|
+
const attrs = size ? { width: sizeMap[size] } : {}
|
|
162
|
+
return <div {...attrs} />
|
|
163
|
+
}
|
|
164
|
+
`, adapter)
|
|
165
|
+
adapter.generate(ir)
|
|
166
|
+
const errs = (adapter as unknown as { errors: { code: string }[] }).errors
|
|
167
|
+
expect(errs.some(e => e.code === 'BF101')).toBe(true)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('BladeAdapter - props-object inherited-attribute enumeration (#checkbox)', () => {
|
|
172
|
+
// A SolidJS props-object component reads inherited attributes (`props.id`)
|
|
173
|
+
// not enumerated in `propsParams`. The bare optional attribute must be
|
|
174
|
+
// guarded so it's omitted when unset (Hono parity), even though `id`
|
|
175
|
+
// isn't a declared param.
|
|
176
|
+
test('guards a props-object bare optional attr (props.id) with isset()', () => {
|
|
177
|
+
const { template } = compileAndGenerate(`
|
|
178
|
+
"use client"
|
|
179
|
+
interface P { tone?: string }
|
|
180
|
+
export function Widget(props: P) {
|
|
181
|
+
return <button id={props.id}>x</button>
|
|
182
|
+
}
|
|
183
|
+
`)
|
|
184
|
+
expect(template).toContain('@if(isset($id))')
|
|
185
|
+
expect(template).toContain('id="{!! e($bf->string($id)) !!}"')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('BladeAdapter - hyphenated child attr dict key (#checkbox)', () => {
|
|
190
|
+
// A child component prop whose JSX name isn't a bare identifier
|
|
191
|
+
// (`<CheckIcon data-slot="..."/>`) must be quoted in the `render_child`
|
|
192
|
+
// array — same as EVERY other key, since Blade array-literal keys are
|
|
193
|
+
// ALWAYS quoted (see the file header for why this diverges from the
|
|
194
|
+
// Kolon port's "only quote when non-bareword-safe" assertion).
|
|
195
|
+
test('quotes every child attribute name in render_child, hyphenated or not', () => {
|
|
196
|
+
const { template } = compileAndGenerate(`
|
|
197
|
+
"use client"
|
|
198
|
+
import { Leaf } from './leaf'
|
|
199
|
+
export function Host() {
|
|
200
|
+
return <div><Leaf data-slot="indicator" size="sm" /></div>
|
|
201
|
+
}
|
|
202
|
+
`)
|
|
203
|
+
expect(template).toContain("'data-slot' => 'indicator'")
|
|
204
|
+
expect(template).toContain("'size' => 'sm'")
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('BladeAdapter - nullish optional-attribute omission (textarea rows)', () => {
|
|
209
|
+
// A no-destructure-default, nillable-typed prop is `null` when the caller
|
|
210
|
+
// omits it; guard its bare-reference attribute with Blade's `isset($x)`
|
|
211
|
+
// test so it DROPS instead of rendering `attr=""` — matching Hono's
|
|
212
|
+
// nullish-attribute omission. Concrete/defaulted props are never `null`
|
|
213
|
+
// and stay unconditional.
|
|
214
|
+
test('guards a no-default nillable attr with an isset() check', () => {
|
|
215
|
+
const { template } = compileAndGenerate(`
|
|
216
|
+
function C({ rows }: { rows?: number }) {
|
|
217
|
+
return <textarea rows={rows} />
|
|
218
|
+
}
|
|
219
|
+
`)
|
|
220
|
+
expect(template).toContain('@if(isset($rows))')
|
|
221
|
+
expect(template).toContain('rows="{!! e($bf->string($rows)) !!}"')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('leaves a defaulted attr unconditional (scope did not widen)', () => {
|
|
225
|
+
const { template } = compileAndGenerate(`
|
|
226
|
+
function C({ value = '' }: { value?: string }) {
|
|
227
|
+
return <textarea value={value} />
|
|
228
|
+
}
|
|
229
|
+
`)
|
|
230
|
+
// `value` has a destructure default → never null → unconditional, exactly
|
|
231
|
+
// like Hono's value="".
|
|
232
|
+
expect(template).toContain('value="{!! e($bf->string($value)) !!}"')
|
|
233
|
+
expect(template).not.toContain('@if(isset(')
|
|
234
|
+
})
|
|
235
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-tree analysis for the Blade template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-jinja/src/adapter/analysis/component-tree.ts`.
|
|
5
|
+
* Pure functions over the IR — they read no adapter instance state.
|
|
6
|
+
* `collectImportedLoopChildComponentErrors` returns its diagnostics instead
|
|
7
|
+
* of pushing onto the adapter's error list, so the adapter stays the sole
|
|
8
|
+
* owner of `errors`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ComponentIR,
|
|
13
|
+
IRNode,
|
|
14
|
+
IRComponent,
|
|
15
|
+
IRElement,
|
|
16
|
+
IRFragment,
|
|
17
|
+
IRConditional,
|
|
18
|
+
IRLoop,
|
|
19
|
+
IRIfStatement,
|
|
20
|
+
IRProvider,
|
|
21
|
+
IRAsync,
|
|
22
|
+
CompilerError,
|
|
23
|
+
} from '@barefootjs/jsx'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether the component needs the client runtime — it owns reactive state
|
|
27
|
+
* (signals / effects / onMount) or the analyzer flagged it as needing init.
|
|
28
|
+
*/
|
|
29
|
+
export function hasClientInteractivity(ir: ComponentIR): boolean {
|
|
30
|
+
return (
|
|
31
|
+
ir.metadata.signals.length > 0 ||
|
|
32
|
+
ir.metadata.effects.length > 0 ||
|
|
33
|
+
ir.metadata.onMounts.length > 0 ||
|
|
34
|
+
(ir.metadata.clientAnalysis?.needsInit ?? false)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build a `BF103` diagnostic for every component reference inside a loop body
|
|
40
|
+
* whose name is imported from a relative-path module. Mirror of the Go /
|
|
41
|
+
* Jinja adapter's check — the Blade adapter has the same
|
|
42
|
+
* cross-template-registration constraint at request time (each `.blade.php`
|
|
43
|
+
* component file must be registered with the shared Blade `Environment`
|
|
44
|
+
* loader alongside the parent). Returns the diagnostics so the caller pushes
|
|
45
|
+
* them onto its own error list.
|
|
46
|
+
*/
|
|
47
|
+
export function collectImportedLoopChildComponentErrors(
|
|
48
|
+
ir: ComponentIR,
|
|
49
|
+
componentName: string,
|
|
50
|
+
): CompilerError[] {
|
|
51
|
+
const errors: CompilerError[] = []
|
|
52
|
+
const relativeImports = new Set<string>()
|
|
53
|
+
for (const imp of ir.metadata.templateImports ?? ir.metadata.imports ?? []) {
|
|
54
|
+
if (!imp.source.startsWith('./') && !imp.source.startsWith('../')) continue
|
|
55
|
+
if (imp.isTypeOnly) continue
|
|
56
|
+
for (const spec of imp.specifiers) {
|
|
57
|
+
relativeImports.add(spec.alias ?? spec.name)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (relativeImports.size === 0) return errors
|
|
61
|
+
|
|
62
|
+
const loc = { file: componentName + '.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } }
|
|
63
|
+
const visit = (node: IRNode, inLoop: boolean): void => {
|
|
64
|
+
switch (node.type) {
|
|
65
|
+
case 'component': {
|
|
66
|
+
const comp = node as IRComponent
|
|
67
|
+
if (inLoop && relativeImports.has(comp.name)) {
|
|
68
|
+
errors.push({
|
|
69
|
+
code: 'BF103',
|
|
70
|
+
severity: 'error',
|
|
71
|
+
message: `Component <${comp.name}> is imported from a sibling module and used inside a loop. The Blade adapter emits a cross-template call; the child template must be registered alongside the parent at render time.`,
|
|
72
|
+
loc: comp.loc ?? loc,
|
|
73
|
+
suggestion: {
|
|
74
|
+
message:
|
|
75
|
+
`Options:\n` +
|
|
76
|
+
` 1. Compile '${comp.name}' (its source file) with the same adapter and register the resulting Blade template alongside the parent at render time.\n` +
|
|
77
|
+
` 2. Inline <${comp.name}> directly inside the loop body so no cross-file template lookup is needed.\n` +
|
|
78
|
+
` 3. Mark the loop position as @client-only so the template is materialised on the client instead of at SSR time.`,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
for (const child of comp.children) visit(child, inLoop)
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
case 'element':
|
|
86
|
+
for (const child of (node as IRElement).children) visit(child, inLoop)
|
|
87
|
+
break
|
|
88
|
+
case 'fragment':
|
|
89
|
+
for (const child of (node as IRFragment).children) visit(child, inLoop)
|
|
90
|
+
break
|
|
91
|
+
case 'conditional': {
|
|
92
|
+
const cond = node as IRConditional
|
|
93
|
+
visit(cond.whenTrue, inLoop)
|
|
94
|
+
if (cond.whenFalse) visit(cond.whenFalse, inLoop)
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
case 'loop':
|
|
98
|
+
for (const child of (node as IRLoop).children) visit(child, true)
|
|
99
|
+
break
|
|
100
|
+
case 'if-statement': {
|
|
101
|
+
const stmt = node as IRIfStatement
|
|
102
|
+
visit(stmt.consequent, inLoop)
|
|
103
|
+
if (stmt.alternate) visit(stmt.alternate, inLoop)
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
case 'provider':
|
|
107
|
+
for (const child of (node as IRProvider).children) visit(child, inLoop)
|
|
108
|
+
break
|
|
109
|
+
case 'async': {
|
|
110
|
+
const a = node as IRAsync
|
|
111
|
+
visit(a.fallback, inLoop)
|
|
112
|
+
for (const child of a.children) visit(child, inLoop)
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
visit(ir.root, false)
|
|
118
|
+
return errors
|
|
119
|
+
}
|