@barefootjs/jsx 0.32.0 → 0.33.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.
@@ -36,7 +36,7 @@ import {
36
36
  extractArrowBodyExpression,
37
37
  freeIdentifiers,
38
38
  inlineBinding,
39
- isSupported,
39
+ isSupportedValue,
40
40
  parseExpression,
41
41
  type ParsedExpr,
42
42
  } from './expression-parser.ts'
@@ -81,6 +81,11 @@ export interface SsrSeedPlan {
81
81
  * opaque). The scope check runs over the parsed SOURCE tree, so a shadowed
82
82
  * name (`items.filter((p) => p.ok) && p`, where the trailing `p` is a
83
83
  * different, unbound reference from the callback's own param) is rejected.
84
+ *
85
+ * Uses `isSupportedValue`, not `isSupported`: a signal/memo initializer is an
86
+ * ASSIGNMENT, never a render, so it's checked at VALUE position — this is
87
+ * what admits e.g. `createSignal([{ a: 'p' }].map(t => t.a).join(','))`
88
+ * (an object-literal array-method receiver) as `derived` instead of opaque.
84
89
  */
85
90
  function classify(
86
91
  name: string,
@@ -89,7 +94,7 @@ function classify(
89
94
  parsed: ParsedExpr,
90
95
  available: ReadonlySet<string>,
91
96
  ): SsrSeedStep {
92
- if (!isSupported(parsed).supported) return { kind: 'opaque', name, origin }
97
+ if (!isSupportedValue(parsed).supported) return { kind: 'opaque', name, origin }
93
98
  const frees = freeIdentifiers(parsed)
94
99
  if (frees === null) return { kind: 'opaque', name, origin }
95
100
  for (const free of frees) {
@@ -198,7 +203,19 @@ export function computeSsrSeedPlan(metadata: IRMetadata): SsrSeedPlan {
198
203
  signal.getter,
199
204
  'signal',
200
205
  expr,
201
- resolveThroughLocalConsts(parseExpression(expr), localConsts),
206
+ // Prefer the analyzer's already-parenthesised parse (`analyzer.ts`'s
207
+ // `parseExpression(\`(${signal.initialValue})\`)`, Roadmap A) over
208
+ // re-parsing the bare `expr` string here — re-parsing WITHOUT the
209
+ // wrap misreads a bare object-literal initializer
210
+ // (`createSignal({ ...base, done: true })`) as a block statement
211
+ // (`parseExpression`'s documented block-vs-expression-statement
212
+ // rule), silently opaquing an otherwise-derivable signal (#2696
213
+ // Step 2 follow-up: this only became observable once `object-
214
+ // literal` could classify `derived` at all). Falling back to a
215
+ // parenthesised re-parse (not the bare string) keeps a signal
216
+ // whose shape the analyzer's pass left unparsed (`signal.parsed`
217
+ // undefined) equally safe.
218
+ resolveThroughLocalConsts(signal.parsed ?? parseExpression(`(${expr})`), localConsts),
202
219
  available,
203
220
  ),
204
221
  )
@@ -60,9 +60,23 @@ export function evaluateStaticLiteral(
60
60
  // Shorthand (`{ a }`) and explicit (`{ a: value }`) properties both
61
61
  // carry their resolved tree in `value` (shorthand's is an
62
62
  // `identifier`) — recursing here handles both uniformly: a shorthand
63
- // property only resolves when `bindings` supplies it.
63
+ // property only resolves when `bindings` supplies it. A spread
64
+ // (`{ ...base, a: 1 }`, #2696 Step 2) resolves its source and merges
65
+ // in source order — the same shallow-merge, later-wins, null/undefined-
66
+ // is-a-no-op semantics as the runtime evaluator (`toEvalNode`'s
67
+ // `object-literal` case) and JS itself; a spread source that resolves
68
+ // to anything other than a plain object/null/undefined isn't
69
+ // statically mergeable here, so the whole literal declines.
64
70
  const out: Record<string, unknown> = {}
65
71
  for (const prop of expr.properties) {
72
+ if (prop.kind === 'spread') {
73
+ const resolved = evaluateStaticLiteral(prop.expr, bindings)
74
+ if (!resolved) return null
75
+ if (resolved.value === null || resolved.value === undefined) continue
76
+ if (typeof resolved.value !== 'object' || Array.isArray(resolved.value)) return null
77
+ Object.assign(out, resolved.value)
78
+ continue
79
+ }
66
80
  const resolved = evaluateStaticLiteral(prop.value, bindings)
67
81
  if (!resolved) return null
68
82
  out[prop.key] = resolved.value
@@ -504,6 +504,10 @@ export function matchToLocaleDateStringCall(
504
504
  let tz: string | null = null
505
505
  const probeOptions: Record<string, string> = {}
506
506
  for (const prop of options.properties) {
507
+ // A spread (`{ ...opts, timeZone: 'UTC' }`, #2696 Step 2) isn't a
508
+ // literal-keyed entry this probe can read without evaluating the spread
509
+ // source — decline rather than silently skipping its keys.
510
+ if (prop.kind === 'spread') return null
507
511
  if (prop.value.kind !== 'literal' || prop.value.literalType !== 'string') return null
508
512
  const value = String(prop.value.value)
509
513
  if (prop.key === 'timeZone') {