@barefootjs/go-template 0.35.0 → 0.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/emit-context.d.ts +16 -11
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/expr/url-builder.d.ts +37 -1
- package/dist/adapter/expr/url-builder.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +73 -23
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +87 -67
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +90 -70
- package/dist/vite.js +115 -95
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +169 -9
- package/src/adapter/emit-context.ts +14 -12
- package/src/adapter/expr/url-builder.ts +63 -28
- package/src/adapter/go-template-adapter.ts +218 -107
- package/src/adapter/value/value-lowering.ts +6 -7
- package/src/conformance-pins.ts +13 -7
|
@@ -130,7 +130,7 @@ import { analyzeBakeableStaticChildLoop, scalarToGoLiteral, type BakedStaticChil
|
|
|
130
130
|
import { analyzeBakeableStaticElementLoop } from "./analysis/static-element-loop-bake.ts"
|
|
131
131
|
import type { GoEmitContext } from "./emit-context.ts"
|
|
132
132
|
import { inlineLocalHelperCall } from "./expr/helper-inline.ts"
|
|
133
|
-
import { lowerRegisteredAttrCall, lowerRegisteredCall, lowerRegisteredCallNode, lowerTernary } from "./expr/url-builder.ts"
|
|
133
|
+
import { lowerRegisteredAttrCall, lowerRegisteredCall, lowerRegisteredCallNode, lowerTemplateLiteralValue, lowerTernary, lowerValueOperand } from "./expr/url-builder.ts"
|
|
134
134
|
import {
|
|
135
135
|
convertInitialValue,
|
|
136
136
|
jsLiteralToGo,
|
|
@@ -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 {
|
|
@@ -5180,12 +5209,35 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5180
5209
|
// arithmetic index lowers correctly. A multi-token operand
|
|
5181
5210
|
// (`bf_add $i 1`) must be parenthesised or Go parses it as extra
|
|
5182
5211
|
// `bf_get` arguments.
|
|
5183
|
-
return `bf_get ${wrapIfMultiToken(
|
|
5212
|
+
return `bf_get ${wrapIfMultiToken(this.emitOperand(object, emit))} ${wrapIfMultiToken(this.emitOperand(index, emit))}`
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
/**
|
|
5216
|
+
* A `binary`/`logical`/`unary` operand that feeds a prefix-call Go form
|
|
5217
|
+
* (`bf_mul a b`, `and a b`, `not a`, …), which cannot host a raw `{{…}}`
|
|
5218
|
+
* action the way the generic `emit` dispatcher's `templateLiteral()` would
|
|
5219
|
+
* produce for a template-literal operand (#2863). ONLY a template literal
|
|
5220
|
+
* is special-cased here, folded through `bf_concat_str` (the same door
|
|
5221
|
+
* `lowerValueOperand` uses for a ternary branch / helper-call arg /
|
|
5222
|
+
* query-guard value); every other kind still goes through the caller's own
|
|
5223
|
+
* `emit` closure unchanged. This matters: routing every operand through
|
|
5224
|
+
* `lowerValueOperand`'s `convertExpressionToGo` fallback (rather than only
|
|
5225
|
+
* the one broken kind) would ALSO pick up `convertExpressionToGo`'s own
|
|
5226
|
+
* string-keyed fast paths — e.g. inlining a bare identifier bound to a
|
|
5227
|
+
* local literal const — which `emit`'s AST-walk `identifier()` dispatch
|
|
5228
|
+
* does not do, a real behavioral divergence for that shape (caught by the
|
|
5229
|
+
* #2236 loop-param-shadowing regression pins).
|
|
5230
|
+
*/
|
|
5231
|
+
private emitOperand(n: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5232
|
+
if (n.kind === 'template-literal') {
|
|
5233
|
+
return wrapIfMultiToken(lowerTemplateLiteralValue(this.emitCtx, n.parts))
|
|
5234
|
+
}
|
|
5235
|
+
return emit(n)
|
|
5184
5236
|
}
|
|
5185
5237
|
|
|
5186
5238
|
binary(op: string, left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5187
|
-
const l =
|
|
5188
|
-
const r =
|
|
5239
|
+
const l = this.emitOperand(left, emit)
|
|
5240
|
+
const r = this.emitOperand(right, emit)
|
|
5189
5241
|
// Every Go form below is a prefix function call (`bf_mul a b`, `gt a b`,
|
|
5190
5242
|
// `eq a b`), so a COMPOUND operand must be parenthesised or the template
|
|
5191
5243
|
// parser folds its tokens into the call's argument list — e.g.
|
|
@@ -5268,7 +5320,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5268
5320
|
}
|
|
5269
5321
|
|
|
5270
5322
|
unary(op: string, argument: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5271
|
-
const arg =
|
|
5323
|
+
const arg = this.emitOperand(argument, emit)
|
|
5272
5324
|
// `not` is a Go template prefix builtin like `and`/`or` (see `logical()`
|
|
5273
5325
|
// below) — a multi-token argument (e.g. `or a b`) must be parenthesised
|
|
5274
5326
|
// or it degrades into extra sibling args of `not` itself (#2758: `not
|
|
@@ -5291,8 +5343,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5291
5343
|
// `and`/`or`. This makes `searchParams().get(k) ?? d` lower to
|
|
5292
5344
|
// `or (.SearchParams.Get "sort") "none"` instead of the broken
|
|
5293
5345
|
// `or .SearchParams.Get "sort" "none"`.
|
|
5294
|
-
const wrapLeft = wrapIfMultiToken(
|
|
5295
|
-
const wrapRight = wrapIfMultiToken(
|
|
5346
|
+
const wrapLeft = wrapIfMultiToken(this.emitOperand(left, emit))
|
|
5347
|
+
const wrapRight = wrapIfMultiToken(this.emitOperand(right, emit))
|
|
5296
5348
|
if (op === '&&') return `and ${wrapLeft} ${wrapRight}`
|
|
5297
5349
|
// `??` on a nillable prop needs true JS nullish semantics (#2248): Go's
|
|
5298
5350
|
// `or` is truthiness-based, so `{{or .Label "Default"}}` falls back on a
|
|
@@ -5600,8 +5652,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5600
5652
|
method: ArrayMethod,
|
|
5601
5653
|
object: ParsedExpr,
|
|
5602
5654
|
args: ParsedExpr[],
|
|
5603
|
-
|
|
5655
|
+
rawEmit: (e: ParsedExpr) => string,
|
|
5604
5656
|
): string {
|
|
5657
|
+
// Every `emit(...)` call below lowers an ARGUMENT position (the
|
|
5658
|
+
// receiver or a `.method(...)` arg) that feeds a prefix-call Go form
|
|
5659
|
+
// (`bf_join (...) ...`, `bf_replace ... ... ...`, …) — shadow the
|
|
5660
|
+
// dispatcher's own `emit` with `emitOperand` (#2863) so a
|
|
5661
|
+
// template-literal receiver/argument (e.g. `items.join(\`${a}-${b}\`)`)
|
|
5662
|
+
// folds through `bf_concat_str` instead of leaking the TEXT-position
|
|
5663
|
+
// `templateLiteral()` form into another action's argument list.
|
|
5664
|
+
const emit = (e: ParsedExpr): string => this.emitOperand(e, rawEmit)
|
|
5605
5665
|
// `bf_join` etc. are registered in the runtime FuncMap. The exhaustive
|
|
5606
5666
|
// switch on `method` mirrors the IR-level discriminator — adding a new
|
|
5607
5667
|
// `ArrayMethod` variant becomes a TS compile error until every adapter
|
|
@@ -6181,12 +6241,28 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6181
6241
|
// prefix because a filter predicate must always escape back to
|
|
6182
6242
|
// root regardless of loop nesting; call it only for its
|
|
6183
6243
|
// registration side effect and keep the prefix here (pullfrog
|
|
6184
|
-
// review, PR #2818).
|
|
6185
|
-
|
|
6186
|
-
|
|
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)
|
|
6187
6249
|
}
|
|
6188
|
-
|
|
6189
|
-
|
|
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)
|
|
6190
6266
|
}
|
|
6191
6267
|
|
|
6192
6268
|
case 'literal':
|
|
@@ -6203,6 +6279,18 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6203
6279
|
if (expr.object.kind === 'identifier' && expr.object.name === param) {
|
|
6204
6280
|
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`
|
|
6205
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
|
+
}
|
|
6206
6294
|
// `.length` on a higher-order filter result (e.g.
|
|
6207
6295
|
// `x.tags.filter(t => t.active).length > 0`). Reuse
|
|
6208
6296
|
// `renderFilterLengthExpr` so the inner filter lowers to
|
|
@@ -6231,11 +6319,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6231
6319
|
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`
|
|
6232
6320
|
}
|
|
6233
6321
|
// Signal calls: `filter()` -> `$.Filter`. Same registration-only
|
|
6234
|
-
// `rootFieldRef` call
|
|
6322
|
+
// `rootFieldRef` call — and the same `resolveRootFieldAlias` field
|
|
6323
|
+
// resolution (#2857) — as the `identifier` case above, for the same
|
|
6235
6324
|
// reason (#2700's BF101 gate; pullfrog review, PR #2818).
|
|
6236
6325
|
if (expr.callee.kind === 'identifier' && expr.args.length === 0) {
|
|
6237
|
-
this.
|
|
6238
|
-
return `$.${capitalizeFieldName(expr.callee.name)}`
|
|
6326
|
+
return this.filterRootFieldRef(expr.callee.name)
|
|
6239
6327
|
}
|
|
6240
6328
|
// A nested callback method call (`other.some(r => …)`) reaching this
|
|
6241
6329
|
// arm has no Go template form in filter context — the fallthrough
|
|
@@ -6271,30 +6359,50 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6271
6359
|
if (this.filterExprUnsupported) return 'false'
|
|
6272
6360
|
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField)
|
|
6273
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)
|
|
6274
6375
|
|
|
6275
6376
|
switch (expr.op) {
|
|
6276
6377
|
case '===':
|
|
6277
6378
|
case '==':
|
|
6278
|
-
return `eq ${
|
|
6379
|
+
return `eq ${wl} ${wr}`
|
|
6279
6380
|
case '!==':
|
|
6280
6381
|
case '!=':
|
|
6281
|
-
return `ne ${
|
|
6382
|
+
return `ne ${wl} ${wr}`
|
|
6282
6383
|
case '>':
|
|
6283
|
-
return `gt ${
|
|
6384
|
+
return `gt ${wl} ${wr}`
|
|
6284
6385
|
case '<':
|
|
6285
|
-
return `lt ${
|
|
6386
|
+
return `lt ${wl} ${wr}`
|
|
6286
6387
|
case '>=':
|
|
6287
|
-
return `ge ${
|
|
6388
|
+
return `ge ${wl} ${wr}`
|
|
6288
6389
|
case '<=':
|
|
6289
|
-
return `le ${
|
|
6390
|
+
return `le ${wl} ${wr}`
|
|
6290
6391
|
case '+':
|
|
6291
|
-
return this._emitPlus(expr.left, expr.right,
|
|
6392
|
+
return this._emitPlus(expr.left, expr.right, wl, wr)
|
|
6292
6393
|
case '-':
|
|
6293
|
-
return `bf_sub ${
|
|
6394
|
+
return `bf_sub ${wl} ${wr}`
|
|
6294
6395
|
case '*':
|
|
6295
|
-
return `bf_mul ${
|
|
6396
|
+
return `bf_mul ${wl} ${wr}`
|
|
6296
6397
|
case '/':
|
|
6297
|
-
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}`
|
|
6298
6406
|
default:
|
|
6299
6407
|
return `${left} ${expr.op} ${right}`
|
|
6300
6408
|
}
|
|
@@ -6540,7 +6648,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6540
6648
|
* `loopVarRefCount` — so both are still consulted. Shared by the
|
|
6541
6649
|
* string-keyed fast paths (#2236, #2242 Copilot review) that resolve raw
|
|
6542
6650
|
* `jsExpr` text before `identifier()`'s own guards can see it. Mirrors the
|
|
6543
|
-
* checks in `resolveModuleStringConst` / `
|
|
6651
|
+
* checks in `resolveModuleStringConst` / `resolveModuleConstAsGo`.
|
|
6544
6652
|
*/
|
|
6545
6653
|
private isLoopShadowedName(name: string): boolean {
|
|
6546
6654
|
return this.scope.isBound(name) || this.loopVarRefCount.has(name)
|
|
@@ -6682,34 +6790,23 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6682
6790
|
}
|
|
6683
6791
|
continue
|
|
6684
6792
|
}
|
|
6685
|
-
// A ternary
|
|
6686
|
-
//
|
|
6687
|
-
//
|
|
6688
|
-
//
|
|
6689
|
-
//
|
|
6690
|
-
//
|
|
6691
|
-
//
|
|
6692
|
-
//
|
|
6693
|
-
//
|
|
6694
|
-
//
|
|
6695
|
-
|
|
6696
|
-
exprOut.parsed
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
go = go.slice(2, -2)
|
|
6703
|
-
}
|
|
6704
|
-
// Skip the fragment re-check for the just-unwrapped single-part case —
|
|
6705
|
-
// `kind` is still (accurately) `template-literal`, which would
|
|
6706
|
-
// otherwise re-trip `isTemplateFragment`'s `kind === 'template-literal'`
|
|
6707
|
-
// branch on the ALREADY-unwrapped bare value.
|
|
6708
|
-
if (!singlePartTemplateLiteral && this.isTemplateFragment(go, exprOut.parsed?.kind)) {
|
|
6709
|
-
// A genuine multi-part `{{if}}...{{end}}`-shaped fragment can't be a
|
|
6710
|
-
// bare pipeline argument — refuse loudly rather than silently
|
|
6711
|
-
// dropping the prop (the #2445 bug was exactly a silent drop one
|
|
6712
|
-
// level up).
|
|
6793
|
+
// A ternary or template literal (`row.on ? "yes" : "no"`, `` `#${row.id}
|
|
6794
|
+
// ${row.label}` ``) in this ARGUMENT position (a `bf_with_props`/
|
|
6795
|
+
// `bf_reprops` value, not a text position) must lower through the
|
|
6796
|
+
// shared value-operand door (#2863/#2335) — `convertExpressionToGo`'s
|
|
6797
|
+
// generic path returns `templateLiteral()`'s TEXT-position mixed
|
|
6798
|
+
// literal-text-plus-`{{…}}`-actions form for a template literal, which
|
|
6799
|
+
// is not a legal bare pipeline argument (multi-part) or needed an
|
|
6800
|
+
// ad-hoc single-part unwrap here (this replaces both). Recompute from
|
|
6801
|
+
// the already-validated `exprOut.parsed` tree rather than trying to
|
|
6802
|
+
// unwrap or refuse the text-position string after the fact.
|
|
6803
|
+
if (exprOut.parsed?.kind === 'template-literal' || exprOut.parsed?.kind === 'conditional') {
|
|
6804
|
+
go = lowerValueOperand(this.emitCtx, exprOut.parsed)
|
|
6805
|
+
} else if (this.isTemplateFragment(go, exprOut.parsed?.kind)) {
|
|
6806
|
+
// A genuine `{{if}}...{{end}}`-shaped (or other `{{`-leading action)
|
|
6807
|
+
// fragment can't be a bare pipeline argument — refuse loudly rather
|
|
6808
|
+
// than silently dropping the prop (the #2445 bug was exactly a
|
|
6809
|
+
// silent drop one level up).
|
|
6713
6810
|
this.state.errors.push({
|
|
6714
6811
|
code: 'BF101',
|
|
6715
6812
|
severity: 'error',
|
|
@@ -7107,6 +7204,15 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7107
7204
|
result = `bf_mul ${left} ${right}`; break
|
|
7108
7205
|
case '/':
|
|
7109
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
|
|
7110
7216
|
default:
|
|
7111
7217
|
result = `${left} ${expr.op} ${right}`
|
|
7112
7218
|
}
|
|
@@ -7164,7 +7270,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7164
7270
|
}
|
|
7165
7271
|
|
|
7166
7272
|
case 'template-literal':
|
|
7167
|
-
|
|
7273
|
+
// #2863 follow-up: fold to one Go value (`bf_concat_str` chain) via
|
|
7274
|
+
// the same door as the `conditional` case just above, instead of
|
|
7275
|
+
// `renderParsedExpr`'s TEXT-position `templateLiteral()` form —
|
|
7276
|
+
// which would leak raw `{{…}}` into THIS condition-expression's own
|
|
7277
|
+
// pipeline position (e.g. `(a === \`${x}-${y}\`) ? … : …`).
|
|
7278
|
+
return plain(lowerTemplateLiteralValue(this.emitCtx, expr.parts))
|
|
7168
7279
|
|
|
7169
7280
|
case 'arrow':
|
|
7170
7281
|
// A standalone arrow has no Go condition form (callbacks reach the
|
|
@@ -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
|
@@ -52,13 +52,16 @@ export const conformancePins: ConformancePins = {
|
|
|
52
52
|
'filter-nested-find-predicate': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' }],
|
|
53
53
|
'date-method-uncatalogued': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2356' }],
|
|
54
54
|
'rich-prop-client-read': [{ code: 'BF049', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2648' }],
|
|
55
|
-
//
|
|
55
|
+
// A ternary or array literal LITERALLY WRAPPING JSX at a non-children prop
|
|
56
56
|
// position (e.g. `header={cond ? <a/> : <b/>}`) is refused ahead of
|
|
57
57
|
// `adapter.generate()` in the shared jsx-to-ir.ts phase, so it is pinned
|
|
58
58
|
// identically on every adapter (including Hono) — same reasoning as
|
|
59
|
-
// `rich-prop-client-read` above.
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// `rich-prop-client-read` above. This is the permanent, intended behavior
|
|
60
|
+
// (formerly tracked as #2667, closed): the issue's own acceptance criteria
|
|
61
|
+
// treated a loud refusal as fully resolving the silent-divergence bug, so
|
|
62
|
+
// no open issue tracks further work here.
|
|
63
|
+
'jsx-element-prop-ternary': [{ code: 'BF021', severity: 'error' }],
|
|
64
|
+
'jsx-element-prop-array': [{ code: 'BF021', severity: 'error' }],
|
|
62
65
|
// `jsx-element-prop-fragment-conditional` (#2703) is NOT pinned here — it
|
|
63
66
|
// renders correctly since `queueDynamicPropDefine` (go-template-adapter.ts)
|
|
64
67
|
// extended the reserved `children` slot's `bf_with_children`/`bf_tmpl`
|
|
@@ -88,11 +91,14 @@ export const conformancePins: ConformancePins = {
|
|
|
88
91
|
severity: 'error',
|
|
89
92
|
issue: 'https://github.com/piconic-ai/barefootjs/issues/2700',
|
|
90
93
|
}],
|
|
91
|
-
//
|
|
94
|
+
// A reactive primitive invoked through a namespace import
|
|
92
95
|
// (`import * as bf from '@barefootjs/client'`, `bf.createSignal(...)`)
|
|
93
96
|
// that the analyzer's checker-less fast path cannot recognize refuses
|
|
94
97
|
// loudly (BF013) instead of silently dropping the declaration — fired
|
|
95
98
|
// in the shared analyzer pass ahead of any adapter's `adapter.generate()`,
|
|
96
|
-
// so all nine adapters (including Hono) pin this identically.
|
|
97
|
-
|
|
99
|
+
// so all nine adapters (including Hono) pin this identically. A compile
|
|
100
|
+
// that supplies a shared `ts.Program` (e.g. via `@barefootjs/vite`)
|
|
101
|
+
// resolves the primitive normally and never reaches this refusal (formerly
|
|
102
|
+
// tracked as #2771, closed) — no open issue tracks further work.
|
|
103
|
+
'namespace-import-primitive': [{ code: 'BF013', severity: 'error' }],
|
|
98
104
|
}
|