@barefootjs/go-template 0.35.1 → 0.35.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/adapter/emit-context.d.ts +16 -11
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +55 -22
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +52 -48
- package/dist/adapter/props/prop-classes.d.ts +13 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -1
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +58 -49
- package/dist/vite.js +53 -49
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +33 -2
- package/src/adapter/emit-context.ts +14 -12
- package/src/adapter/go-template-adapter.ts +156 -70
- package/src/adapter/props/prop-classes.ts +20 -1
- package/src/adapter/value/value-lowering.ts +6 -7
- package/src/conformance-pins.ts +15 -0
|
@@ -262,8 +262,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
262
262
|
extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
|
|
263
263
|
extractCollisionDerivation: (parsed) => this.extractCollisionDerivation(parsed),
|
|
264
264
|
resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
|
|
265
|
-
|
|
266
|
-
resolveModuleBooleanConst: (name) => this.resolveModuleBooleanConst(name),
|
|
265
|
+
resolveModuleConstAsGo: (name, target) => this.resolveModuleConstAsGo(name, target),
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
/** Diagnostics from the current compile (backed by `CompileState`); `generate()` also merges these into `ir.errors`. */
|
|
@@ -4726,11 +4725,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4726
4725
|
}
|
|
4727
4726
|
const inlined = this.resolveModuleStringConst(name)
|
|
4728
4727
|
if (inlined !== null) return inlined
|
|
4729
|
-
// Module
|
|
4730
|
-
// inline the literal value rather than emit
|
|
4731
|
-
// field that never exists. Mirrors the
|
|
4732
|
-
const
|
|
4733
|
-
|
|
4728
|
+
// Module scalar const (e.g. `const TRACK = 8` used in a width expression,
|
|
4729
|
+
// or `const OPEN = true`): inline the literal value rather than emit
|
|
4730
|
+
// `{{.TRACK}}` against a Props field that never exists. Mirrors the
|
|
4731
|
+
// string-const inlining above.
|
|
4732
|
+
const inlinedScalar = this.resolveModuleConstAsGo(name, { kind: 'template-action' })
|
|
4733
|
+
if (inlinedScalar !== null) return inlinedScalar
|
|
4734
4734
|
if (this.isCurrentLoopItem(name)) return '.'
|
|
4735
4735
|
// An *outer* loop's value variable (we're in a nested loop) is in scope as
|
|
4736
4736
|
// the Go range variable `$name` declared by that loop's `{{range … := …}}`;
|
|
@@ -4866,19 +4866,41 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4866
4866
|
* rebinds. Outside any loop the root *is* the dot, so we emit `.Field`.
|
|
4867
4867
|
*/
|
|
4868
4868
|
private rootFieldRef(name: string): string {
|
|
4869
|
-
|
|
4870
|
-
// signal/memo getter (`const items__alias = items`) or a bare-props-
|
|
4871
|
-
// form body destructure's renamed prop (`const { children: kids } =
|
|
4872
|
-
// props`) — resolve it to the ALIASED entity's own name FIRST, so the
|
|
4873
|
-
// emitted field matches what that entity itself seeds (`.Items`,
|
|
4874
|
-
// `.Children`) instead of registering a second, never-populated field
|
|
4875
|
-
// under the local alias (`.Items__alias`, `.Kids`).
|
|
4876
|
-
const resolved = this.state.getterAliases.get(name) ?? this.state.propDestructureAliases.get(name) ?? name
|
|
4869
|
+
const resolved = this.resolveRootFieldAlias(name)
|
|
4877
4870
|
this.state.templateReadRootFields.add(resolved)
|
|
4878
4871
|
const prefix = this.inLoop ? '$.' : '.'
|
|
4879
4872
|
return `${prefix}${capitalizeFieldName(resolved)}`
|
|
4880
4873
|
}
|
|
4881
4874
|
|
|
4875
|
+
/**
|
|
4876
|
+
* #2813 / #2788: `name` may be a bare local-const alias of a signal/memo
|
|
4877
|
+
* getter (`const items__alias = items`) or a bare-props-form body
|
|
4878
|
+
* destructure's renamed prop (`const { children: kids } = props`) —
|
|
4879
|
+
* resolve it to the ALIASED entity's own name, so a caller that needs to
|
|
4880
|
+
* build its own prefixed field reference (e.g. `renderFilterExprNode`,
|
|
4881
|
+
* which must hardcode `$.` regardless of `rootFieldRef`'s
|
|
4882
|
+
* `this.inLoop`-conditional prefix — #2857) still lands on the field that
|
|
4883
|
+
* entity itself seeds (`.Items`, `.Children`) instead of a never-populated
|
|
4884
|
+
* field under the local alias (`.Items__alias`, `.Kids`). `rootFieldRef`
|
|
4885
|
+
* itself is built on top of this so there is exactly one resolution.
|
|
4886
|
+
*/
|
|
4887
|
+
private resolveRootFieldAlias(name: string): string {
|
|
4888
|
+
return this.state.getterAliases.get(name) ?? this.state.propDestructureAliases.get(name) ?? name
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
/**
|
|
4892
|
+
* Root-scope field reference from INSIDE a `.filter()`/`.find()` predicate
|
|
4893
|
+
* (#2857, #2879). Same registration + alias resolution as `rootFieldRef`,
|
|
4894
|
+
* but the `$.` prefix is unconditional: a filter predicate's `{{if}}`
|
|
4895
|
+
* always sits inside its loop's own `{{range}}` (`renderLoop` restores
|
|
4896
|
+
* `this.inLoop` before rendering it), so a bare `.Field` would resolve
|
|
4897
|
+
* against the current row's type instead of root.
|
|
4898
|
+
*/
|
|
4899
|
+
private filterRootFieldRef(name: string): string {
|
|
4900
|
+
this.rootFieldRef(name)
|
|
4901
|
+
return `$.${capitalizeFieldName(this.resolveRootFieldAlias(name))}`
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4882
4904
|
/**
|
|
4883
4905
|
* When `name` is a local binding of the `searchParams()` env signal, resolve
|
|
4884
4906
|
* it to the canonical `.SearchParams` field — not `.<Capitalized name>` — so
|
|
@@ -4926,13 +4948,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4926
4948
|
}
|
|
4927
4949
|
|
|
4928
4950
|
/**
|
|
4929
|
-
* The single module-const lookup shared by `
|
|
4930
|
-
*
|
|
4931
|
-
*
|
|
4932
|
-
*
|
|
4933
|
-
* would grow `binding-scope-ratchet.test.ts`'s shrink-only floor for this
|
|
4934
|
-
* file (already at 5) for a shape variance the callers can express
|
|
4935
|
-
* themselves instead.
|
|
4951
|
+
* The single module-const lookup shared by `resolveModuleConstAsGo` —
|
|
4952
|
+
* kept as its own method (rather than inlined) so a second `.find(` isn't
|
|
4953
|
+
* added elsewhere, growing `binding-scope-ratchet.test.ts`'s shrink-only
|
|
4954
|
+
* floor for this file (already at 5).
|
|
4936
4955
|
*/
|
|
4937
4956
|
private findModuleConst(name: string): ConstantInfo | undefined {
|
|
4938
4957
|
return this.state.localConstants.find(
|
|
@@ -4941,40 +4960,50 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4941
4960
|
}
|
|
4942
4961
|
|
|
4943
4962
|
/**
|
|
4944
|
-
*
|
|
4945
|
-
*
|
|
4946
|
-
*
|
|
4947
|
-
*
|
|
4948
|
-
*
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
*
|
|
4965
|
-
*
|
|
4966
|
-
*
|
|
4967
|
-
*
|
|
4968
|
-
*
|
|
4963
|
+
* Resolve a bare identifier naming a plain module-level const (`const
|
|
4964
|
+
* TRACK = 8`, `const OPEN = true`, `const INITIAL: Row[] = [...]`, #2794 /
|
|
4965
|
+
* #2815 / #2862) to its value's Go literal — dispatched STRUCTURALLY off
|
|
4966
|
+
* `ConstantInfo.parsed`, through the same `parsedLiteralToGo` door an
|
|
4967
|
+
* inline `createSignal([...])`/`createSignal({...})` seed already takes,
|
|
4968
|
+
* rather than a separate per-type text-matching resolver for each literal
|
|
4969
|
+
* shape (the numeric/boolean resolvers this replaces, and the array/
|
|
4970
|
+
* object-literal gap #2862 tracked, were exactly that repeated pattern).
|
|
4971
|
+
* `resolveModuleStringConst` stays separate: its fixed-point text
|
|
4972
|
+
* resolution also covers a COMPOSED template-literal const (`` `${A}-${B}`
|
|
4973
|
+
* ``) that has no single structural literal to bake.
|
|
4974
|
+
*
|
|
4975
|
+
* `target` names the emission position, which constrains what can resolve
|
|
4976
|
+
* there:
|
|
4977
|
+
* - `go-source` (the `New<Component>Props` constructor) may bake a
|
|
4978
|
+
* composite (array/object) literal against `bakeType` — falling back
|
|
4979
|
+
* to the const's OWN declared type when the consumer's type is
|
|
4980
|
+
* `unknown` (the analyzer's inference is text-shaped and never chases
|
|
4981
|
+
* a bare `createSignal(INITIAL)` seed to its declaration).
|
|
4982
|
+
* - `template-action` (a bare `{{...}}` splice, e.g. a className
|
|
4983
|
+
* expression) has no valid Go template spelling for a composite
|
|
4984
|
+
* literal, so only a scalar (or unary-minus number) resolves there —
|
|
4985
|
+
* the same restriction the numeric/boolean resolvers already had.
|
|
4986
|
+
*
|
|
4987
|
+
* Scoped to module consts and guarded against loop vars so a range
|
|
4988
|
+
* variable that shadows a const name still wins (same guards the
|
|
4989
|
+
* resolvers this replaces already had).
|
|
4969
4990
|
*/
|
|
4970
|
-
private
|
|
4991
|
+
private resolveModuleConstAsGo(
|
|
4992
|
+
name: string,
|
|
4993
|
+
target: { kind: 'template-action' } | { kind: 'go-source'; bakeType: TypeInfo },
|
|
4994
|
+
): string | null {
|
|
4971
4995
|
if (this.isCurrentLoopItem(name)) return null
|
|
4972
4996
|
if (this.loopVarRefCount.has(name)) return null
|
|
4973
4997
|
if (this.isOuterLoopParam(name)) return null
|
|
4974
4998
|
const c = this.findModuleConst(name)
|
|
4975
|
-
if (!c
|
|
4976
|
-
|
|
4977
|
-
|
|
4999
|
+
if (!c?.parsed) return null
|
|
5000
|
+
if (target.kind === 'template-action') {
|
|
5001
|
+
return c.parsed.kind === 'literal' || c.parsed.kind === 'unary'
|
|
5002
|
+
? parsedLiteralToGo(this.emitCtx, c.parsed)
|
|
5003
|
+
: null
|
|
5004
|
+
}
|
|
5005
|
+
const bakeType = target.bakeType.kind !== 'unknown' ? target.bakeType : (c.type ?? undefined)
|
|
5006
|
+
return parsedLiteralToGo(this.emitCtx, c.parsed, bakeType)
|
|
4978
5007
|
}
|
|
4979
5008
|
|
|
4980
5009
|
literal(value: string | number | boolean | null, literalType: LiteralType): string {
|
|
@@ -6212,12 +6241,28 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6212
6241
|
// prefix because a filter predicate must always escape back to
|
|
6213
6242
|
// root regardless of loop nesting; call it only for its
|
|
6214
6243
|
// registration side effect and keep the prefix here (pullfrog
|
|
6215
|
-
// review, PR #2818).
|
|
6216
|
-
|
|
6217
|
-
|
|
6244
|
+
// review, PR #2818). The field name itself DOES go through
|
|
6245
|
+
// `resolveRootFieldAlias` (#2857) so a getter-alias local
|
|
6246
|
+
// (`const items__alias = items`) still emits the field the
|
|
6247
|
+
// signal itself seeds instead of a phantom `.Items__alias`.
|
|
6248
|
+
return this.filterRootFieldRef(signal)
|
|
6218
6249
|
}
|
|
6219
|
-
|
|
6220
|
-
|
|
6250
|
+
// Any other identifier reaching here is ALSO a root-scope
|
|
6251
|
+
// reference (a memo, a plain derived const, or — #2857 — a
|
|
6252
|
+
// bare-props-form destructured/renamed prop) rather than the
|
|
6253
|
+
// loop's own param, so it needs the same unconditional `$.`
|
|
6254
|
+
// escape as the signal branch above, for the same reason: a
|
|
6255
|
+
// `.filter().map()` JSX loop's `{{if}}` gate (built from
|
|
6256
|
+
// `loop.filterPredicate` a few hundred lines below, via
|
|
6257
|
+
// `renderPredicateCondition` → this method) always sits inside
|
|
6258
|
+
// that loop's own `{{range}}`, never at the template's un-rebound
|
|
6259
|
+
// root, so `rootFieldRef`'s own `this.inLoop`-conditional prefix
|
|
6260
|
+
// would be wrong here even without the alias bug — a bare
|
|
6261
|
+
// `.Field` resolves against the CURRENT LOOP ITEM's type, not
|
|
6262
|
+
// root, and `html/template` panics at execute time (`can't
|
|
6263
|
+
// evaluate field ... in type ...`) the first time such a
|
|
6264
|
+
// reference is exercised.
|
|
6265
|
+
return this.filterRootFieldRef(expr.name)
|
|
6221
6266
|
}
|
|
6222
6267
|
|
|
6223
6268
|
case 'literal':
|
|
@@ -6234,6 +6279,18 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6234
6279
|
if (expr.object.kind === 'identifier' && expr.object.name === param) {
|
|
6235
6280
|
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`
|
|
6236
6281
|
}
|
|
6282
|
+
// props.x on a bare-props-form component (#2879): props are
|
|
6283
|
+
// flattened onto the root struct, so this is a root-scope read
|
|
6284
|
+
// (`$.X`), not `.Props.X`. Mirrors `renderConditionExpr`'s own
|
|
6285
|
+
// `propsObjectName` branch; `props.a.b` still reaches root through
|
|
6286
|
+
// the generic recursion below.
|
|
6287
|
+
if (
|
|
6288
|
+
expr.object.kind === 'identifier' &&
|
|
6289
|
+
this.state.propsObjectName &&
|
|
6290
|
+
expr.object.name === this.state.propsObjectName
|
|
6291
|
+
) {
|
|
6292
|
+
return this.filterRootFieldRef(expr.property)
|
|
6293
|
+
}
|
|
6237
6294
|
// `.length` on a higher-order filter result (e.g.
|
|
6238
6295
|
// `x.tags.filter(t => t.active).length > 0`). Reuse
|
|
6239
6296
|
// `renderFilterLengthExpr` so the inner filter lowers to
|
|
@@ -6262,11 +6319,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6262
6319
|
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`
|
|
6263
6320
|
}
|
|
6264
6321
|
// Signal calls: `filter()` -> `$.Filter`. Same registration-only
|
|
6265
|
-
// `rootFieldRef` call
|
|
6322
|
+
// `rootFieldRef` call — and the same `resolveRootFieldAlias` field
|
|
6323
|
+
// resolution (#2857) — as the `identifier` case above, for the same
|
|
6266
6324
|
// reason (#2700's BF101 gate; pullfrog review, PR #2818).
|
|
6267
6325
|
if (expr.callee.kind === 'identifier' && expr.args.length === 0) {
|
|
6268
|
-
this.
|
|
6269
|
-
return `$.${capitalizeFieldName(expr.callee.name)}`
|
|
6326
|
+
return this.filterRootFieldRef(expr.callee.name)
|
|
6270
6327
|
}
|
|
6271
6328
|
// A nested callback method call (`other.some(r => …)`) reaching this
|
|
6272
6329
|
// arm has no Go template form in filter context — the fallthrough
|
|
@@ -6302,30 +6359,50 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6302
6359
|
if (this.filterExprUnsupported) return 'false'
|
|
6303
6360
|
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField)
|
|
6304
6361
|
if (this.filterExprUnsupported) return 'false'
|
|
6362
|
+
// #2873 (pullfrog review, PR #2882): every case below is a prefix
|
|
6363
|
+
// function call (`bf_mod a b`, `eq a b`, …), so a COMPOUND operand —
|
|
6364
|
+
// itself a nested binary result like `bf_mod .N 2` — must be
|
|
6365
|
+
// parenthesised or the template parser folds its tokens into the
|
|
6366
|
+
// outer call's argument list (`eq bf_mod .N 2 0` hands `eq` four
|
|
6367
|
+
// args, and a bare func-name argument like `bf_mod` there is called
|
|
6368
|
+
// with ZERO args instead of nested: `wrong number of args for
|
|
6369
|
+
// bf_mod: want 2 got 0`). Mirrors the general (non-filter) binary
|
|
6370
|
+
// emitter's `wl`/`wr` wrapping (`wrapIfMultiToken`, above) — this
|
|
6371
|
+
// switch had never applied it, so `-`/`*`/`/` had the identical
|
|
6372
|
+
// pre-existing gap for a nested compound operand.
|
|
6373
|
+
const wl = wrapIfMultiToken(left)
|
|
6374
|
+
const wr = wrapIfMultiToken(right)
|
|
6305
6375
|
|
|
6306
6376
|
switch (expr.op) {
|
|
6307
6377
|
case '===':
|
|
6308
6378
|
case '==':
|
|
6309
|
-
return `eq ${
|
|
6379
|
+
return `eq ${wl} ${wr}`
|
|
6310
6380
|
case '!==':
|
|
6311
6381
|
case '!=':
|
|
6312
|
-
return `ne ${
|
|
6382
|
+
return `ne ${wl} ${wr}`
|
|
6313
6383
|
case '>':
|
|
6314
|
-
return `gt ${
|
|
6384
|
+
return `gt ${wl} ${wr}`
|
|
6315
6385
|
case '<':
|
|
6316
|
-
return `lt ${
|
|
6386
|
+
return `lt ${wl} ${wr}`
|
|
6317
6387
|
case '>=':
|
|
6318
|
-
return `ge ${
|
|
6388
|
+
return `ge ${wl} ${wr}`
|
|
6319
6389
|
case '<=':
|
|
6320
|
-
return `le ${
|
|
6390
|
+
return `le ${wl} ${wr}`
|
|
6321
6391
|
case '+':
|
|
6322
|
-
return this._emitPlus(expr.left, expr.right,
|
|
6392
|
+
return this._emitPlus(expr.left, expr.right, wl, wr)
|
|
6323
6393
|
case '-':
|
|
6324
|
-
return `bf_sub ${
|
|
6394
|
+
return `bf_sub ${wl} ${wr}`
|
|
6325
6395
|
case '*':
|
|
6326
|
-
return `bf_mul ${
|
|
6396
|
+
return `bf_mul ${wl} ${wr}`
|
|
6327
6397
|
case '/':
|
|
6328
|
-
return `bf_div ${
|
|
6398
|
+
return `bf_div ${wl} ${wr}`
|
|
6399
|
+
case '%':
|
|
6400
|
+
// #2873: mirrors the general binary emitter's `%` case (below,
|
|
6401
|
+
// `bf_mod`) — this switch previously had no `%` case, so the
|
|
6402
|
+
// `default` arm emitted a literal ` % ` into the template text,
|
|
6403
|
+
// which `html/template` can't parse (`unexpected "%" in
|
|
6404
|
+
// operand`).
|
|
6405
|
+
return `bf_mod ${wl} ${wr}`
|
|
6329
6406
|
default:
|
|
6330
6407
|
return `${left} ${expr.op} ${right}`
|
|
6331
6408
|
}
|
|
@@ -6571,7 +6648,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6571
6648
|
* `loopVarRefCount` — so both are still consulted. Shared by the
|
|
6572
6649
|
* string-keyed fast paths (#2236, #2242 Copilot review) that resolve raw
|
|
6573
6650
|
* `jsExpr` text before `identifier()`'s own guards can see it. Mirrors the
|
|
6574
|
-
* checks in `resolveModuleStringConst` / `
|
|
6651
|
+
* checks in `resolveModuleStringConst` / `resolveModuleConstAsGo`.
|
|
6575
6652
|
*/
|
|
6576
6653
|
private isLoopShadowedName(name: string): boolean {
|
|
6577
6654
|
return this.scope.isBound(name) || this.loopVarRefCount.has(name)
|
|
@@ -7127,6 +7204,15 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7127
7204
|
result = `bf_mul ${left} ${right}`; break
|
|
7128
7205
|
case '/':
|
|
7129
7206
|
result = `bf_div ${left} ${right}`; break
|
|
7207
|
+
case '%':
|
|
7208
|
+
// #2873: this switch had no `%` case even though
|
|
7209
|
+
// `needsParensInGoTemplate` (above) already treats `%` as an
|
|
7210
|
+
// arithmetic operator needing parens — so the `default` arm
|
|
7211
|
+
// below emitted a literal ` % ` into a ternary/condition's
|
|
7212
|
+
// template text, which `html/template` can't parse
|
|
7213
|
+
// (`unexpected "%" in operand`). Mirrors the general binary
|
|
7214
|
+
// emitter's `%` case (`bf_mod`, outside condition position).
|
|
7215
|
+
result = `bf_mod ${left} ${right}`; break
|
|
7130
7216
|
default:
|
|
7131
7217
|
result = `${left} ${expr.op} ${right}`
|
|
7132
7218
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* function over `ir.metadata`; no adapter instance state.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { collectLoopBoundNames, type ComponentIR, type TypeInfo } from '@barefootjs/jsx'
|
|
10
|
+
import { collectLoopBoundNames, resolveBodyDestructuredPropAliases, type ComponentIR, type TypeInfo } from '@barefootjs/jsx'
|
|
11
11
|
|
|
12
12
|
/** True when `type` is the `string` primitive. */
|
|
13
13
|
function isStringTypeInfo(type: TypeInfo): boolean {
|
|
@@ -49,6 +49,19 @@ function isBareStringLiteral(initialValue: string | undefined): boolean {
|
|
|
49
49
|
* string elsewhere in the component) but safe: the suppressed case just
|
|
50
50
|
* falls back to today's numeric `bf_add` — the same, already-accepted
|
|
51
51
|
* residual as an unresolvable operand — never silently-wrong output.
|
|
52
|
+
*
|
|
53
|
+
* A bare-props-form component's BODY-destructured, RENAMED prop (`const {
|
|
54
|
+
* fallbackLabel: skipLabel } = props`, the #2788 alias family) has no
|
|
55
|
+
* `propsParams` entry of its own — `propsParams` only carries the TYPE-level,
|
|
56
|
+
* caller-facing names, and the local alias never appears there. Without the
|
|
57
|
+
* alias resolved here too, a string-typed prop referenced only under its
|
|
58
|
+
* local rename in a `+` concat (`greeting + skipLabel`) is invisible to this
|
|
59
|
+
* witness, so `isStringConcatBinary` falls through to numeric `bf_add`
|
|
60
|
+
* instead of `bf_concat_str` (#2894, the go-template sibling of #2883's
|
|
61
|
+
* Mojolicious fix). `resolveBodyDestructuredPropAliases` already recognizes
|
|
62
|
+
* this exact destructuring shape; reusing it here keeps this one witness the
|
|
63
|
+
* single source of truth `_isStringValueName` reads, rather than growing a
|
|
64
|
+
* second, adapter-local alias lookup.
|
|
52
65
|
*/
|
|
53
66
|
export function collectStringValueNames(ir: ComponentIR): Set<string> {
|
|
54
67
|
const names = new Set<string>()
|
|
@@ -60,6 +73,12 @@ export function collectStringValueNames(ir: ComponentIR): Set<string> {
|
|
|
60
73
|
for (const p of ir.metadata.propsParams) {
|
|
61
74
|
if (isStringTypeInfo(p.type)) names.add(p.name)
|
|
62
75
|
}
|
|
76
|
+
for (const [local, callerKey] of resolveBodyDestructuredPropAliases(
|
|
77
|
+
ir.metadata.localConstants ?? [],
|
|
78
|
+
ir.metadata.propsObjectName,
|
|
79
|
+
)) {
|
|
80
|
+
if (names.has(callerKey)) names.add(local)
|
|
81
|
+
}
|
|
63
82
|
for (const c of ir.metadata.localConstants) {
|
|
64
83
|
if ((c.type !== null && isStringTypeInfo(c.type)) || isBareStringLiteral(c.value)) {
|
|
65
84
|
names.add(c.name)
|
|
@@ -94,9 +94,10 @@ export function convertInitialValue(
|
|
|
94
94
|
if (param) {
|
|
95
95
|
return propRef(param)
|
|
96
96
|
}
|
|
97
|
-
// Module-const seed (#2794): a signal seeded from a bare
|
|
98
|
-
// that refers to a module-level const (`const PAYLOAD = 'x';
|
|
99
|
-
// createSignal(PAYLOAD)
|
|
97
|
+
// Module-const seed (#2794/#2815/#2862): a signal seeded from a bare
|
|
98
|
+
// identifier that refers to a module-level const (`const PAYLOAD = 'x';
|
|
99
|
+
// createSignal(PAYLOAD)`, or `const INITIAL: Row[] = [...];
|
|
100
|
+
// createSignal(INITIAL)`) types `unknown` — the analyzer's type
|
|
100
101
|
// inference is text-shaped and never chases an identifier to its
|
|
101
102
|
// declaration — so none of the typed branches below ever see it and
|
|
102
103
|
// this used to fall through to the final `nil`. Checked AFTER the
|
|
@@ -107,10 +108,8 @@ export function convertInitialValue(
|
|
|
107
108
|
// leaving that case on the pre-existing `nil` path unchanged.
|
|
108
109
|
const inlinedStr = ctx.resolveModuleStringConst(value)
|
|
109
110
|
if (inlinedStr !== null) return inlinedStr
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
const inlinedBool = ctx.resolveModuleBooleanConst(value)
|
|
113
|
-
if (inlinedBool !== null) return inlinedBool
|
|
111
|
+
const inlinedConst = ctx.resolveModuleConstAsGo(value, { kind: 'go-source', bakeType: typeInfo })
|
|
112
|
+
if (inlinedConst !== null) return inlinedConst
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
const propName = ctx.extractPropNameFromInitialValue(value, preParsed)
|
package/src/conformance-pins.ts
CHANGED
|
@@ -101,4 +101,19 @@ export const conformancePins: ConformancePins = {
|
|
|
101
101
|
// resolves the primitive normally and never reaches this refusal (formerly
|
|
102
102
|
// tracked as #2771, closed) — no open issue tracks further work.
|
|
103
103
|
'namespace-import-primitive': [{ code: 'BF013', severity: 'error' }],
|
|
104
|
+
// #2893: `analyzeBakeableStaticElementLoop`'s static-array bake (Go's only
|
|
105
|
+
// path to bind a static/non-signal array as a loop source — `html/template`
|
|
106
|
+
// has no slice literal syntax) bails the WHOLE loop when the body contains
|
|
107
|
+
// a nested `loop` node (module docstring, `static-element-loop-bake.ts`).
|
|
108
|
+
// This fixture's outer static array's row has a nested `.map()` over
|
|
109
|
+
// `item.children`, so it falls through to the generic "computed loop
|
|
110
|
+
// array" BF101 refusal. Unrelated to #2798's own fix (a client-JS-only
|
|
111
|
+
// ref/reactive-binding gap) — this is a pre-existing Go-adapter SSR
|
|
112
|
+
// static-loop-baking scope boundary the new fixture happened to be the
|
|
113
|
+
// first to exercise.
|
|
114
|
+
'static-nested-loop-ref': [{
|
|
115
|
+
code: 'BF101',
|
|
116
|
+
severity: 'error',
|
|
117
|
+
issue: 'https://github.com/piconic-ai/barefootjs/issues/2893',
|
|
118
|
+
}],
|
|
104
119
|
}
|