@barefootjs/jsx 0.20.0 → 0.21.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/analyzer.d.ts.map +1 -1
- package/dist/builtin-lowering-plugins.d.ts.map +1 -1
- package/dist/date-lowering.d.ts +33 -0
- package/dist/date-lowering.d.ts.map +1 -0
- package/dist/expression-parser.d.ts +11 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +475 -286
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +14 -1
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/rich-type-evidence.d.ts +11 -0
- package/dist/rich-type-evidence.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/date-lowering.test.ts +232 -0
- package/src/__tests__/hydrate-template.test.ts +10 -5
- package/src/__tests__/rich-type-method-refusal.test.ts +21 -1
- package/src/analyzer.ts +19 -2
- package/src/builtin-lowering-plugins.ts +2 -1
- package/src/date-lowering.ts +117 -0
- package/src/expression-parser.ts +47 -0
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/emit-reactive.ts +103 -2
- package/src/ir-to-client-js/imports.ts +4 -0
- package/src/ir-to-client-js/index.ts +2 -0
- package/src/ir-to-client-js/types.ts +15 -0
- package/src/jsx-to-ir.ts +159 -3
- package/src/rich-type-evidence.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit-reactive.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/emit-reactive.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"emit-reactive.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/emit-reactive.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmBjD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAyC7G;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,MAAM,CAmBzF;AAiGD,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CA8DlF;AAED,kFAAkF;AAClF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAQrF;AAED,kGAAkG;AAClG,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAyBxF;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAqDpF;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAgClF"}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Import detection and DOM import management.
|
|
3
3
|
*/
|
|
4
4
|
import type { ComponentIR } from '../types.ts';
|
|
5
|
-
export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'reconcileElements', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'updateClientMarker', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'tAfter', 'beginTurn', 'endTurn'];
|
|
5
|
+
export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'reconcileElements', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'updateClientMarker', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'tAfter', 'beginTurn', 'endTurn', 'date'];
|
|
6
6
|
/** @deprecated Use RUNTIME_IMPORT_CANDIDATES */
|
|
7
|
-
export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "reconcileElements", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "updateClientMarker", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "tAfter", "beginTurn", "endTurn"];
|
|
7
|
+
export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "reconcileElements", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "updateClientMarker", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "tAfter", "beginTurn", "endTurn", "date"];
|
|
8
8
|
export declare const RUNTIME_MODULE = "@barefootjs/client/runtime";
|
|
9
9
|
export declare const IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
|
|
10
10
|
export declare const MODULE_CONSTANTS_PLACEHOLDER = "/* __MODULE_LEVEL_CONSTANTS__ */";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;AAItD,eAAO,MAAM,yBAAyB,YACpC,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EACpE,SAAS,EAAE,QAAQ,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,wBAAwB,EACrI,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,oBAAoB,EAC3H,cAAc,EACd,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAC/C,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EACvG,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EACxG,QAAQ,EAER,WAAW,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;AAItD,eAAO,MAAM,yBAAyB,YACpC,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EACpE,SAAS,EAAE,QAAQ,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,wBAAwB,EACrI,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,oBAAoB,EAC3H,cAAc,EACd,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAC/C,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EACvG,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EACxG,QAAQ,EAER,WAAW,EAAE,SAAS,EAItB,MAAM,CACE,CAAA;AAEV,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,mpBAA4B,CAAA;AAE9D,eAAO,MAAM,cAAc,+BAA+B,CAAA;AAE1D,eAAO,MAAM,kBAAkB,qCAAqC,CAAA;AACpE,eAAO,MAAM,4BAA4B,qCAAqC,CAAA;AAE9E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAqB3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAiB/D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmCvH"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Internal type definitions for client JS generation.
|
|
3
3
|
*/
|
|
4
4
|
import type { TemplatePrimitiveRegistry, TemplateCallAcceptor } from '../adapters/interface.ts';
|
|
5
|
-
import type { AttrMeta, IREvent, IRLoopChildComponent, SignalInfo, MemoInfo, EffectInfo, OnMountInfo, InitStatementInfo, FunctionInfo, ConstantInfo, ParamInfo, CompilerError, ImportInfo } from '../types.ts';
|
|
5
|
+
import type { AttrMeta, IREvent, IRLoopChildComponent, SignalInfo, MemoInfo, EffectInfo, OnMountInfo, InitStatementInfo, FunctionInfo, ConstantInfo, ParamInfo, CompilerError, ImportInfo, TypeInfo, TypeDefinition } from '../types.ts';
|
|
6
6
|
import type { CsrInlinabilityMap } from './csr-substitute.ts';
|
|
7
7
|
import type { SkeletonSlotPaths } from './html-template.ts';
|
|
8
8
|
export interface ClientJsContext {
|
|
@@ -48,6 +48,19 @@ export interface ClientJsContext {
|
|
|
48
48
|
propsParams: ParamInfo[];
|
|
49
49
|
propsObjectName: string | null;
|
|
50
50
|
restPropsName: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Threaded through (alongside `propsParams` above) so the reactive-effect
|
|
53
|
+
* emitter can bind its own `datePlugin` matcher (#2292) — see
|
|
54
|
+
* `emit-reactive.ts`'s `getReactiveDateLoweringMatcher`. Mirrors the
|
|
55
|
+
* `EvidenceMetadata` slice (`rich-type-evidence.ts`) the SSR adapters and
|
|
56
|
+
* `jsx-to-ir.ts`'s static-template lowering both consult; nothing else in
|
|
57
|
+
* this file reads a Date-typed prop's shape. Optional (rather than a hard
|
|
58
|
+
* requirement alongside `propsParams`) so existing hand-built
|
|
59
|
+
* `ClientJsContext` test fixtures that predate #2292 keep type-checking
|
|
60
|
+
* without every call site listing these two fields.
|
|
61
|
+
*/
|
|
62
|
+
propsType?: TypeInfo | null;
|
|
63
|
+
typeDefinitions?: TypeDefinition[];
|
|
51
64
|
interactiveElements: InteractiveElement[];
|
|
52
65
|
dynamicElements: DynamicElement[];
|
|
53
66
|
conditionalElements: ConditionalElement[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,yBAAyB,EACzB,oBAAoB,EACrB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EACV,QAAQ,EACR,OAAO,EACP,oBAAoB,EACpB,UAAU,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,UAAU,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/types.ts"],"names":[],"mappings":"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"}
|
package/dist/jsx-to-ir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,EA2BZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,eAAe,EAA0E,MAAM,uBAAuB,CAAA;AAkyBpI,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAIhE"}
|
|
@@ -39,6 +39,17 @@ export declare const HOST_RICH_TYPE_NAMES: ReadonlySet<string>;
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function baseTypeName(raw: string): string;
|
|
41
41
|
type EvidenceMetadata = Pick<IRMetadata, 'propsType' | 'propsObjectName' | 'propsParams' | 'typeDefinitions'>;
|
|
42
|
+
export declare function stripUnion(type: TypeInfo | null): TypeInfo | null;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a named type (`{ kind: 'interface', raw: 'Props' }`) that carries
|
|
45
|
+
* no inline `properties` to its declaration's field list via
|
|
46
|
+
* `metadata.typeDefinitions`. A type already carrying properties (an inline
|
|
47
|
+
* object literal type, or a type resolved from `tsTypeToTypeInfo`) is
|
|
48
|
+
* returned unchanged — this only fills in the gap left by a *named*
|
|
49
|
+
* reference, which `typeNodeToTypeInfo` intentionally resolves to
|
|
50
|
+
* `{ kind: 'interface', raw }` with no member walk of its own.
|
|
51
|
+
*/
|
|
52
|
+
export declare function derefNamedType(type: TypeInfo, meta: EvidenceMetadata): TypeInfo;
|
|
42
53
|
/**
|
|
43
54
|
* Resolve the TypeInfo of a receiver expression, using only propsType /
|
|
44
55
|
* propsParams / typeDefinitions and the caller-supplied local bindings.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-type-evidence.d.ts","sourceRoot":"","sources":["../src/rich-type-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,QAAQ,EAAE,MAAM,YAAY,CAAA;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAcnD,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,KAAK,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,iBAAiB,GAAG,aAAa,GAAG,iBAAiB,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"rich-type-evidence.d.ts","sourceRoot":"","sources":["../src/rich-type-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,QAAQ,EAAE,MAAM,YAAY,CAAA;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAcnD,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,KAAK,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,iBAAiB,GAAG,aAAa,GAAG,iBAAiB,CAAC,CAAA;AAmB7G,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,CAIjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,GAAG,QAAQ,CAO/E;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,GAC7C,QAAQ,GAAG,IAAI,CAwBjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/jsx"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.21.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Date` lowering plugin (#2274) — the first catalogued rich-type lowering
|
|
3
|
+
* built on top of the #2273 refusal seam. Covers the matcher's own
|
|
4
|
+
* recognition rules directly (mirrors `query-href-recognition.test.ts`'s
|
|
5
|
+
* `metadata()` extraction), plus the BF021-exemption integration promised by
|
|
6
|
+
* `rich-type-refusal.ts`'s module doc: a call the registry claims must not
|
|
7
|
+
* also fire BF021, while an un-catalogued Date method on the same receiver
|
|
8
|
+
* still does.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, test, expect, afterEach } from 'bun:test'
|
|
11
|
+
import { compileJSX, type ComponentIR } from '../index'
|
|
12
|
+
import { TestAdapter } from '../adapters/test-adapter'
|
|
13
|
+
import { parseExpression, type ParsedExpr } from '../expression-parser'
|
|
14
|
+
import { datePlugin, DATE_METHODS } from '../date-lowering'
|
|
15
|
+
import { registerLoweringPlugin, __resetLoweringPluginsForTest, getLoweringPlugins } from '../lowering-registry'
|
|
16
|
+
import { ErrorCodes } from '../errors'
|
|
17
|
+
|
|
18
|
+
function metadata(src: string): ComponentIR['metadata'] {
|
|
19
|
+
const result = compileJSX(src.trimStart(), 'T.tsx', { adapter: new TestAdapter(), outputIR: true })
|
|
20
|
+
const ir = JSON.parse(result.files.find((f) => f.type === 'ir')!.content) as ComponentIR
|
|
21
|
+
return ir.metadata
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse a call expression source and return its callee + args, the exact
|
|
25
|
+
* shape a `LoweringMatcher` receives. */
|
|
26
|
+
function callParts(expr: string): { callee: ParsedExpr; args: ParsedExpr[] } {
|
|
27
|
+
const parsed = parseExpression(expr)
|
|
28
|
+
if (parsed.kind !== 'call') throw new Error(`expected a call expression, got ${parsed.kind}`)
|
|
29
|
+
return { callee: parsed.callee, args: parsed.args }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('DATE_METHODS catalogue', () => {
|
|
33
|
+
test('is exactly the 8 zero-arg Date.prototype accessors the spec entry names', () => {
|
|
34
|
+
expect([...DATE_METHODS].sort()).toEqual(
|
|
35
|
+
[
|
|
36
|
+
'getUTCFullYear',
|
|
37
|
+
'getUTCMonth',
|
|
38
|
+
'getUTCDate',
|
|
39
|
+
'getUTCHours',
|
|
40
|
+
'getUTCMinutes',
|
|
41
|
+
'getUTCSeconds',
|
|
42
|
+
'getTime',
|
|
43
|
+
'toISOString',
|
|
44
|
+
].sort(),
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('datePlugin matcher recognition (#2274)', () => {
|
|
50
|
+
test('props-object member chain (props.createdAt.toISOString()) matches', () => {
|
|
51
|
+
const md = metadata(`
|
|
52
|
+
export function Foo(props: { createdAt: Date }) {
|
|
53
|
+
return <div>{props.createdAt.toISOString()}</div>
|
|
54
|
+
}
|
|
55
|
+
`)
|
|
56
|
+
const matcher = datePlugin.prepare(md)
|
|
57
|
+
expect(matcher).not.toBeNull()
|
|
58
|
+
const { callee, args } = callParts('props.createdAt.toISOString()')
|
|
59
|
+
expect(matcher!(callee, args)).toEqual({
|
|
60
|
+
kind: 'helper-call',
|
|
61
|
+
helper: 'date',
|
|
62
|
+
args: [(callee as { object: ParsedExpr }).object, { kind: 'literal', value: 'toISOString', literalType: 'string' }],
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('destructured Date prop (createdAt.getUTCFullYear()) matches', () => {
|
|
67
|
+
const md = metadata(`
|
|
68
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
69
|
+
return <div>{createdAt.getUTCFullYear()}</div>
|
|
70
|
+
}
|
|
71
|
+
`)
|
|
72
|
+
const matcher = datePlugin.prepare(md)
|
|
73
|
+
expect(matcher).not.toBeNull()
|
|
74
|
+
const { callee, args } = callParts('createdAt.getUTCFullYear()')
|
|
75
|
+
expect(matcher!(callee, args)).toEqual({
|
|
76
|
+
kind: 'helper-call',
|
|
77
|
+
helper: 'date',
|
|
78
|
+
args: [(callee as { object: ParsedExpr }).object, { kind: 'literal', value: 'getUTCFullYear', literalType: 'string' }],
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('renamed destructured Date prop ({ createdAt: c }) resolves via the source name', () => {
|
|
83
|
+
const md = metadata(`
|
|
84
|
+
export function Foo({ createdAt: c }: { createdAt: Date }) {
|
|
85
|
+
return <div>{c.getTime()}</div>
|
|
86
|
+
}
|
|
87
|
+
`)
|
|
88
|
+
const matcher = datePlugin.prepare(md)
|
|
89
|
+
expect(matcher).not.toBeNull()
|
|
90
|
+
const { callee, args } = callParts('c.getTime()')
|
|
91
|
+
expect(matcher!(callee, args)).toEqual({
|
|
92
|
+
kind: 'helper-call',
|
|
93
|
+
helper: 'date',
|
|
94
|
+
args: [(callee as { object: ParsedExpr }).object, { kind: 'literal', value: 'getTime', literalType: 'string' }],
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('#2274: destructured Date prop now carries a real propsParams TypeInfo (analyzer widening)', () => {
|
|
99
|
+
// Pre-widening this degraded to { kind: 'unknown', raw: 'unknown' }
|
|
100
|
+
// (analyzer.ts's #2150 primitives-only gate) — resolveReceiverType's
|
|
101
|
+
// destructured path reads the type from propsType, not propsParams, so
|
|
102
|
+
// the widening isn't load-bearing for the matcher above, but propsParams
|
|
103
|
+
// is itself observable IR metadata this plugin's existence is meant to
|
|
104
|
+
// unlock (see analyzer.ts's docstring on `isResolvablePrimitive`).
|
|
105
|
+
const md = metadata(`
|
|
106
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
107
|
+
return <div>{createdAt.toISOString()}</div>
|
|
108
|
+
}
|
|
109
|
+
`)
|
|
110
|
+
const param = md.propsParams.find((p) => p.name === 'createdAt')
|
|
111
|
+
expect(param?.type.kind).toBe('interface')
|
|
112
|
+
expect(param?.type.raw).toBe('Date')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('toLocaleDateString is not a catalogued method — declines (falls back to BF021)', () => {
|
|
116
|
+
const md = metadata(`
|
|
117
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
118
|
+
return <div>{createdAt.toLocaleDateString()}</div>
|
|
119
|
+
}
|
|
120
|
+
`)
|
|
121
|
+
const matcher = datePlugin.prepare(md)
|
|
122
|
+
expect(matcher).not.toBeNull() // Date IS reachable — the gate stays active …
|
|
123
|
+
const { callee, args } = callParts('createdAt.toLocaleDateString()')
|
|
124
|
+
expect(matcher!(callee, args)).toBeNull() // … but this specific method declines.
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('a catalogued method name called with an argument declines (zero-arg only)', () => {
|
|
128
|
+
const md = metadata(`
|
|
129
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
130
|
+
return <div>{createdAt.getTime()}</div>
|
|
131
|
+
}
|
|
132
|
+
`)
|
|
133
|
+
const matcher = datePlugin.prepare(md)
|
|
134
|
+
expect(matcher).not.toBeNull()
|
|
135
|
+
const { callee, args } = callParts('createdAt.getTime(1)')
|
|
136
|
+
expect(matcher!(callee, args)).toBeNull()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('a non-Date receiver never activates the plugin (prepare declines entirely)', () => {
|
|
140
|
+
const md = metadata(`
|
|
141
|
+
export function Foo({ createdAt }: { createdAt: string }) {
|
|
142
|
+
return <div>{createdAt.toUpperCase()}</div>
|
|
143
|
+
}
|
|
144
|
+
`)
|
|
145
|
+
expect(datePlugin.prepare(md)).toBeNull()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('a component with no props type at all never activates the plugin', () => {
|
|
149
|
+
const md = metadata(`
|
|
150
|
+
export function Foo() {
|
|
151
|
+
return <div>static</div>
|
|
152
|
+
}
|
|
153
|
+
`)
|
|
154
|
+
expect(datePlugin.prepare(md)).toBeNull()
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('BF021 exemption via the real datePlugin (#2274 seam)', () => {
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
__resetLoweringPluginsForTest(getLoweringPlugins().filter((p) => p.name !== 'date'))
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
function bf021Count(source: string): number {
|
|
164
|
+
registerLoweringPlugin(datePlugin)
|
|
165
|
+
const result = compileJSX(source.trimStart(), 'Test.tsx', { adapter: new TestAdapter() })
|
|
166
|
+
return result.errors.filter((e) => e.code === ErrorCodes.UNSUPPORTED_JSX_PATTERN).length
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
test('a catalogued call the plugin claims fires zero BF021', () => {
|
|
170
|
+
expect(
|
|
171
|
+
bf021Count(`
|
|
172
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
173
|
+
return <div>{createdAt.toISOString()}</div>
|
|
174
|
+
}
|
|
175
|
+
`),
|
|
176
|
+
).toBe(0)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('an un-catalogued Date method on the same prop still fires BF021', () => {
|
|
180
|
+
expect(
|
|
181
|
+
bf021Count(`
|
|
182
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
183
|
+
return <div>{createdAt.toLocaleDateString()}</div>
|
|
184
|
+
}
|
|
185
|
+
`),
|
|
186
|
+
).toBe(1)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe('client-JS lowering (#2292)', () => {
|
|
191
|
+
function clientJs(src: string): string {
|
|
192
|
+
const result = compileJSX(src.trimStart(), 'T.tsx', { adapter: new TestAdapter() })
|
|
193
|
+
return result.files.find((f) => f.type === 'clientJs')!.content
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
test('lowers a Date-typed prop accessor call to the date() runtime helper', () => {
|
|
197
|
+
const js = clientJs(`
|
|
198
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
199
|
+
return <div>{createdAt.toISOString()}</div>
|
|
200
|
+
}
|
|
201
|
+
`)
|
|
202
|
+
expect(js).toContain('date(_p.createdAt, "toISOString")')
|
|
203
|
+
// auto-imported from the runtime barrel (imports.ts RUNTIME_IMPORT_CANDIDATES)
|
|
204
|
+
expect(js).toMatch(/import\s*\{[^}]*\bdate\b[^}]*\}\s*from\s*'@barefootjs\/client\/runtime'/)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('leaves a non-Date receiver method call raw (parity: only what datePlugin claims)', () => {
|
|
208
|
+
const js = clientJs(`
|
|
209
|
+
export function Foo({ label }: { label: string }) {
|
|
210
|
+
return <div>{label.toUpperCase()}</div>
|
|
211
|
+
}
|
|
212
|
+
`)
|
|
213
|
+
expect(js).not.toContain('date(')
|
|
214
|
+
expect(js).toContain('toUpperCase()')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('protects a template-literal static segment that matches the call text (#2294)', () => {
|
|
218
|
+
// The real call is in the ${…} interpolation; an identical-looking run
|
|
219
|
+
// of text sits in the static segment. Template-aware string protection
|
|
220
|
+
// must keep the non-global .replace from rewriting the static text
|
|
221
|
+
// before the real call site (Copilot review).
|
|
222
|
+
const js = clientJs(`
|
|
223
|
+
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
224
|
+
return <div>{\`createdAt.toISOString() = \${createdAt.toISOString()}\`}</div>
|
|
225
|
+
}
|
|
226
|
+
`)
|
|
227
|
+
// the real (interpolated) call is lowered
|
|
228
|
+
expect(js).toContain('date(_p.createdAt, "toISOString")')
|
|
229
|
+
// the static segment is preserved verbatim, not rewritten to date(...)
|
|
230
|
+
expect(js).toContain('createdAt.toISOString() = ')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
@@ -201,13 +201,18 @@ describe('hydrate() template generation for signal-bearing components', () => {
|
|
|
201
201
|
// Icon is used as a child → gets CSR fallback with template
|
|
202
202
|
expect(content).toMatch(/hydrate\('Icon',.*template:/)
|
|
203
203
|
|
|
204
|
-
// String literals
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
// String literals size-4/6/8 must NOT be corrupted by the constant `size`
|
|
205
|
+
// being inlined into them. A bare `class={sizeClasses[size]}` is now lifted
|
|
206
|
+
// into the same `lookup` part the `${sizeClasses[size]}` template-literal
|
|
207
|
+
// form produces (#2300), so the record inlines as a JSON-serialized object
|
|
208
|
+
// (`{"sm": "size-4", …}[size]`) with double-quoted values — the literals
|
|
209
|
+
// stay intact, which is exactly what this guard checks.
|
|
210
|
+
expect(content).toContain('"size-4"')
|
|
211
|
+
expect(content).toContain('"size-6"')
|
|
212
|
+
expect(content).toContain('"size-8"')
|
|
209
213
|
// The word 'size' inside 'size-4' should not be replaced with the constant value
|
|
210
214
|
expect(content).not.toMatch(/'\(props\.size/)
|
|
215
|
+
expect(content).not.toMatch(/"\(props\.size/)
|
|
211
216
|
})
|
|
212
217
|
|
|
213
218
|
test('block-body memo with local decls: wraps body in IIFE instead of dropping decls', () => {
|
|
@@ -7,9 +7,20 @@
|
|
|
7
7
|
* time. `checkRichTypeMethodCalls` (rich-type-refusal.ts) is wired into
|
|
8
8
|
* `compileJSX` (not the bare analyzer/jsxToIR pipeline other BF021 tests in
|
|
9
9
|
* this directory use), so these tests go through `compileJSX` directly.
|
|
10
|
+
*
|
|
11
|
+
* This suite intentionally exercises the refusal against an EMPTY plugin
|
|
12
|
+
* registry (only the "registry-claimed call is exempt" test opts a plugin
|
|
13
|
+
* back in, and cleans up after itself) — `Date`/`Map`/… calls here must
|
|
14
|
+
* still have no catalogued lowering for the fires/silent split below to mean
|
|
15
|
+
* anything. `bun test` runs every file in one process, and a sibling file
|
|
16
|
+
* that imports the package entry (`../index`) registers the real built-ins
|
|
17
|
+
* (`queryHref`, `date`, #2274) as a global side effect — including `date`,
|
|
18
|
+
* which legitimately claims `.toISOString()`. Snapshot + clear + restore
|
|
19
|
+
* around the whole file so this suite's result never depends on which other
|
|
20
|
+
* test files happened to run first in the same process.
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
|
-
import { describe, test, expect } from 'bun:test'
|
|
23
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
|
|
13
24
|
import { compileJSX } from '../compiler'
|
|
14
25
|
import { ErrorCodes } from '../errors'
|
|
15
26
|
import { TestAdapter } from '../adapters/test-adapter'
|
|
@@ -17,6 +28,15 @@ import { registerLoweringPlugin, __resetLoweringPluginsForTest, getLoweringPlugi
|
|
|
17
28
|
|
|
18
29
|
const adapter = new TestAdapter()
|
|
19
30
|
|
|
31
|
+
let savedPlugins: readonly LoweringPlugin[]
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
savedPlugins = getLoweringPlugins()
|
|
34
|
+
__resetLoweringPluginsForTest([])
|
|
35
|
+
})
|
|
36
|
+
afterAll(() => {
|
|
37
|
+
__resetLoweringPluginsForTest(savedPlugins)
|
|
38
|
+
})
|
|
39
|
+
|
|
20
40
|
function bf021(source: string, filePath = 'Test.tsx') {
|
|
21
41
|
const result = compileJSX(source, filePath, { adapter })
|
|
22
42
|
return result.errors.filter((e) => e.code === ErrorCodes.UNSUPPORTED_JSX_PATTERN)
|
package/src/analyzer.ts
CHANGED
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
collectReactiveGetterNames,
|
|
25
25
|
} from './analyzer-context.ts'
|
|
26
26
|
import { createError, createWarning, ErrorCodes } from './errors.ts'
|
|
27
|
+
import { baseTypeName } from './rich-type-evidence.ts'
|
|
28
|
+
import { CATALOGUED_RICH_TYPE_NAMES } from './date-lowering.ts'
|
|
27
29
|
import path from 'node:path'
|
|
28
30
|
import fs from 'node:fs'
|
|
29
31
|
|
|
@@ -3174,9 +3176,24 @@ function collectMemberTypes(
|
|
|
3174
3176
|
typeNode: ts.TypeNode,
|
|
3175
3177
|
ctx: AnalyzerContext
|
|
3176
3178
|
): Map<string, { type: TypeInfo | null; optional: boolean }> | null {
|
|
3179
|
+
// #2150 originally restricted this gate to string/number/boolean only,
|
|
3180
|
+
// because a non-primitive TypeInfo here used to mean "the typed adapters
|
|
3181
|
+
// will emit an unchecked scalar assertion (`in.X.(int)`) that panics" for
|
|
3182
|
+
// a shape the template layer has no representation for. That reasoning
|
|
3183
|
+
// does NOT extend to a rich type with a CATALOGUED lowering (#2274: `Date`
|
|
3184
|
+
// → the `date` helper): its propsParams TypeInfo is consumed only as
|
|
3185
|
+
// call-site evidence for `resolveReceiverType` (rich-type-evidence.ts) —
|
|
3186
|
+
// never emitted as a concrete field type (`typeInfoToGo`'s `interface`
|
|
3187
|
+
// case falls through to `interface{}` for an unbacked host name exactly
|
|
3188
|
+
// as `unknown` already did) — so there is no assertion to panic. Gating on
|
|
3189
|
+
// `CATALOGUED_RICH_TYPE_NAMES` specifically (not `HOST_RICH_TYPE_NAMES`
|
|
3190
|
+
// wholesale) keeps an un-catalogued rich type (`Map`, `Set`, …) at
|
|
3191
|
+
// `unknown`, i.e. avoids resurrecting the #2150 mistake for a shape no
|
|
3192
|
+
// lowering plugin exists for yet.
|
|
3177
3193
|
const isResolvablePrimitive = (info: TypeInfo): boolean =>
|
|
3178
|
-
info.kind === 'primitive' &&
|
|
3179
|
-
|
|
3194
|
+
(info.kind === 'primitive' &&
|
|
3195
|
+
(info.primitive === 'string' || info.primitive === 'number' || info.primitive === 'boolean')) ||
|
|
3196
|
+
(info.kind === 'interface' && CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw)))
|
|
3180
3197
|
|
|
3181
3198
|
const fromMembers = (
|
|
3182
3199
|
members: ts.NodeArray<ts.TypeElement>
|
|
@@ -16,6 +16,7 @@ import type { LoweringPlugin } from './lowering-registry.ts'
|
|
|
16
16
|
import { registerLoweringPlugin } from './lowering-registry.ts'
|
|
17
17
|
import { queryHrefLocalNames } from './adapters/env-signal.ts'
|
|
18
18
|
import { matchQueryHrefCall } from './query-href-lowering.ts'
|
|
19
|
+
import { datePlugin } from './date-lowering.ts'
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* `queryHref(base, { … })` — the pure URL-query builder (#2042). Its runtime
|
|
@@ -41,7 +42,7 @@ export const queryHrefPlugin: LoweringPlugin = {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/** Every plugin the compiler ships and applies by default. */
|
|
44
|
-
export const BUILTIN_LOWERING_PLUGINS: readonly LoweringPlugin[] = [queryHrefPlugin]
|
|
45
|
+
export const BUILTIN_LOWERING_PLUGINS: readonly LoweringPlugin[] = [queryHrefPlugin, datePlugin]
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Register the built-in plugins into the shared registry. Called for its side
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Date` lowering plugin (#2274) — the first catalogued entry in the rich-type
|
|
3
|
+
* lowering seam #2273 left open (`rich-type-refusal.ts`'s module doc: "the
|
|
4
|
+
* seam #2274 and later plugins use to catalogue a rich-type API without
|
|
5
|
+
* touching this module"). A zero-arg call to one of `DATE_METHODS` on a
|
|
6
|
+
* receiver resolved (via `resolveReceiverType`) to a `Date`-typed prop lowers
|
|
7
|
+
* to a backend-neutral `helper-call` node on the `date` helper
|
|
8
|
+
* (`date(recv, op)`, spec/template-helpers.md); every adapter already renders
|
|
9
|
+
* `helper-call` generically (#2069), so no adapter-specific code is needed
|
|
10
|
+
* here — only the runtime `date` helper each backend ships.
|
|
11
|
+
*
|
|
12
|
+
* v1 scope is deliberately prop-rooted only: the matcher resolves receivers
|
|
13
|
+
* with `EMPTY_BINDINGS` (no loop-item / arrow-param context), mirroring
|
|
14
|
+
* `rich-type-refusal.ts`'s own top-level walk. A loop item's `.toISOString()`
|
|
15
|
+
* therefore stays BF021-refused rather than silently falling back to a
|
|
16
|
+
* generic (and wrong) lowering — widening to loop bindings is a deliberate
|
|
17
|
+
* future step, not an oversight.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { IRMetadata, TypeInfo } from './types.ts'
|
|
21
|
+
import type { ParsedExpr } from './expression-parser.ts'
|
|
22
|
+
import type { LoweringNode, LoweringPlugin } from './lowering-registry.ts'
|
|
23
|
+
import { resolveReceiverType, baseTypeName, stripUnion, derefNamedType } from './rich-type-evidence.ts'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Host rich-type names (`rich-type-evidence.ts`'s `HOST_RICH_TYPE_NAMES`)
|
|
27
|
+
* that additionally have a catalogued lowering — currently just `Date`.
|
|
28
|
+
* `analyzer.ts`'s `collectMemberTypes` widening keys off this set (not
|
|
29
|
+
* `HOST_RICH_TYPE_NAMES` wholesale) so a destructured prop only gets a real
|
|
30
|
+
* TypeInfo when a plugin actually exists to consume it; adding a future
|
|
31
|
+
* lowering (`Map`, …) is a one-line addition here, not a second gate to keep
|
|
32
|
+
* in sync.
|
|
33
|
+
*/
|
|
34
|
+
export const CATALOGUED_RICH_TYPE_NAMES: ReadonlySet<string> = new Set(['Date'])
|
|
35
|
+
|
|
36
|
+
/** Zero-arg `Date.prototype` methods the `date` helper catalogues (spec/template-helpers.md). */
|
|
37
|
+
export const DATE_METHODS: ReadonlySet<string> = new Set([
|
|
38
|
+
'getUTCFullYear',
|
|
39
|
+
'getUTCMonth',
|
|
40
|
+
'getUTCDate',
|
|
41
|
+
'getUTCHours',
|
|
42
|
+
'getUTCMinutes',
|
|
43
|
+
'getUTCSeconds',
|
|
44
|
+
'getTime',
|
|
45
|
+
'toISOString',
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
type Bindings = ReadonlyMap<string, TypeInfo | null>
|
|
49
|
+
const EMPTY_BINDINGS: Bindings = new Map()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether `type` (or a property reachable from it through non-array,
|
|
53
|
+
* non-computed member chains) can ever resolve to a `Date` — the same
|
|
54
|
+
* receiver shapes `resolveReceiverType` walks for an actual call site. Used
|
|
55
|
+
* only as `prepare`'s cheap activation gate: a `false` here must never miss a
|
|
56
|
+
* receiver the matcher could later prove Date-typed (that would silently drop
|
|
57
|
+
* the lowering and mis-flag BF021), so this over-approximates by recursing
|
|
58
|
+
* into every nested object-shaped property, not just direct ones.
|
|
59
|
+
*
|
|
60
|
+
* `seen` guards a self-referential named type (`interface Tree { self: Tree
|
|
61
|
+
* }`) from an infinite walk; keyed by the type's own name so two distinct
|
|
62
|
+
* properties of the SAME named type aren't short-circuited against each
|
|
63
|
+
* other.
|
|
64
|
+
*/
|
|
65
|
+
function typeReachesDate(type: TypeInfo | null, meta: IRMetadata, seen: Set<string>): boolean {
|
|
66
|
+
const stripped = stripUnion(type)
|
|
67
|
+
if (!stripped) return false
|
|
68
|
+
// `kind: 'object'` is an INLINE type literal (`{ createdAt: Date }` — the
|
|
69
|
+
// shape `propsType` itself takes for the common case) and already carries
|
|
70
|
+
// `properties` directly; `kind: 'interface'` is a NAMED reference (`Date`
|
|
71
|
+
// itself, or a local `interface Props { … }`) that needs the Date-name
|
|
72
|
+
// check plus `derefNamedType` to reach its member list. Anything else
|
|
73
|
+
// (primitive/array/union/…) is a dead end.
|
|
74
|
+
if (stripped.kind === 'interface') {
|
|
75
|
+
const name = baseTypeName(stripped.raw)
|
|
76
|
+
if (name === 'Date') return true
|
|
77
|
+
if (seen.has(name)) return false
|
|
78
|
+
seen.add(name)
|
|
79
|
+
} else if (stripped.kind !== 'object') {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
const deref = derefNamedType(stripped, meta)
|
|
83
|
+
if (!deref.properties) return false
|
|
84
|
+
return deref.properties.some((p) => typeReachesDate(p.type, meta, seen))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* `datePlugin`'s matcher: recognises `<Date-typed receiver>.<method>()` per
|
|
89
|
+
* the module doc, or declines (null) for anything else — a non-member
|
|
90
|
+
* callee, a non-catalogued method name, a call with arguments (every
|
|
91
|
+
* catalogued method is zero-arg on `Date.prototype`), or a receiver that
|
|
92
|
+
* doesn't resolve to `Date` (unknown, a different host rich type, or a
|
|
93
|
+
* same-named local `typeDefinitions` entry shadowing the built-in — mirrors
|
|
94
|
+
* `rich-type-refusal.ts`'s `inFileShadow` check).
|
|
95
|
+
*/
|
|
96
|
+
function matchDateCall(callee: ParsedExpr, args: readonly ParsedExpr[], metadata: IRMetadata): LoweringNode | null {
|
|
97
|
+
if (callee.kind !== 'member' || callee.computed) return null
|
|
98
|
+
if (args.length !== 0 || !DATE_METHODS.has(callee.property)) return null
|
|
99
|
+
const receiverType = resolveReceiverType(callee.object, metadata, EMPTY_BINDINGS)
|
|
100
|
+
if (!receiverType || receiverType.kind !== 'interface') return null
|
|
101
|
+
const typeName = baseTypeName(receiverType.raw)
|
|
102
|
+
if (typeName !== 'Date') return null
|
|
103
|
+
if (metadata.typeDefinitions.some((d) => d.name === typeName)) return null
|
|
104
|
+
return {
|
|
105
|
+
kind: 'helper-call',
|
|
106
|
+
helper: 'date',
|
|
107
|
+
args: [callee.object, { kind: 'literal', value: callee.property, literalType: 'string' }],
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const datePlugin: LoweringPlugin = {
|
|
112
|
+
name: 'date',
|
|
113
|
+
prepare(metadata) {
|
|
114
|
+
if (!metadata.propsType || !typeReachesDate(metadata.propsType, metadata, new Set())) return null
|
|
115
|
+
return (callee, args) => matchDateCall(callee, args, metadata)
|
|
116
|
+
},
|
|
117
|
+
}
|