From 22a9a0dab3767aaf6959d119fd29582d4251ab06 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Thu, 9 Oct 2025 11:39:43 -0700 Subject: [PATCH] chore: Introduce CTHelpers in ts-transformers serving imported symbols. As a part of the "pretransform" step, we replace the CTS enable directive with a wildcard import for "commontools" module that is used when injecting new functionality via transformations. Remove unused imports from various patterns that are now injected by the transformers. --- .../array-in-cell-ast-nocomponents.tsx | 12 +- ...y-in-cell-with-remove-ast-nocomponents.tsx | 1 - .../array-in-cell-with-remove-editable.tsx | 12 +- packages/patterns/charm-ref-in-cell.tsx | 1 - packages/patterns/chatbot-list-view.tsx | 1 - packages/patterns/chatbot-note-composed.tsx | 15 +- packages/patterns/chatbot-outliner.tsx | 10 - packages/patterns/chatbot.tsx | 5 - packages/patterns/cheeseboard.tsx | 15 +- packages/patterns/common-tools.tsx | 7 - packages/patterns/counter-handlers.ts | 2 +- packages/patterns/counter.tsx | 10 +- packages/patterns/ct-list.tsx | 11 +- packages/patterns/ct-render.tsx | 14 +- packages/patterns/ct-select.tsx | 20 +- packages/patterns/ct-tags.tsx | 20 +- packages/patterns/default-app.tsx | 1 - packages/patterns/dice.tsx | 11 +- packages/patterns/fetch-data.tsx | 5 - packages/patterns/instantiate-recipe.tsx | 1 - packages/patterns/list-operations.tsx | 3 - packages/patterns/nested-counter.tsx | 2 +- packages/patterns/note.tsx | 1 - packages/patterns/output_schema.tsx | 4 - packages/runner/src/harness/engine.ts | 32 +-- packages/runner/src/harness/pretransform.ts | 60 +++++ packages/ts-transformers/src/core/context.ts | 10 +- .../ts-transformers/src/core/ct-helpers.ts | 120 +++++++++ .../ts-transformers/src/core/cts-directive.ts | 17 -- packages/ts-transformers/src/core/imports.ts | 254 ------------------ packages/ts-transformers/src/core/mod.ts | 7 +- packages/ts-transformers/src/ct-pipeline.ts | 2 +- packages/ts-transformers/src/mod.ts | 5 +- .../src/transformers/builtins/derive.ts | 39 +-- .../src/transformers/builtins/ifelse.ts | 13 +- .../src/transformers/opaque-ref-jsx.ts | 18 +- .../emitters/conditional-expression.ts | 4 +- .../src/transformers/opaque-ref/helpers.ts | 2 +- .../src/transformers/schema-generator.ts | 69 ++--- .../src/transformers/schema-injection.ts | 19 +- .../builder-conditional.expected.tsx | 9 +- .../ast-transform/counter-recipe.expected.tsx | 17 +- .../derive-object-literal-input.expected.tsx | 25 +- .../event-handler-no-derive.expected.tsx | 13 +- .../handler-object-literal.expected.tsx | 9 +- .../recipe-array-map.expected.tsx | 13 +- .../schema-generation-builders.expected.tsx | 11 +- ...chema-generation-derive-alias.expected.tsx | 9 +- ...-generation-derive-inside-jsx.expected.tsx | 11 +- ...ation-derive-multiple-returns.expected.tsx | 11 +- ...ema-generation-derive-untyped.expected.tsx | 9 +- .../schema-generation-derive.expected.tsx | 9 +- ...eneration-handler-both-inline.expected.tsx | 11 +- ...generation-handler-event-only.expected.tsx | 9 +- ...generation-handler-inside-jsx.expected.tsx | 9 +- ...ration-handler-no-annotations.expected.tsx | 9 +- ...ma-generation-lift-cell-array.expected.tsx | 11 +- ...ma-generation-lift-inside-jsx.expected.tsx | 9 +- ...a-generation-lift-no-generics.expected.tsx | 9 +- ...chema-generation-lift-untyped.expected.tsx | 9 +- .../schema-generation-lift.expected.tsx | 9 +- .../ast-transform/ternary_derive.expected.tsx | 9 +- ...array-cell-remove-intersection.expected.ts | 13 +- .../complex-nested-types.expected.ts | 13 +- .../date-and-map-types.expected.ts | 9 +- .../preserve-explicit-schemas.expected.ts | 3 +- .../handler-schema/simple-handler.expected.ts | 9 +- ...unsupported-intersection-index.expected.ts | 9 +- .../complex-expressions.expected.tsx | 11 +- .../complex-expressions.input.tsx | 2 +- .../element-access-complex.expected.tsx | 42 +-- .../element-access-complex.input.tsx | 2 +- .../element-access-simple.expected.tsx | 13 +- .../element-access-simple.input.tsx | 2 +- .../jsx-arithmetic-operations.expected.tsx | 29 +- .../jsx-complex-mixed.expected.tsx | 27 +- .../jsx-conditional-rendering.expected.tsx | 29 +- .../jsx-function-calls.expected.tsx | 55 ++-- .../jsx-property-access.expected.tsx | 33 +-- .../jsx-string-operations.expected.tsx | 31 +-- .../method-chains.expected.tsx | 51 ++-- .../jsx-expressions/method-chains.input.tsx | 2 +- .../no-double-derive.expected.tsx | 5 +- .../no-transform-simple-ref.expected.tsx | 3 +- .../opaque-ref-cell-map.expected.tsx | 18 +- .../opaque-ref-cell-map.input.tsx | 1 - .../opaque-ref-operations.expected.tsx | 11 +- .../optional-chain-predicate.expected.tsx | 7 +- .../optional-element-access.expected.tsx | 7 +- .../parent-suppression-edge.expected.tsx | 28 +- .../parent-suppression-edge.input.tsx | 2 +- .../recipe-statements-vs-jsx.expected.tsx | 23 +- .../recipe-with-cells.expected.tsx | 11 +- .../nested-default-optional.expected.tsx | 13 +- .../opaque-ref-map.expected.ts | 7 +- .../recipe-with-types.expected.tsx | 13 +- .../with-opaque-ref.expected.tsx | 11 +- .../schema-transform/with-options.expected.ts | 7 +- .../test/opaque-ref/map-callbacks.test.ts | 6 +- .../test/opaque-ref/runtime-style.test.ts | 29 -- .../ts-transformers/test/transform.test.ts | 14 +- packages/ts-transformers/test/utils.ts | 13 +- 102 files changed, 708 insertions(+), 1001 deletions(-) create mode 100644 packages/runner/src/harness/pretransform.ts create mode 100644 packages/ts-transformers/src/core/ct-helpers.ts delete mode 100644 packages/ts-transformers/src/core/cts-directive.ts delete mode 100644 packages/ts-transformers/src/core/imports.ts diff --git a/packages/patterns/array-in-cell-ast-nocomponents.tsx b/packages/patterns/array-in-cell-ast-nocomponents.tsx index 7c1a7a654..ab9f0e97d 100644 --- a/packages/patterns/array-in-cell-ast-nocomponents.tsx +++ b/packages/patterns/array-in-cell-ast-nocomponents.tsx @@ -1,15 +1,5 @@ /// -import { - Cell, - Default, - derive, - h, - handler, - NAME, - recipe, - Stream, - UI, -} from "commontools"; +import { Cell, Default, h, handler, NAME, recipe, UI } from "commontools"; interface Item { text: Default; diff --git a/packages/patterns/array-in-cell-with-remove-ast-nocomponents.tsx b/packages/patterns/array-in-cell-with-remove-ast-nocomponents.tsx index 6c336bf7a..8e6419f30 100644 --- a/packages/patterns/array-in-cell-with-remove-ast-nocomponents.tsx +++ b/packages/patterns/array-in-cell-with-remove-ast-nocomponents.tsx @@ -2,7 +2,6 @@ import { Cell, Default, - derive, h, handler, NAME, diff --git a/packages/patterns/array-in-cell-with-remove-editable.tsx b/packages/patterns/array-in-cell-with-remove-editable.tsx index 4073f000f..d249239e6 100644 --- a/packages/patterns/array-in-cell-with-remove-editable.tsx +++ b/packages/patterns/array-in-cell-with-remove-editable.tsx @@ -1,15 +1,5 @@ /// -import { - Cell, - Default, - derive, - h, - handler, - NAME, - recipe, - Stream, - UI, -} from "commontools"; +import { Cell, Default, h, handler, NAME, recipe, UI } from "commontools"; interface Item { text: Default; diff --git a/packages/patterns/charm-ref-in-cell.tsx b/packages/patterns/charm-ref-in-cell.tsx index 55ebe4d79..3b4c38600 100644 --- a/packages/patterns/charm-ref-in-cell.tsx +++ b/packages/patterns/charm-ref-in-cell.tsx @@ -2,7 +2,6 @@ import { Cell, cell, - createCell, Default, derive, h, diff --git a/packages/patterns/chatbot-list-view.tsx b/packages/patterns/chatbot-list-view.tsx index 42ccccbca..bc5e5f678 100644 --- a/packages/patterns/chatbot-list-view.tsx +++ b/packages/patterns/chatbot-list-view.tsx @@ -3,7 +3,6 @@ import { Cell, cell, Default, - derive, h, handler, ID, diff --git a/packages/patterns/chatbot-note-composed.tsx b/packages/patterns/chatbot-note-composed.tsx index cfbdada5f..11480e872 100644 --- a/packages/patterns/chatbot-note-composed.tsx +++ b/packages/patterns/chatbot-note-composed.tsx @@ -4,31 +4,18 @@ import { Cell, cell, Default, - derive, - fetchData, - getRecipeEnvironment, h, handler, - ID, - ifElse, - JSONSchema, - lift, - llm, - llmDialog, NAME, navigateTo, OpaqueRef, recipe, - str, - Stream, - toSchema, - UI, } from "commontools"; import Chat from "./chatbot.tsx"; import Note from "./note.tsx"; import { type BacklinksMap } from "./backlinks-index.tsx"; -import Tools, { +import { addListItem, calculator, ListItem, diff --git a/packages/patterns/chatbot-outliner.tsx b/packages/patterns/chatbot-outliner.tsx index a5c406408..5ddba89b2 100644 --- a/packages/patterns/chatbot-outliner.tsx +++ b/packages/patterns/chatbot-outliner.tsx @@ -2,25 +2,15 @@ import { BuiltInLLMMessage, Cell, - cell, Default, - derive, - fetchData, - getRecipeEnvironment, h, handler, - ID, ifElse, JSONSchema, - lift, - llm, - llmDialog, NAME, navigateTo, OpaqueRef, recipe, - str, - Stream, UI, } from "commontools"; diff --git a/packages/patterns/chatbot.tsx b/packages/patterns/chatbot.tsx index 6b7e2e9c8..39ae48a7d 100644 --- a/packages/patterns/chatbot.tsx +++ b/packages/patterns/chatbot.tsx @@ -9,14 +9,9 @@ import { generateObject, h, handler, - ifElse, - JSONSchema, - lift, - llm, llmDialog, NAME, recipe, - str, Stream, UI, } from "commontools"; diff --git a/packages/patterns/cheeseboard.tsx b/packages/patterns/cheeseboard.tsx index e8bfd76ee..9ccc594a1 100644 --- a/packages/patterns/cheeseboard.tsx +++ b/packages/patterns/cheeseboard.tsx @@ -1,18 +1,5 @@ /// -import { - Cell, - cell, - Default, - derive, - fetchData, - h, - handler, - ifElse, - lift, - NAME, - recipe, - UI, -} from "commontools"; +import { fetchData, h, lift, NAME, recipe, UI } from "commontools"; /** * Fetch the Cheeseboard pizza schedule via Toolshed's web-read endpoint and diff --git a/packages/patterns/common-tools.tsx b/packages/patterns/common-tools.tsx index 55783fd07..ff3fe05b5 100644 --- a/packages/patterns/common-tools.tsx +++ b/packages/patterns/common-tools.tsx @@ -1,20 +1,13 @@ /// import { - BuiltInLLMMessage, BuiltInLLMTool, Cell, - cell, - Default, derive, fetchData, h, handler, ifElse, - llmDialog, - NAME, recipe, - Stream, - UI, } from "commontools"; ///// COMMON TOOLS (get it?) //// diff --git a/packages/patterns/counter-handlers.ts b/packages/patterns/counter-handlers.ts index 522165404..08a9b53a6 100644 --- a/packages/patterns/counter-handlers.ts +++ b/packages/patterns/counter-handlers.ts @@ -1,5 +1,5 @@ /// -import { Cell, derive, handler } from "commontools"; +import { Cell, handler } from "commontools"; export const increment = handler< unknown, diff --git a/packages/patterns/counter.tsx b/packages/patterns/counter.tsx index 99f3271ae..7e017ae84 100644 --- a/packages/patterns/counter.tsx +++ b/packages/patterns/counter.tsx @@ -1,12 +1,6 @@ /// -import { Default, derive, h, NAME, recipe, str, Stream, UI } from "commontools"; -import { - decrement, - getValue, - increment, - nth, - previous, -} from "./counter-handlers.ts"; +import { Default, h, NAME, recipe, str, Stream, UI } from "commontools"; +import { decrement, increment, nth, previous } from "./counter-handlers.ts"; interface RecipeState { value: Default; diff --git a/packages/patterns/ct-list.tsx b/packages/patterns/ct-list.tsx index 7976688eb..9087ececb 100644 --- a/packages/patterns/ct-list.tsx +++ b/packages/patterns/ct-list.tsx @@ -1,14 +1,5 @@ /// -import { - Cell, - Default, - h, - handler, - NAME, - recipe, - toSchema, - UI, -} from "commontools"; +import { Default, h, NAME, recipe, UI } from "commontools"; interface Item { title: string; diff --git a/packages/patterns/ct-render.tsx b/packages/patterns/ct-render.tsx index ff3e9b16b..42e09c60f 100644 --- a/packages/patterns/ct-render.tsx +++ b/packages/patterns/ct-render.tsx @@ -1,17 +1,5 @@ /// -import { - Cell, - Default, - derive, - h, - handler, - NAME, - Opaque, - OpaqueRef, - recipe, - str, - UI, -} from "commontools"; +import { Cell, Default, h, handler, NAME, recipe, str, UI } from "commontools"; interface RecipeState { value: Default; diff --git a/packages/patterns/ct-select.tsx b/packages/patterns/ct-select.tsx index 53d237468..31951efa8 100644 --- a/packages/patterns/ct-select.tsx +++ b/packages/patterns/ct-select.tsx @@ -1,24 +1,6 @@ /// -import { - Cell, - cell, - compileAndRun, - Default, - derive, - h, - handler, - ifElse, - Mutable, - NAME, - navigateTo, - Opaque, - OpaqueRef, - recipe, - render, - str, - UI, -} from "commontools"; +import { Default, h, NAME, OpaqueRef, recipe, UI } from "commontools"; type Input = { selected: Default; diff --git a/packages/patterns/ct-tags.tsx b/packages/patterns/ct-tags.tsx index f8851341f..89b373647 100644 --- a/packages/patterns/ct-tags.tsx +++ b/packages/patterns/ct-tags.tsx @@ -1,24 +1,6 @@ /// -import { - Cell, - cell, - compileAndRun, - Default, - derive, - h, - handler, - ifElse, - Mutable, - NAME, - navigateTo, - Opaque, - OpaqueRef, - recipe, - render, - str, - UI, -} from "commontools"; +import { Cell, Default, h, handler, NAME, recipe, UI } from "commontools"; type Input = { tags: string[]; diff --git a/packages/patterns/default-app.tsx b/packages/patterns/default-app.tsx index 2abaf91fb..e5e30368f 100644 --- a/packages/patterns/default-app.tsx +++ b/packages/patterns/default-app.tsx @@ -7,7 +7,6 @@ import { handler, NAME, navigateTo, - Opaque, OpaqueRef, recipe, str, diff --git a/packages/patterns/dice.tsx b/packages/patterns/dice.tsx index 9b03fc887..d1a242756 100644 --- a/packages/patterns/dice.tsx +++ b/packages/patterns/dice.tsx @@ -1,14 +1,5 @@ /// -import { - Default, - h, - NAME, - OpaqueRef, - recipe, - str, - Stream, - UI, -} from "commontools"; +import { Default, h, NAME, recipe, Stream, UI } from "commontools"; import { getValue, roll } from "./dice-handlers.ts"; interface RecipeState { diff --git a/packages/patterns/fetch-data.tsx b/packages/patterns/fetch-data.tsx index 343c67f0c..64761060c 100644 --- a/packages/patterns/fetch-data.tsx +++ b/packages/patterns/fetch-data.tsx @@ -1,17 +1,12 @@ /// import { - Cell, - cell, Default, derive, fetchData, h, - handler, lift, NAME, recipe, - schema, - str, UI, } from "commontools"; diff --git a/packages/patterns/instantiate-recipe.tsx b/packages/patterns/instantiate-recipe.tsx index e5f8e998c..9105a67ce 100644 --- a/packages/patterns/instantiate-recipe.tsx +++ b/packages/patterns/instantiate-recipe.tsx @@ -2,7 +2,6 @@ import { Cell, Default, - derive, h, handler, NAME, diff --git a/packages/patterns/list-operations.tsx b/packages/patterns/list-operations.tsx index 43bbc4d94..acce23425 100644 --- a/packages/patterns/list-operations.tsx +++ b/packages/patterns/list-operations.tsx @@ -8,10 +8,7 @@ import { ID, lift, NAME, - Opaque, recipe, - str, - toSchema, UI, } from "commontools"; diff --git a/packages/patterns/nested-counter.tsx b/packages/patterns/nested-counter.tsx index 9e7caaabe..90e6551fb 100644 --- a/packages/patterns/nested-counter.tsx +++ b/packages/patterns/nested-counter.tsx @@ -1,5 +1,5 @@ /// -import { Default, derive, h, NAME, recipe, str, UI } from "commontools"; +import { Default, h, NAME, recipe, str, UI } from "commontools"; import { decrement, increment, nth, previous } from "./counter-handlers.ts"; interface RecipeState { diff --git a/packages/patterns/note.tsx b/packages/patterns/note.tsx index a3379cb8c..345130af5 100644 --- a/packages/patterns/note.tsx +++ b/packages/patterns/note.tsx @@ -11,7 +11,6 @@ import { navigateTo, OpaqueRef, recipe, - toSchema, UI, } from "commontools"; import { type BacklinksMap } from "./backlinks-index.tsx"; diff --git a/packages/patterns/output_schema.tsx b/packages/patterns/output_schema.tsx index 3e683a53b..07f5afa06 100644 --- a/packages/patterns/output_schema.tsx +++ b/packages/patterns/output_schema.tsx @@ -2,14 +2,10 @@ import { Cell, Default, - derive, h, handler, NAME, - Opaque, - OpaqueRef, recipe, - str, UI, VNode, } from "commontools"; diff --git a/packages/runner/src/harness/engine.ts b/packages/runner/src/harness/engine.ts index 7bbb8dc11..f8d97d604 100644 --- a/packages/runner/src/harness/engine.ts +++ b/packages/runner/src/harness/engine.ts @@ -22,6 +22,7 @@ import * as RuntimeModules from "./runtime-modules.ts"; import { IRuntime } from "../runtime.ts"; import * as merkleReference from "merkle-reference"; import { StaticCache } from "@commontools/static"; +import { pretransformProgram } from "./pretransform.ts"; const RUNTIME_ENGINE_CONSOLE_HOOK = "RUNTIME_ENGINE_CONSOLE_HOOK"; const INJECTED_SCRIPT = @@ -147,7 +148,7 @@ export class Engine extends EventTarget implements Harness { > { const id = options.identifier ?? computeId(program); const filename = options.filename ?? `${id}.js`; - const mappedProgram = mapPrefixProgramFiles(program, id); + const mappedProgram = pretransformProgram(program, id); const resolver = new EngineProgramResolver( mappedProgram, this.ctRuntime.staticCache, @@ -250,32 +251,3 @@ function computeId(program: Program): string { ]; return merkleReference.refer(source).toString(); } - -// Adds `id` as a prefix to all files in the program. -// Injects a new entry at root `/index.ts` to re-export -// the entry contents because otherwise `typescript` -// flattens the output, eliding the common prefix. -function mapPrefixProgramFiles(program: RuntimeProgram, id: string): Program { - const main = program.main; - const exportNameds = `export * from "${prefix(main, id)}";`; - const exportDefault = `export { default } from "${prefix(main, id)}";`; - const hasDefault = !program.mainExport || program.mainExport === "default"; - const files = [ - ...program.files.map((source) => ({ - name: prefix(source.name, id), - contents: source.contents, - })), - { - name: `/index.ts`, - contents: `${exportNameds}${hasDefault ? `\n${exportDefault}` : ""}`, - }, - ]; - return { - main: `/index.ts`, - files, - }; -} - -function prefix(filename: string, id: string): string { - return `/${id}${filename}`; -} diff --git a/packages/runner/src/harness/pretransform.ts b/packages/runner/src/harness/pretransform.ts new file mode 100644 index 000000000..39f3524d4 --- /dev/null +++ b/packages/runner/src/harness/pretransform.ts @@ -0,0 +1,60 @@ +import { transformCtDirective } from "@commontools/ts-transformers"; +import { RuntimeProgram } from "./types.ts"; + +export function pretransformProgram( + program: RuntimeProgram, + id: string, +): RuntimeProgram { + program = transformInjectHelperModule(program); + program = transformProgramWithPrefix(program, id); + return program; +} + +// For each source file in the program, replace +// a `/// ` directive line with an +// internal import statement for use by the AST transformer +// to provide access to helpers like `derive`, etc. +export function transformInjectHelperModule( + program: RuntimeProgram, +): RuntimeProgram { + return { + main: program.main, + files: program.files.map((source) => ({ + name: source.name, + contents: transformCtDirective(source.contents), + })), + mainExport: program.mainExport, + }; +} + +// Adds `id` as a prefix to all files in the program. +// Injects a new entry at root `/index.ts` to re-export +// the entry contents because otherwise `typescript` +// flattens the output, eliding the common prefix. +export function transformProgramWithPrefix( + program: RuntimeProgram, + id: string, +): RuntimeProgram { + const main = program.main; + const exportNameds = `export * from "${prefix(main, id)}";`; + const exportDefault = `export { default } from "${prefix(main, id)}";`; + const hasDefault = !program.mainExport || program.mainExport === "default"; + const files = [ + ...program.files.map((source) => ({ + name: prefix(source.name, id), + contents: source.contents, + })), + { + name: `/index.ts`, + contents: `${exportNameds}${hasDefault ? `\n${exportDefault}` : ""}`, + }, + ]; + return { + main: `/index.ts`, + files, + }; +} + +function prefix(filename: string, id: string): string { + return `/${id}${filename}`; +} diff --git a/packages/ts-transformers/src/core/context.ts b/packages/ts-transformers/src/core/context.ts index 4f3a3b7a2..6497485b1 100644 --- a/packages/ts-transformers/src/core/context.ts +++ b/packages/ts-transformers/src/core/context.ts @@ -1,10 +1,10 @@ import ts from "typescript"; -import { ImportRequirements } from "./imports.ts"; import { DiagnosticInput, TransformationDiagnostic, TransformationOptions, } from "./transformers.ts"; +import { CTHelpers } from "./ct-helpers.ts"; const DEFAULT_OPTIONS: TransformationOptions = { mode: "transform", @@ -16,7 +16,6 @@ export interface TransformationContextConfig { sourceFile: ts.SourceFile; tsContext: ts.TransformationContext; options?: TransformationOptions; - imports?: ImportRequirements; } export class TransformationContext { @@ -25,7 +24,7 @@ export class TransformationContext { readonly factory: ts.NodeFactory; readonly sourceFile: ts.SourceFile; readonly options: TransformationOptions; - readonly imports: ImportRequirements; + readonly ctHelpers: CTHelpers; readonly diagnostics: TransformationDiagnostic[] = []; readonly tsContext: ts.TransformationContext; #typeCache = new Map(); @@ -36,7 +35,10 @@ export class TransformationContext { this.tsContext = config.tsContext; this.factory = config.tsContext.factory; this.sourceFile = config.sourceFile; - this.imports = config.imports ?? new ImportRequirements(); + this.ctHelpers = new CTHelpers({ + factory: this.factory, + sourceFile: this.sourceFile, + }); this.options = { ...DEFAULT_OPTIONS, ...config.options, diff --git a/packages/ts-transformers/src/core/ct-helpers.ts b/packages/ts-transformers/src/core/ct-helpers.ts new file mode 100644 index 000000000..eff061d62 --- /dev/null +++ b/packages/ts-transformers/src/core/ct-helpers.ts @@ -0,0 +1,120 @@ +import ts from "typescript"; +import { TransformationContext } from "./mod.ts"; + +export const CT_HELPERS_IDENTIFIER = "__ctHelpers"; + +const CT_HELPERS_SPECIFIER = "commontools"; + +const HELPERS_STMT = + `import * as ${CT_HELPERS_IDENTIFIER} from "${CT_HELPERS_SPECIFIER}";`; + +const HELPERS_USED_STMT = `${CT_HELPERS_IDENTIFIER}.NAME; // `; + +export class CTHelpers { + #sourceFile: ts.SourceFile; + #factory: ts.NodeFactory; + #helperIdent?: ts.Identifier; + + constructor(params: Pick) { + this.#sourceFile = params.sourceFile; + this.#factory = params.factory; + + for (const stmt of this.#sourceFile.statements) { + const symbol = getCTHelpersIdentifier(stmt); + if (symbol) { + this.#helperIdent = symbol; + break; + } + } + } + + sourceHasHelpers(): boolean { + return !!this.#helperIdent; + } + + // Returns an PropertyAccessExpression of the requested + // helper name e.g. `(__ctHelpers.derive)`. + getHelperExpr( + name: string, + ): ts.PropertyAccessExpression { + if (!this.sourceHasHelpers()) { + throw new Error("Source file does not contain helpers."); + } + return this.#factory.createPropertyAccessExpression( + this.#helperIdent!, + name, + ); + } + + // Returns an QualifiedName of the requested + // helper name e.g. `__ctHelpers.JSONSchema`. + getHelperQualified( + name: string, + ): ts.QualifiedName { + if (!this.sourceHasHelpers()) { + throw new Error("Source file does not contain helpers."); + } + return this.#factory.createQualifiedName( + this.#helperIdent!, + name, + ); + } +} + +// Replace a `/// ` directive line with an +// internal import statement for use by the AST transformer +// to provide access to helpers like `derive`, etc. +// This operates on strings, and to be used outside of +// the TypeScript transformer pipeline, since symbol binding +// occurs before transformers run. +// +// We must also inject usage of the module before the AST transformer +// pipeline, otherwise the binding fails, and the helper module +// is not available in the compiled JS. +// +// Source maps are derived from this transformation. +// Take care in maintaining source lines from its input. +// +// This injected statement enables subsequent transformations. +export function transformCtDirective( + source: string, +): string { + // Throw when this symbol name is already in use + // for some reason. + if (source.indexOf(CT_HELPERS_IDENTIFIER) !== -1) { + throw new Error( + `Source cannot contain reserved '${CT_HELPERS_IDENTIFIER}' symbol.`, + ); + } + const lines = source.split("\n"); + if (!lines[0] || !isCTSEnabled(lines[0])) { + return source; + } + return [ + HELPERS_STMT, + ...lines.slice(1), + HELPERS_USED_STMT, + ].join("\n"); +} + +function isCTSEnabled(line: string) { + return /^\/\/\/\s*/m.test(line); +} + +function getCTHelpersIdentifier( + statement: ts.Statement, +): ts.Identifier | undefined { + if (!ts.isImportDeclaration(statement)) return; + const { importClause, moduleSpecifier } = statement; + + // Check specifier is "commontools" + if (!ts.isStringLiteral(moduleSpecifier)) return; + if (moduleSpecifier.text !== CT_HELPERS_SPECIFIER) return; + + // Check it is a namespace import `* as __ctHelpers` + if (!importClause || !ts.isImportClause(importClause)) return; + const { namedBindings } = importClause; + if (!namedBindings || !ts.isNamespaceImport(namedBindings)) return; + if (namedBindings.name.getText() !== CT_HELPERS_IDENTIFIER) return; + return namedBindings.name; +} diff --git a/packages/ts-transformers/src/core/cts-directive.ts b/packages/ts-transformers/src/core/cts-directive.ts deleted file mode 100644 index 85192397a..000000000 --- a/packages/ts-transformers/src/core/cts-directive.ts +++ /dev/null @@ -1,17 +0,0 @@ -import ts from "typescript"; - -/** - * Returns true when the source file starts with the CommonTools - * triple-slash directive `/// `. - */ -export function hasCtsEnableDirective(sourceFile: ts.SourceFile): boolean { - const text = sourceFile.getFullText(); - const ranges = ts.getLeadingCommentRanges(text, 0) ?? []; - for (const range of ranges) { - const commentText = text.slice(range.pos, range.end); - if (/^\/\/\/\s*/m.test(commentText)) { - return true; - } - } - return false; -} diff --git a/packages/ts-transformers/src/core/imports.ts b/packages/ts-transformers/src/core/imports.ts deleted file mode 100644 index 84e45cb3c..000000000 --- a/packages/ts-transformers/src/core/imports.ts +++ /dev/null @@ -1,254 +0,0 @@ -import ts from "typescript"; -import { TransformationContext } from "./mod.ts"; - -export interface ImportRequest { - readonly name: string; - readonly module: string; - readonly typeOnly?: boolean; -} - -interface ModuleRequirements { - readonly required: Set; - readonly forbidden: Set; - readonly module: string; - readonly typeOnly: boolean; -} - -// Accumulates requirements in the form of required -// or forbidden module imports. Once accumulated, -// these requirements can be applied to a SourceFile. -export class ImportRequirements { - #map = new Map(); - - // Marks the import as required, importing the - // symbol during render if not already imported. - require(request: ImportRequest): void { - const reqs = this.#get(request); - reqs.forbidden.delete(request.name); - reqs.required.add(request.name); - } - - // Marks the import for removal when rendered, - // if imported by the source code. - forbid(request: ImportRequest): void { - const reqs = this.#get(request); - reqs.required.delete(request.name); - reqs.forbidden.add(request.name); - } - - getIdentifier( - context: Pick, - request: ImportRequest, - ): ts.Identifier { - this.require(request); - const identifier = findImportedIdentifier( - context.sourceFile, - request.module, - request.name, - ); - if (identifier) return identifier; - // This will create a raw identifier that is not bound - // to an import, and most likely will fail when compiling - // to JS. - return context.factory.createIdentifier(`${request.name}`); - } - - // Returns the requirements scoped by the request, - // lazily creating and storing as needed. - #get(request: ImportRequest): ModuleRequirements { - const key = `${request.module}|${request.typeOnly ? "t" : "v"}`; - const existing = this.#map.get(key); - if (existing) { - return existing; - } - const reqs = { - module: request.module, - typeOnly: request.typeOnly ?? false, - required: new Set(), - forbidden: new Set(), - }; - this.#map.set(key, reqs); - return reqs; - } - - // Applies all requirements to the provided SourceFile, - // returning the mapped source. - apply(sourceFile: ts.SourceFile, factory: ts.NodeFactory): ts.SourceFile { - let source = sourceFile; - for (const record of this.#map.values()) { - source = applyToSource(source, factory, record); - } - return source; - } -} - -function applyToSource( - sourceFile: ts.SourceFile, - factory: ts.NodeFactory, - record: ModuleRequirements, -): ts.SourceFile { - let existing: ts.ImportDeclaration | undefined; - let existingIndex = -1; - - sourceFile.statements.forEach((statement, index) => { - if (!ts.isImportDeclaration(statement)) { - return; - } - if (!ts.isStringLiteral(statement.moduleSpecifier)) { - return; - } - if (statement.moduleSpecifier.text !== record.module) { - return; - } - const clause = statement.importClause; - if (!clause || !clause.namedBindings) { - return; - } - if (!ts.isNamedImports(clause.namedBindings)) { - return; - } - if (record.typeOnly && !clause.isTypeOnly) { - return; - } - existing = statement; - existingIndex = index; - }); - - if (existing) { - const newStmt = applyToImport(existing, factory, record); - if (newStmt) { - const statements = [...sourceFile.statements]; - statements[existingIndex] = newStmt; - return factory.updateSourceFile(sourceFile, statements); - } else { - return sourceFile; - } - } - return insertImport(sourceFile, factory, record); -} - -// Transforms the given import statement with -// the module requirements. Returns `undefined` -// if no transformation could be applied. -function applyToImport( - existing: ts.ImportDeclaration, - factory: ts.NodeFactory, - record: ModuleRequirements, -): ts.ImportDeclaration | undefined { - const clause = existing.importClause; - if (!clause || !clause.namedBindings) { - return undefined; - } - - const named = clause.namedBindings; - if (!ts.isNamedImports(named)) { - return undefined; - } - - const filteredExisting = named.elements.filter((element) => - !record.forbidden.has(element.name.text) - ); - const didRemove = filteredExisting.length !== named.elements.length; - const existingNames = new Set( - filteredExisting.map((element) => element.name.text), - ); - const nextElements = [...filteredExisting]; - - for (const name of record.required) { - if (existingNames.has(name)) continue; - nextElements.push( - factory.createImportSpecifier( - false, - undefined, - factory.createIdentifier(name), - ), - ); - } - - if (!didRemove && nextElements.length === named.elements.length) { - return undefined; - } - - return factory.updateImportDeclaration( - existing, - existing.modifiers, - factory.updateImportClause( - clause, - record.typeOnly || clause.isTypeOnly, - clause.name, - factory.createNamedImports(nextElements), - ), - existing.moduleSpecifier, - existing.assertClause, - ); -} - -function insertImport( - sourceFile: ts.SourceFile, - factory: ts.NodeFactory, - record: ModuleRequirements, -): ts.SourceFile { - const elements = Array.from(record.required).map((name) => - factory.createImportSpecifier( - false, - undefined, - factory.createIdentifier(name), - ) - ); - - const clause = factory.createImportClause( - record.typeOnly, - undefined, - factory.createNamedImports(elements), - ); - - const declaration = factory.createImportDeclaration( - undefined, - clause, - factory.createStringLiteral(record.module), - undefined, - ); - - const statements = [...sourceFile.statements]; - let insertIndex = 0; - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - if (!statement) { - break; - } - if (ts.isImportDeclaration(statement)) { - insertIndex = i + 1; - continue; - } - break; - } - statements.splice(insertIndex, 0, declaration); - return factory.updateSourceFile(sourceFile, statements); -} - -function findImportedIdentifier( - sourceFile: ts.SourceFile, - moduleName: string, - importName: string, -): ts.Identifier | undefined { - for (const statement of sourceFile.statements) { - if (ts.isImportDeclaration(statement)) { - const moduleSpecifier = statement.moduleSpecifier; - if ( - ts.isStringLiteral(moduleSpecifier) && - moduleSpecifier.text === moduleName - ) { - const namedBindings = statement.importClause?.namedBindings; - if (namedBindings && ts.isNamedImports(namedBindings)) { - for (const element of namedBindings.elements) { - const name = element.propertyName?.text || element.name.text; - if (name === importName) { - return element.name; // This is the local identifier - } - } - } - } - } - } - return undefined; -} diff --git a/packages/ts-transformers/src/core/mod.ts b/packages/ts-transformers/src/core/mod.ts index 25f1ca262..8511d4c0d 100644 --- a/packages/ts-transformers/src/core/mod.ts +++ b/packages/ts-transformers/src/core/mod.ts @@ -1,4 +1,3 @@ -export { type ImportRequest, ImportRequirements } from "./imports.ts"; export { TransformationContext } from "./context.ts"; export type { DiagnosticInput, @@ -9,4 +8,8 @@ export type { } from "./transformers.ts"; export { Pipeline, Transformer } from "./transformers.ts"; export * from "./common-tools-symbols.ts"; -export { hasCtsEnableDirective } from "./cts-directive.ts"; +export { + CT_HELPERS_IDENTIFIER, + CTHelpers, + transformCtDirective, +} from "./ct-helpers.ts"; diff --git a/packages/ts-transformers/src/ct-pipeline.ts b/packages/ts-transformers/src/ct-pipeline.ts index 0047d0980..35cc39232 100644 --- a/packages/ts-transformers/src/ct-pipeline.ts +++ b/packages/ts-transformers/src/ct-pipeline.ts @@ -3,7 +3,7 @@ import { SchemaGeneratorTransformer, SchemaInjectionTransformer, } from "./transformers/mod.ts"; -import { Pipeline, TransformationOptions, TypeRegistry } from "./core/mod.ts"; +import { Pipeline, TransformationOptions } from "./core/mod.ts"; export class CommonToolsTransformerPipeline extends Pipeline { constructor(options: TransformationOptions = {}) { diff --git a/packages/ts-transformers/src/mod.ts b/packages/ts-transformers/src/mod.ts index 361a26d21..ea2ff6296 100644 --- a/packages/ts-transformers/src/mod.ts +++ b/packages/ts-transformers/src/mod.ts @@ -1,13 +1,10 @@ -export { ImportRequirements as ImportManager } from "./core/imports.ts"; -export type { ImportRequest } from "./core/imports.ts"; - export type { TransformationContext, TransformationDiagnostic, TransformationOptions, TransformMode, } from "./core/mod.ts"; -export { Pipeline, Transformer } from "./core/mod.ts"; +export { Pipeline, transformCtDirective, Transformer } from "./core/mod.ts"; export { OpaqueRefJSXTransformer, diff --git a/packages/ts-transformers/src/transformers/builtins/derive.ts b/packages/ts-transformers/src/transformers/builtins/derive.ts index ab6153425..21c02974a 100644 --- a/packages/ts-transformers/src/transformers/builtins/derive.ts +++ b/packages/ts-transformers/src/transformers/builtins/derive.ts @@ -1,31 +1,5 @@ import ts from "typescript"; -import { - containsOpaqueRef, - isOpaqueRefType, - isSimpleOpaqueRefAccess, -} from "../opaque-ref/opaque-ref.ts"; -import { - createDataFlowAnalyzer, - type DataFlowAnalysis, - dedupeExpressions, -} from "../../ast/mod.ts"; -import { ImportRequirements } from "../../core/mod.ts"; - -function replaceOpaqueRefWithParam( - expression: ts.Expression, - opaqueRef: ts.Expression, - paramName: string, - factory: ts.NodeFactory, - context: ts.TransformationContext, -): ts.Expression { - const visit = (node: ts.Node): ts.Node => { - if (node === opaqueRef) { - return factory.createIdentifier(paramName); - } - return ts.visitEachChild(node, visit, context); - }; - return visit(expression) as ts.Expression; -} +import { CTHelpers } from "../../core/ct-helpers.ts"; function replaceOpaqueRefsWithParams( expression: ts.Expression, @@ -58,7 +32,7 @@ export interface DeriveCallOptions { readonly factory: ts.NodeFactory; readonly sourceFile: ts.SourceFile; readonly tsContext: ts.TransformationContext; - readonly imports: ImportRequirements; + readonly ctHelpers: CTHelpers; } function createPropertyName( @@ -166,7 +140,7 @@ export function createDeriveCall( ): ts.Expression | undefined { if (refs.length === 0) return undefined; - const { factory, tsContext, imports, sourceFile } = options; + const { factory, tsContext, ctHelpers, sourceFile } = options; const { entries, refToParamName } = planDeriveEntries(refs); if (entries.length === 0) return undefined; @@ -186,17 +160,14 @@ export function createDeriveCall( lambdaBody, ); - const deriveIdentifier = imports.getIdentifier({ factory, sourceFile }, { - module: "commontools", - name: "derive", - }); + const deriveExpr = ctHelpers.getHelperExpr("derive"); const deriveArgs = [ ...createDeriveArgs(factory, entries), arrowFunction, ]; return factory.createCallExpression( - deriveIdentifier, + deriveExpr, undefined, deriveArgs, ); diff --git a/packages/ts-transformers/src/transformers/builtins/ifelse.ts b/packages/ts-transformers/src/transformers/builtins/ifelse.ts index ca9f7ea3e..86ed5de14 100644 --- a/packages/ts-transformers/src/transformers/builtins/ifelse.ts +++ b/packages/ts-transformers/src/transformers/builtins/ifelse.ts @@ -1,10 +1,10 @@ import ts from "typescript"; -import { ImportRequirements } from "../../core/mod.ts"; +import { CTHelpers } from "../../core/mod.ts"; export interface IfElseParams { expression: ts.ConditionalExpression; factory: ts.NodeFactory; - imports: ImportRequirements; + ctHelpers: CTHelpers; sourceFile: ts.SourceFile; overrides?: IfElseOverrides; } @@ -16,11 +16,8 @@ export interface IfElseOverrides { } export function createIfElseCall(params: IfElseParams): ts.CallExpression { - const { factory, imports, overrides, expression, sourceFile } = params; - const ifElseIdentifier = imports.getIdentifier({ factory, sourceFile }, { - module: "commontools", - name: "ifElse", - }); + const { factory, ctHelpers, overrides, expression } = params; + const ifElseExpr = ctHelpers.getHelperExpr("ifElse"); let predicate = overrides?.predicate ?? expression.condition; let whenTrue = overrides?.whenTrue ?? expression.whenTrue; @@ -34,7 +31,7 @@ export function createIfElseCall(params: IfElseParams): ts.CallExpression { } return factory.createCallExpression( - ifElseIdentifier, + ifElseExpr, undefined, [predicate, whenTrue, whenFalse], ); diff --git a/packages/ts-transformers/src/transformers/opaque-ref-jsx.ts b/packages/ts-transformers/src/transformers/opaque-ref-jsx.ts index bb2a097e5..d79959090 100644 --- a/packages/ts-transformers/src/transformers/opaque-ref-jsx.ts +++ b/packages/ts-transformers/src/transformers/opaque-ref-jsx.ts @@ -1,29 +1,19 @@ import ts from "typescript"; -import { - hasCtsEnableDirective, - TransformationContext, - Transformer, -} from "../core/mod.ts"; +import { TransformationContext, Transformer } from "../core/mod.ts"; import { createDataFlowAnalyzer, detectCallKind, isEventHandlerJsxAttribute, } from "../ast/mod.ts"; -import { OpaqueRefHelperName, rewriteExpression } from "./opaque-ref/mod.ts"; +import { rewriteExpression } from "./opaque-ref/mod.ts"; export class OpaqueRefJSXTransformer extends Transformer { override filter(context: TransformationContext): boolean { - return hasCtsEnableDirective(context.sourceFile); + return context.ctHelpers.sourceHasHelpers(); } transform(context: TransformationContext): ts.SourceFile { - const { tsContext: transformation } = context; - - const out = transform(context); - return context.imports.apply( - out, - transformation.factory, - ); + return transform(context); } } diff --git a/packages/ts-transformers/src/transformers/opaque-ref/emitters/conditional-expression.ts b/packages/ts-transformers/src/transformers/opaque-ref/emitters/conditional-expression.ts index 77ab91042..46b7c50a2 100644 --- a/packages/ts-transformers/src/transformers/opaque-ref/emitters/conditional-expression.ts +++ b/packages/ts-transformers/src/transformers/opaque-ref/emitters/conditional-expression.ts @@ -1,6 +1,6 @@ import ts from "typescript"; -import type { Emitter, OpaqueRefHelperName } from "../types.ts"; +import type { Emitter } from "../types.ts"; import { createIfElseCall } from "../../builtins/ifelse.ts"; import { selectDataFlowsWithin } from "../../../ast/mod.ts"; import { isSimpleOpaqueRefAccess } from "../opaque-ref.ts"; @@ -109,7 +109,7 @@ export const emitConditionalExpression: Emitter = ({ return createIfElseCall({ expression, factory: context.factory, - imports: context.imports, + ctHelpers: context.ctHelpers, sourceFile: context.sourceFile, overrides: { predicate, diff --git a/packages/ts-transformers/src/transformers/opaque-ref/helpers.ts b/packages/ts-transformers/src/transformers/opaque-ref/helpers.ts index 1c1ed2a28..544312d0e 100644 --- a/packages/ts-transformers/src/transformers/opaque-ref/helpers.ts +++ b/packages/ts-transformers/src/transformers/opaque-ref/helpers.ts @@ -285,7 +285,7 @@ export function createDeriveCallForExpression( factory: context.factory, sourceFile: context.sourceFile, tsContext: context.tsContext, - imports: context.imports, + ctHelpers: context.ctHelpers, }); return deriveCall; diff --git a/packages/ts-transformers/src/transformers/schema-generator.ts b/packages/ts-transformers/src/transformers/schema-generator.ts index 67f0b32b7..96fa06ea5 100644 --- a/packages/ts-transformers/src/transformers/schema-generator.ts +++ b/packages/ts-transformers/src/transformers/schema-generator.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import { - hasCtsEnableDirective, + CT_HELPERS_IDENTIFIER, TransformationContext, Transformer, } from "../core/mod.ts"; @@ -10,7 +10,7 @@ let generateSchema: ReturnType | undefined; export class SchemaGeneratorTransformer extends Transformer { override filter(context: TransformationContext): boolean { - return hasCtsEnableDirective(context.sourceFile); + return context.ctHelpers.sourceHasHelpers(); } transform(context: TransformationContext): ts.SourceFile { @@ -19,17 +19,8 @@ export class SchemaGeneratorTransformer extends Transformer { const { logger, typeRegistry } = context.options; const visit: ts.Visitor = (node) => { - if ( - ts.isCallExpression(node) && - ts.isIdentifier(node.expression) && - node.expression.text === "toSchema" && - node.typeArguments && - node.typeArguments.length === 1 - ) { - const typeArg = node.typeArguments[0]; - if (!typeArg) { - return ts.visitEachChild(node, visit, transformation); - } + if (isToSchemaNode(node)) { + const typeArg = node.typeArguments[0]!; // First check if we have a registered Type for this node // (from schema-injection when synthetic TypeNodes were created) @@ -73,16 +64,12 @@ export class SchemaGeneratorTransformer extends Transformer { ), ); - const jsonSchemaIdentifier = context.imports.getIdentifier( - context, - { module: "commontools", name: "JSONSchema" }, + const jsonSchemaName = context.ctHelpers.getHelperQualified( + "JSONSchema", ); const satisfiesExpression = context.factory.createSatisfiesExpression( constAssertion, - context.factory.createTypeReferenceNode( - jsonSchemaIdentifier, - undefined, - ), + context.factory.createTypeReferenceNode(jsonSchemaName), ); return satisfiesExpression; @@ -91,17 +78,7 @@ export class SchemaGeneratorTransformer extends Transformer { return ts.visitEachChild(node, visit, transformation); }; - const result = ts.visitNode(sourceFile, visit) as ts.SourceFile; - - context.imports.forbid({ - module: "commontools", - name: "toSchema", - }); - - return context.imports.apply( - result, - transformation.factory, - ); + return ts.visitNode(sourceFile, visit) as ts.SourceFile; } } @@ -172,3 +149,33 @@ function evaluateExpression( if (constantValue !== undefined) return constantValue; return undefined; } + +// Helper type extending CallExpression with +// truthy typeArguments. +interface ToSchemaNode extends ts.CallExpression { + typeArguments: ts.NodeArray; +} +function isToSchemaNode(node: ts.Node): node is ToSchemaNode { + if (!ts.isCallExpression(node)) return false; + const { typeArguments, expression } = node; + if (!typeArguments || typeArguments.length !== 1) return false; + + // Raw identity expression `toSchema()` + if ( + ts.isIdentifier(expression) && + expression.text === "toSchema" && + typeArguments && + typeArguments.length === 1 + ) { + return true; + } + // Raw property access expression `__ctHelpers.toSchema()` + if ( + ts.isPropertyAccessExpression(expression) && + expression.expression.getText() === CT_HELPERS_IDENTIFIER && + expression.name.text === "toSchema" + ) { + return true; + } + return false; +} diff --git a/packages/ts-transformers/src/transformers/schema-injection.ts b/packages/ts-transformers/src/transformers/schema-injection.ts index 386cb44d6..5816576dc 100644 --- a/packages/ts-transformers/src/transformers/schema-injection.ts +++ b/packages/ts-transformers/src/transformers/schema-injection.ts @@ -8,7 +8,6 @@ import { typeToSchemaTypeNode, } from "../ast/mod.ts"; import { - hasCtsEnableDirective, TransformationContext, Transformer, type TypeRegistry, @@ -104,25 +103,22 @@ function collectFunctionSchemaTypeNodes( } function createToSchemaCall( - { imports, factory, sourceFile }: Pick< + { ctHelpers, factory }: Pick< TransformationContext, - "imports" | "factory" | "sourceFile" + "ctHelpers" | "factory" >, typeNode: ts.TypeNode, ): ts.CallExpression { - const identifier = imports.getIdentifier({ factory, sourceFile }, { - module: "commontools", - name: "toSchema", - }); + const expr = ctHelpers.getHelperExpr("toSchema"); return factory.createCallExpression( - identifier, + expr, [typeNode], [], ); } function prependSchemaArguments( - context: Pick, + context: Pick, node: ts.CallExpression, argumentTypeNode: ts.TypeNode, argumentType: ts.Type | undefined, @@ -159,10 +155,11 @@ function prependSchemaArguments( export class SchemaInjectionTransformer extends Transformer { override filter(context: TransformationContext): boolean { - return hasCtsEnableDirective(context.sourceFile); + return context.ctHelpers.sourceHasHelpers(); } + transform(context: TransformationContext): ts.SourceFile { - const { sourceFile, tsContext: transformation, checker, imports } = context; + const { sourceFile, tsContext: transformation, checker } = context; const typeRegistry = context.options.typeRegistry; const visit = (node: ts.Node): ts.Node => { diff --git a/packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx index 32b8860fa..4d5864974 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx @@ -1,5 +1,5 @@ -/// -import { Default, h, NAME, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Default, h, NAME, recipe, UI } from "commontools"; interface RecipeState { count: Default; label: Default; @@ -17,11 +17,12 @@ export default recipe({ } }, required: ["count", "label"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [NAME]: state.label, [UI]: (
- {ifElse(derive({ state, state_count: state.count }, ({ state: state, state_count: _v2 }) => state && _v2 > 0),

Positive

,

Non-positive

)} + {__ctHelpers.ifElse(__ctHelpers.derive({ state, state_count: state.count }, ({ state: state, state_count: _v2 }) => state && _v2 > 0),

Positive

,

Non-positive

)}
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/counter-recipe.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/counter-recipe.expected.tsx index 31383fe77..135e7f9f2 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/counter-recipe.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/counter-recipe.expected.tsx @@ -1,12 +1,12 @@ -/// -import { Cell, Default, h, handler, NAME, recipe, str, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, Default, h, handler, NAME, recipe, str, UI } from "commontools"; interface CounterState { value: Cell; } interface RecipeState { value: Default; } -const increment = handler(true as const satisfies JSONSchema, { +const increment = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -15,10 +15,10 @@ const increment = handler(true as const satisfies JSONSchema, { } }, required: ["value"] -} as const satisfies JSONSchema, (e, state) => { +} as const satisfies __ctHelpers.JSONSchema, (e, state) => { state.value.set(state.value.get() + 1); }); -const decrement = handler(true as const satisfies JSONSchema, { +const decrement = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -27,7 +27,7 @@ const decrement = handler(true as const satisfies JSONSchema, { } }, required: ["value"] -} as const satisfies JSONSchema, (_, state: { +} as const satisfies __ctHelpers.JSONSchema, (_, state: { value: Cell; }) => { state.value.set(state.value.get() - 1); @@ -41,16 +41,17 @@ export default recipe({ } }, required: ["value"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [NAME]: str `Simple counter: ${state.value}`, [UI]: (
-
    -
  • next number: {ifElse(state.value, derive(state.value, _v1 => _v1 + 1), "unknown")}
  • +
  • next number: {__ctHelpers.ifElse(state.value, __ctHelpers.derive(state.value, _v1 => _v1 + 1), "unknown")}
+
), value: state.value, }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/derive-object-literal-input.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/derive-object-literal-input.expected.tsx index edc1341f5..9db62853a 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/derive-object-literal-input.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/derive-object-literal-input.expected.tsx @@ -1,29 +1,29 @@ -/// -import { cell, derive, lift, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { cell, derive, lift } from "commontools"; const stage = cell("initial"); const attemptCount = cell(0); const acceptedCount = cell(0); const rejectedCount = cell(0); const normalizedStage = lift({ type: "string" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "string" -} as const satisfies JSONSchema, (value: string) => value)(stage); +} as const satisfies __ctHelpers.JSONSchema, (value: string) => value)(stage); const attempts = lift({ type: "number" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, (count: number) => count)(attemptCount); +} as const satisfies __ctHelpers.JSONSchema, (count: number) => count)(attemptCount); const accepted = lift({ type: "number" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, (count: number) => count)(acceptedCount); +} as const satisfies __ctHelpers.JSONSchema, (count: number) => count)(acceptedCount); const rejected = lift({ type: "number" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, (count: number) => count)(rejectedCount); +} as const satisfies __ctHelpers.JSONSchema, (count: number) => count)(rejectedCount); const summary = derive({ type: "object", properties: { @@ -45,12 +45,13 @@ const summary = derive({ } }, required: ["stage", "attempts", "accepted", "rejected"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "string" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { stage: normalizedStage, attempts: attempts, accepted: accepted, rejected: rejected, }, (snapshot) => `stage:${snapshot.stage} attempts:${snapshot.attempts}` + ` accepted:${snapshot.accepted} rejected:${snapshot.rejected}`); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/event-handler-no-derive.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/event-handler-no-derive.expected.tsx index bd0796a26..25696ac38 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/event-handler-no-derive.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/event-handler-no-derive.expected.tsx @@ -1,5 +1,5 @@ -/// -import { Cell, Default, h, handler, recipe, UI, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, Default, h, handler, recipe, UI } from "commontools"; declare global { namespace JSX { interface IntrinsicElements { @@ -7,7 +7,7 @@ declare global { } } } -const handleClick = handler(true as const satisfies JSONSchema, { +const handleClick = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { count: { @@ -16,7 +16,7 @@ const handleClick = handler(true as const satisfies JSONSchema, { } }, required: ["count"] -} as const satisfies JSONSchema, (_, { count }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { count }) => { count.set(count.get() + 1); }); export default recipe({ @@ -28,11 +28,11 @@ export default recipe({ } }, required: ["count"] -} as const satisfies JSONSchema, ({ count }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ count }) => { return { [UI]: (
{/* Regular JSX expression - should be wrapped in derive */} - Count: {derive(count, count => count + 1)} + Count: {__ctHelpers.derive(count, count => count + 1)} {/* Event handler with OpaqueRef - should NOT be wrapped in derive */} @@ -47,3 +47,4 @@ export default recipe({ count, }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/handler-object-literal.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/handler-object-literal.expected.tsx index c017384d2..12824e042 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/handler-object-literal.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/handler-object-literal.expected.tsx @@ -1,10 +1,10 @@ -/// -import { Cell, handler, recipe, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, handler, recipe } from "commontools"; interface State { value: Cell; name?: Cell; } -const myHandler = handler(true as const satisfies JSONSchema, { +const myHandler = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -17,7 +17,7 @@ const myHandler = handler(true as const satisfies JSONSchema, { } }, required: ["value"] -} as const satisfies JSONSchema, (_, state: State) => { +} as const satisfies __ctHelpers.JSONSchema, (_, state: State) => { state.value.set(state.value.get() + 1); }); export default recipe({ type: "object", properties: { value: { type: "number" }, name: { type: "string" } } }, (state) => { @@ -30,3 +30,4 @@ export default recipe({ type: "object", properties: { value: { type: "number" }, onClick3: myHandler(state), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/recipe-array-map.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/recipe-array-map.expected.tsx index c386b6477..48671121f 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/recipe-array-map.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/recipe-array-map.expected.tsx @@ -1,6 +1,6 @@ -/// -import { Cell, derive, h, handler, NAME, recipe, str, UI, JSONSchema } from "commontools"; -const adder = handler(true as const satisfies JSONSchema, { +import * as __ctHelpers from "commontools"; +import { Cell, derive, h, handler, NAME, recipe, str, UI } from "commontools"; +const adder = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { values: { @@ -12,7 +12,7 @@ const adder = handler(true as const satisfies JSONSchema, { } }, required: ["values"] -} as const satisfies JSONSchema, (_, state: { +} as const satisfies __ctHelpers.JSONSchema, (_, state: { values: Cell; }) => { state.values.push(Math.random().toString(36).substring(2, 15)); @@ -28,11 +28,11 @@ export default recipe({ } }, required: ["values"] -} as const satisfies JSONSchema, ({ values }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ values }) => { derive({ type: "array", items: true - } as const satisfies JSONSchema, true as const satisfies JSONSchema, values, (values) => { + } as const satisfies __ctHelpers.JSONSchema, true as const satisfies __ctHelpers.JSONSchema, values, (values) => { console.log("values#", values?.length); }); return { @@ -48,3 +48,4 @@ export default recipe({ values, }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-builders.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-builders.expected.tsx index 418411de5..3faa5a183 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-builders.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-builders.expected.tsx @@ -1,5 +1,5 @@ -/// -import { handler, recipe, UI, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler, recipe, UI } from "commontools"; type TodoState = { items: string[]; }; @@ -14,7 +14,7 @@ const addTodo = handler({ } }, required: ["add"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { items: { @@ -25,7 +25,7 @@ const addTodo = handler({ } }, required: ["items"] -} as const satisfies JSONSchema, (event, state) => { +} as const satisfies __ctHelpers.JSONSchema, (event, state) => { state.items.push(event.add); }); export default recipe({ @@ -39,7 +39,7 @@ export default recipe({ } }, required: ["items"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-alias.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-alias.expected.tsx index 62319af70..3ff30f9c8 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-alias.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-alias.expected.tsx @@ -1,5 +1,5 @@ -/// -import { derive as deriveAlias, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { derive as deriveAlias } from "commontools"; type AliasInput = { text: string; }; @@ -15,7 +15,7 @@ export const textLength = deriveAlias({ } }, required: ["text"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { length: { @@ -23,6 +23,7 @@ export const textLength = deriveAlias({ } }, required: ["length"] -} as const satisfies JSONSchema, state, (value) => ({ +} as const satisfies __ctHelpers.JSONSchema, state, (value) => ({ length: value.text.length, })); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-inside-jsx.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-inside-jsx.expected.tsx index 1ba446c0a..c4f71326b 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-inside-jsx.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-inside-jsx.expected.tsx @@ -1,10 +1,11 @@ -/// -import { derive, h, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { derive, h } from "commontools"; declare const value: number; export const result = (
{derive({ type: "number" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, value, (v) => v * 2)} -
); \ No newline at end of file +} as const satisfies __ctHelpers.JSONSchema, value, (v) => v * 2)} +
); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-multiple-returns.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-multiple-returns.expected.tsx index d9741173a..6e7b111e9 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-multiple-returns.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-multiple-returns.expected.tsx @@ -1,14 +1,15 @@ -/// -import { derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { derive } from "commontools"; declare const flag: boolean; // Function with multiple return statements - should infer string | number export const multiReturn = derive({ type: "boolean" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { enum: ["hello", 42] -} as const satisfies JSONSchema, flag, (value) => { +} as const satisfies __ctHelpers.JSONSchema, flag, (value) => { if (value) { return "hello"; } return 42; -}); \ No newline at end of file +}); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-untyped.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-untyped.expected.tsx index 94131ed77..0c9209970 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-untyped.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive-untyped.expected.tsx @@ -1,8 +1,9 @@ -/// -import { derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { derive } from "commontools"; declare const total: number; export const doubled = derive({ type: "number" -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, total, (value) => value * 2); +} as const satisfies __ctHelpers.JSONSchema, total, (value) => value * 2); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive.expected.tsx index ddf1598cd..c496d2d24 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-derive.expected.tsx @@ -1,5 +1,5 @@ -/// -import { derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { derive } from "commontools"; type DeriveInput = { count: number; }; @@ -15,7 +15,7 @@ export const doubledValue = derive({ } }, required: ["count"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { doubled: { @@ -23,6 +23,7 @@ export const doubledValue = derive({ } }, required: ["doubled"] -} as const satisfies JSONSchema, source, (input) => ({ +} as const satisfies __ctHelpers.JSONSchema, source, (input) => ({ doubled: input.count * 2, })); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-both-inline.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-both-inline.expected.tsx index caf90bb30..4bef75da6 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-both-inline.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-both-inline.expected.tsx @@ -1,5 +1,5 @@ -/// -import { handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler } from "commontools"; interface IncrementEvent { amount: number; } @@ -15,7 +15,7 @@ export const incrementer = handler({ } }, required: ["amount"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { count: { @@ -23,6 +23,7 @@ export const incrementer = handler({ } }, required: ["count"] -} as const satisfies JSONSchema, (event: IncrementEvent, state: CounterState) => { +} as const satisfies __ctHelpers.JSONSchema, (event: IncrementEvent, state: CounterState) => { state.count += event.amount; -}); \ No newline at end of file +}); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-event-only.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-event-only.expected.tsx index aff078af6..eeaa64c80 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-event-only.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-event-only.expected.tsx @@ -1,5 +1,5 @@ -/// -import { handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler } from "commontools"; interface IncrementEvent { amount: number; } @@ -12,6 +12,7 @@ export const incrementer = handler({ } }, required: ["amount"] -} as const satisfies JSONSchema, true as const satisfies JSONSchema, (event: IncrementEvent, state) => { +} as const satisfies __ctHelpers.JSONSchema, true as const satisfies __ctHelpers.JSONSchema, (event: IncrementEvent, state) => { console.log("increment by", event.amount); -}); \ No newline at end of file +}); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-inside-jsx.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-inside-jsx.expected.tsx index 865689f65..43c65c9f4 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-inside-jsx.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-inside-jsx.expected.tsx @@ -1,5 +1,5 @@ -/// -import { handler, h, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler, h } from "commontools"; interface ClickEvent { x: number; y: number; @@ -23,7 +23,7 @@ export const result = (
} }, required: ["x", "y"] - } as const satisfies JSONSchema, { + } as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { clicks: { @@ -43,8 +43,9 @@ export const result = (
} }, required: ["clicks", "lastPosition"] - } as const satisfies JSONSchema, (event: ClickEvent, state: AppState) => ({ + } as const satisfies __ctHelpers.JSONSchema, (event: ClickEvent, state: AppState) => ({ clicks: state.clicks + 1, lastPosition: { x: event.x, y: event.y }, }))}
); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-no-annotations.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-no-annotations.expected.tsx index afaaa72f1..139a4b7cc 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-no-annotations.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-handler-no-annotations.expected.tsx @@ -1,6 +1,7 @@ -/// -import { handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler } from "commontools"; // No type annotations at all - should generate unknown schemas -export const genericHandler = handler(true as const satisfies JSONSchema, true as const satisfies JSONSchema, (event, state) => { +export const genericHandler = handler(true as const satisfies __ctHelpers.JSONSchema, true as const satisfies __ctHelpers.JSONSchema, (event, state) => { console.log("event:", event, "state:", state); -}); \ No newline at end of file +}); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-cell-array.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-cell-array.expected.tsx index f865367b2..382d7446f 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-cell-array.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-cell-array.expected.tsx @@ -1,5 +1,5 @@ -/// -import { lift, Cell, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { lift, Cell } from "commontools"; interface CharmEntry { id: string; name: string; @@ -33,7 +33,7 @@ const logCharmsList = lift({ required: ["id", "name"] } } -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "array", items: { @@ -54,8 +54,9 @@ const logCharmsList = lift({ required: ["id", "name"] } } -} as const satisfies JSONSchema, ({ charmsList }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ charmsList }) => { console.log("logCharmsList: ", charmsList.get()); return charmsList; }); -export default logCharmsList; \ No newline at end of file +export default logCharmsList; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-inside-jsx.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-inside-jsx.expected.tsx index 2a6b0a404..0e89bbe8d 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-inside-jsx.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-inside-jsx.expected.tsx @@ -1,5 +1,5 @@ -/// -import { lift, h, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { lift, h } from "commontools"; interface Person { name: string; age: number; @@ -21,7 +21,7 @@ export const result = (
} }, required: ["name", "age"] - } as const satisfies JSONSchema, { + } as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { name: { @@ -32,8 +32,9 @@ export const result = (
} }, required: ["name", "birthYear"] - } as const satisfies JSONSchema, (person: Person): PersonWithYear => ({ + } as const satisfies __ctHelpers.JSONSchema, (person: Person): PersonWithYear => ({ name: person.name, birthYear: currentYear - person.age, }))}
); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-no-generics.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-no-generics.expected.tsx index ce98538cf..d950a5500 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-no-generics.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-no-generics.expected.tsx @@ -1,5 +1,5 @@ -/// -import { lift, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { lift } from "commontools"; type LiftArgs = { value: number; }; @@ -14,7 +14,7 @@ export const doubleValue = lift({ } }, required: ["value"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { doubled: { @@ -22,6 +22,7 @@ export const doubleValue = lift({ } }, required: ["doubled"] -} as const satisfies JSONSchema, (args: LiftArgs): LiftResult => ({ +} as const satisfies __ctHelpers.JSONSchema, (args: LiftArgs): LiftResult => ({ doubled: args.value * 2, })); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-untyped.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-untyped.expected.tsx index c1ba73291..fc1c6acdc 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-untyped.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift-untyped.expected.tsx @@ -1,5 +1,6 @@ -/// -import { lift, JSONSchema } from "commontools"; -export const doubleValue = lift(true as const satisfies JSONSchema, { +import * as __ctHelpers from "commontools"; +import { lift } from "commontools"; +export const doubleValue = lift(true as const satisfies __ctHelpers.JSONSchema, { type: "number" -} as const satisfies JSONSchema, (value) => value * 2); +} as const satisfies __ctHelpers.JSONSchema, (value) => value * 2); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift.expected.tsx index 46411f0bf..4e3c1521b 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/schema-generation-lift.expected.tsx @@ -1,5 +1,5 @@ -/// -import { lift, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { lift } from "commontools"; type LiftArgs = { value: number; }; @@ -14,7 +14,7 @@ export const doubleValue = lift({ } }, required: ["value"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { doubled: { @@ -22,6 +22,7 @@ export const doubleValue = lift({ } }, required: ["doubled"] -} as const satisfies JSONSchema, ({ value }) => ({ +} as const satisfies __ctHelpers.JSONSchema, ({ value }) => ({ doubled: value * 2, })); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/ast-transform/ternary_derive.expected.tsx b/packages/ts-transformers/test/fixtures/ast-transform/ternary_derive.expected.tsx index 8fbb795c9..7aae15bd5 100644 --- a/packages/ts-transformers/test/fixtures/ast-transform/ternary_derive.expected.tsx +++ b/packages/ts-transformers/test/fixtures/ast-transform/ternary_derive.expected.tsx @@ -1,5 +1,5 @@ -/// -import { Cell, Default, derive, h, handler, NAME, Opaque, OpaqueRef, recipe, str, UI, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, Default, derive, h, handler, NAME, Opaque, OpaqueRef, recipe, str, UI, } from "commontools"; interface RecipeState { value: Default; } @@ -12,11 +12,12 @@ export default recipe({ } }, required: ["value"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [NAME]: "test ternary with derive", [UI]: (
- {ifElse(derive(state.value, _v1 => _v1 + 1), derive(state.value, _v1 => _v1 + 2), "undefined")} + {__ctHelpers.ifElse(__ctHelpers.derive(state.value, _v1 => _v1 + 1), __ctHelpers.derive(state.value, _v1 => _v1 + 2), "undefined")}
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/array-cell-remove-intersection.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/array-cell-remove-intersection.expected.ts index af31b6871..2a7ef8f06 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/array-cell-remove-intersection.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/array-cell-remove-intersection.expected.ts @@ -1,12 +1,12 @@ -/// -import { handler, Cell, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler, Cell } from "commontools"; interface Item { text: string; } interface ListState { items: Cell; } -const removeItem = handler(true as const satisfies JSONSchema, { +const removeItem = handler(true as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { @@ -33,7 +33,7 @@ const removeItem = handler(true as const satisfies JSONSchema, { required: ["text"] } } -} as const satisfies JSONSchema, (_, { items, index }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { items, index }) => { const next = items.get().slice(); if (index >= 0 && index < next.length) next.splice(index, 1); @@ -43,7 +43,7 @@ const removeItem = handler(true as const satisfies JSONSchema, { type ListStateWithIndex = ListState & { index: number; }; -const removeItemAlias = handler(true as const satisfies JSONSchema, { +const removeItemAlias = handler(true as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { @@ -70,10 +70,11 @@ const removeItemAlias = handler(true as const satisfies JSONSchema, { required: ["text"] } } -} as const satisfies JSONSchema, (_, { items, index }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { items, index }) => { const next = items.get().slice(); if (index >= 0 && index < next.length) next.splice(index, 1); items.set(next); }); export { removeItem, removeItemAlias }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/complex-nested-types.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/complex-nested-types.expected.ts index 446c75ec2..f8a46bb92 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/complex-nested-types.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/complex-nested-types.expected.ts @@ -1,5 +1,5 @@ -/// -import { handler, Cell, recipe, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler, Cell, recipe } from "commontools"; // Updated 2025-09-03: String literal unions now generate correct JSON Schema // (enum instead of array) due to schema-generator UnionFormatter improvements interface UserEvent { @@ -42,7 +42,7 @@ const userHandler = handler({ } }, required: ["user", "action"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { users: { @@ -74,7 +74,7 @@ const userHandler = handler({ } }, required: ["users", "lastAction", "count"] -} as const satisfies JSONSchema, (event, state) => { +} as const satisfies __ctHelpers.JSONSchema, (event, state) => { if (event.action === "create") { state.users.push({ id: Date.now().toString(), @@ -102,7 +102,7 @@ const updateTags = handler({ } }, required: ["detail"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { tags: { @@ -114,10 +114,11 @@ const updateTags = handler({ } }, required: ["tags"] -} as const satisfies JSONSchema, ({ detail }, state) => { +} as const satisfies __ctHelpers.JSONSchema, ({ detail }, state) => { state.tags.set(detail?.tags ?? []); }); export { userHandler }; export default recipe("complex-nested-types test", () => { return { userHandler }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/date-and-map-types.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/date-and-map-types.expected.ts index d85114c98..9a97e6e9a 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/date-and-map-types.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/date-and-map-types.expected.ts @@ -1,5 +1,5 @@ -/// -import { handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler } from "commontools"; interface TimedEvent { timestamp: Date; data: Map; @@ -32,7 +32,7 @@ const timedHandler = handler({ required: ["size"] } } -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { @@ -56,10 +56,11 @@ const timedHandler = handler({ required: ["size"] } } -} as const satisfies JSONSchema, (event, state) => { +} as const satisfies __ctHelpers.JSONSchema, (event, state) => { state.lastUpdate = event.timestamp; event.data.forEach((value, key) => { state.history.set(key, new Date()); }); }); export { timedHandler }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/preserve-explicit-schemas.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/preserve-explicit-schemas.expected.ts index 68120dd47..bd71b0165 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/preserve-explicit-schemas.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/preserve-explicit-schemas.expected.ts @@ -1,4 +1,4 @@ -/// +import * as __ctHelpers from "commontools"; import { handler } from "commontools"; const eventSchema = { type: "object", @@ -16,3 +16,4 @@ const logHandler = handler(eventSchema, stateSchema, (event, state) => { state.log.push(event.message); }); export { logHandler }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/simple-handler.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/simple-handler.expected.ts index 29a326c2d..2cc088b8b 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/simple-handler.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/simple-handler.expected.ts @@ -1,5 +1,5 @@ -/// -import { handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler } from "commontools"; interface CounterEvent { increment: number; } @@ -14,7 +14,7 @@ const myHandler = handler({ } }, required: ["increment"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -22,7 +22,8 @@ const myHandler = handler({ } }, required: ["value"] -} as const satisfies JSONSchema, (event, state) => { +} as const satisfies __ctHelpers.JSONSchema, (event, state) => { state.value = state.value + event.increment; }); export { myHandler }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/handler-schema/unsupported-intersection-index.expected.ts b/packages/ts-transformers/test/fixtures/handler-schema/unsupported-intersection-index.expected.ts index 3df71c58a..4fde3bbd8 100644 --- a/packages/ts-transformers/test/fixtures/handler-schema/unsupported-intersection-index.expected.ts +++ b/packages/ts-transformers/test/fixtures/handler-schema/unsupported-intersection-index.expected.ts @@ -1,5 +1,5 @@ -/// -import { handler, Cell, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { handler, Cell } from "commontools"; interface Item { text: string; } @@ -10,12 +10,13 @@ interface ListState { type Indexed = { [k: string]: unknown; }; -const removeItem = handler(true as const satisfies JSONSchema, { +const removeItem = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", additionalProperties: true, $comment: "Unsupported intersection pattern: index signature on constituent" -} as const satisfies JSONSchema, (_, { items }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { items }) => { // noop items.get(); }); export { removeItem }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.expected.tsx index b03dcac07..2f59bc9b9 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.expected.tsx @@ -1,5 +1,5 @@ -/// -import { cell, derive, h, recipe, UI, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { cell, h, recipe, UI } from "commontools"; interface Problem { price: number; discount: number; @@ -19,12 +19,13 @@ export default recipe({ } }, required: ["price", "discount", "tax"] -} as const satisfies JSONSchema, ({ price, discount, tax }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ price, discount, tax }) => { return { [UI]: (

Price: {price}

-

Discount: {derive({ price, discount }, ({ price: price, discount: discount }) => price - discount)}

-

With tax: {derive({ price, discount, tax }, ({ price: price, discount: discount, tax: tax }) => (price - discount) * (1 + tax))}

+

Discount: {__ctHelpers.derive({ price, discount }, ({ price: price, discount: discount }) => price - discount)}

+

With tax: {__ctHelpers.derive({ price, discount, tax }, ({ price: price, discount: discount, tax: tax }) => (price - discount) * (1 + tax))}

) }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.input.tsx index 708f596c4..f29e6f3f6 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/complex-expressions.input.tsx @@ -1,5 +1,5 @@ /// -import { cell, derive, h, recipe, UI } from "commontools"; +import { cell, h, recipe, UI } from "commontools"; interface Problem { price: number; diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.expected.tsx index b9b51089b..791089a67 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI, ifElse } from "commontools"; interface State { matrix: number[][]; row: number; @@ -106,67 +106,67 @@ export default recipe({ } }, required: ["matrix", "row", "col", "items", "arr", "a", "b", "indices", "nested", "users", "selectedUser", "selectedScore"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Nested Element Access

{/* Double indexing into matrix */} -

Matrix value: {derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3])}

+

Matrix value: {__ctHelpers.derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3])}

{/* Triple nested access */} -

Deep nested: {derive({ state_nested_arrays: state.nested.arrays, state_nested_index: state.nested.index, state_row: state.row }, ({ state_nested_arrays: _v1, state_nested_index: _v2, state_row: _v3 }) => _v1[_v2][_v3])}

+

Deep nested: {__ctHelpers.derive({ state_nested_arrays: state.nested.arrays, state_nested_index: state.nested.index, state_row: state.row }, ({ state_nested_arrays: _v1, state_nested_index: _v2, state_row: _v3 }) => _v1[_v2][_v3])}

Multiple References to Same Array

{/* Same array accessed multiple times with different indices */} -

First and last: {state.items[0]} and {derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

+

First and last: {state.items[0]} and {__ctHelpers.derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

{/* Array used in computation and access */} -

Sum of ends: {derive({ state_arr: state.arr, state_arr_length: state.arr.length }, ({ state_arr: _v1, state_arr_length: _v2 }) => _v1[0] + _v1[_v2 - 1])}

+

Sum of ends: {__ctHelpers.derive({ state_arr: state.arr, state_arr_length: state.arr.length }, ({ state_arr: _v1, state_arr_length: _v2 }) => _v1[0] + _v1[_v2 - 1])}

Computed Indices

{/* Index from multiple state values */} -

Computed index: {derive({ state_arr: state.arr, state_a: state.a, state_b: state.b }, ({ state_arr: _v1, state_a: _v2, state_b: _v3 }) => _v1[_v2 + _v3])}

+

Computed index: {__ctHelpers.derive({ state_arr: state.arr, state_a: state.a, state_b: state.b }, ({ state_arr: _v1, state_a: _v2, state_b: _v3 }) => _v1[_v2 + _v3])}

{/* Index from computation involving array */} -

Modulo index: {derive({ state_items: state.items, state_row: state.row, state_items_length: state.items.length }, ({ state_items: _v1, state_row: _v2, state_items_length: _v3 }) => _v1[_v2 % _v3])}

+

Modulo index: {__ctHelpers.derive({ state_items: state.items, state_row: state.row, state_items_length: state.items.length }, ({ state_items: _v1, state_row: _v2, state_items_length: _v3 }) => _v1[_v2 % _v3])}

{/* Complex index expression */} -

Complex: {derive({ state_arr: state.arr, state_a: state.a, state_arr_length: state.arr.length }, ({ state_arr: _v1, state_a: _v2, state_arr_length: _v3 }) => _v1[Math.min(_v2 * 2, _v3 - 1)])}

+

Complex: {__ctHelpers.derive({ state_arr: state.arr, state_a: state.a, state_arr_length: state.arr.length }, ({ state_arr: _v1, state_a: _v2, state_arr_length: _v3 }) => _v1[Math.min(_v2 * 2, _v3 - 1)])}

Chained Element Access

{/* Element access returning array, then accessing that */} -

User score: {derive({ state_users: state.users, state_selectedUser: state.selectedUser, state_selectedScore: state.selectedScore }, ({ state_users: _v1, state_selectedUser: _v2, state_selectedScore: _v3 }) => _v1[_v2].scores[_v3])}

+

User score: {__ctHelpers.derive({ state_users: state.users, state_selectedUser: state.selectedUser, state_selectedScore: state.selectedScore }, ({ state_users: _v1, state_selectedUser: _v2, state_selectedScore: _v3 }) => _v1[_v2].scores[_v3])}

{/* Using one array element as index for another */} -

Indirect: {derive({ state_items: state.items, state_indices: state.indices }, ({ state_items: _v1, state_indices: _v2 }) => _v1[_v2[0]])}

+

Indirect: {__ctHelpers.derive({ state_items: state.items, state_indices: state.indices }, ({ state_items: _v1, state_indices: _v2 }) => _v1[_v2[0]])}

{/* Array element used as index for same array */} -

Self reference: {derive(state.arr, _v1 => _v1[_v1[0]])}

+

Self reference: {__ctHelpers.derive(state.arr, _v1 => _v1[_v1[0]])}

Mixed Property and Element Access

{/* Property access followed by element access with computed index */} -

Mixed: {derive({ state_nested_arrays: state.nested.arrays, state_nested_index: state.nested.index }, ({ state_nested_arrays: _v1, state_nested_index: _v2 }) => _v1[_v2].length)}

+

Mixed: {__ctHelpers.derive({ state_nested_arrays: state.nested.arrays, state_nested_index: state.nested.index }, ({ state_nested_arrays: _v1, state_nested_index: _v2 }) => _v1[_v2].length)}

{/* Element access followed by property access */} -

User name length: {derive({ state_users: state.users, state_selectedUser: state.selectedUser }, ({ state_users: _v1, state_selectedUser: _v2 }) => _v1[_v2].name.length)}

+

User name length: {__ctHelpers.derive({ state_users: state.users, state_selectedUser: state.selectedUser }, ({ state_users: _v1, state_selectedUser: _v2 }) => _v1[_v2].name.length)}

Element Access in Conditions

{/* Element access in ternary */} -

Conditional: {ifElse(derive({ state_arr: state.arr, state_a: state.a }, ({ state_arr: _v1, state_a: _v2 }) => _v1[_v2] > 10), derive({ state_items: state.items, state_b: state.b }, ({ state_items: _v1, state_b: _v2 }) => _v1[_v2]), state.items[0])}

+

Conditional: {__ctHelpers.ifElse(__ctHelpers.derive({ state_arr: state.arr, state_a: state.a }, ({ state_arr: _v1, state_a: _v2 }) => _v1[_v2] > 10), __ctHelpers.derive({ state_items: state.items, state_b: state.b }, ({ state_items: _v1, state_b: _v2 }) => _v1[_v2]), state.items[0])}

{/* Element access in boolean expression */} -

Has value: {ifElse(derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3] > 0), "positive", "non-positive")}

+

Has value: {ifElse(__ctHelpers.derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3] > 0), "positive", "non-positive")}

Element Access with Operators

{/* Element access with arithmetic */} -

Product: {derive({ state_arr: state.arr, state_a: state.a, state_b: state.b }, ({ state_arr: _v1, state_a: _v2, state_b: _v3 }) => _v1[_v2] * _v1[_v3])}

+

Product: {__ctHelpers.derive({ state_arr: state.arr, state_a: state.a, state_b: state.b }, ({ state_arr: _v1, state_a: _v2, state_b: _v3 }) => _v1[_v2] * _v1[_v3])}

{/* Element access with string concatenation */} -

Concat: {derive({ state_items: state.items, state_indices: state.indices }, ({ state_items: _v1, state_indices: _v2 }) => _v1[0] + " - " + _v1[_v2[0]])}

+

Concat: {__ctHelpers.derive({ state_items: state.items, state_indices: state.indices }, ({ state_items: _v1, state_indices: _v2 }) => _v1[0] + " - " + _v1[_v2[0]])}

{/* Multiple element accesses in single expression */} -

Sum: {derive(state.arr, _v1 => _v1[0] + _v1[1] + _v1[2])}

+

Sum: {__ctHelpers.derive(state.arr, _v1 => _v1[0] + _v1[1] + _v1[2])}

), }; }); - +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.input.tsx index f5f3b0a35..29b658813 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-complex.input.tsx @@ -1,5 +1,5 @@ /// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import { h, recipe, UI, ifElse } from "commontools"; interface State { matrix: number[][]; diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.expected.tsx index f31801200..0ebbc9454 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { items: string[]; index: number; @@ -36,18 +36,19 @@ export default recipe({ } }, required: ["items", "index", "matrix", "row", "col"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Dynamic Element Access

{/* Basic dynamic index */} -

Item: {derive({ state_items: state.items, state_index: state.index }, ({ state_items: _v1, state_index: _v2 }) => _v1[_v2])}

+

Item: {__ctHelpers.derive({ state_items: state.items, state_index: state.index }, ({ state_items: _v1, state_index: _v2 }) => _v1[_v2])}

{/* Computed index */} -

Last: {derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

+

Last: {__ctHelpers.derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

{/* Double indexing */} -

Matrix: {derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3])}

+

Matrix: {__ctHelpers.derive({ state_matrix: state.matrix, state_row: state.row, state_col: state.col }, ({ state_matrix: _v1, state_row: _v2, state_col: _v3 }) => _v1[_v2][_v3])}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.input.tsx index 14c23b21a..23efcbf5c 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/element-access-simple.input.tsx @@ -1,5 +1,5 @@ /// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { items: string[]; diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-arithmetic-operations.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-arithmetic-operations.expected.tsx index e6639be7f..de60ec183 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-arithmetic-operations.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-arithmetic-operations.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { count: number; price: number; @@ -23,25 +23,26 @@ export default recipe({ } }, required: ["count", "price", "discount", "quantity"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Basic Arithmetic

-

Count + 1: {derive(state.count, _v1 => _v1 + 1)}

-

Count - 1: {derive(state.count, _v1 => _v1 - 1)}

-

Count * 2: {derive(state.count, _v1 => _v1 * 2)}

-

Price / 2: {derive(state.price, _v1 => _v1 / 2)}

-

Count % 3: {derive(state.count, _v1 => _v1 % 3)}

+

Count + 1: {__ctHelpers.derive(state.count, _v1 => _v1 + 1)}

+

Count - 1: {__ctHelpers.derive(state.count, _v1 => _v1 - 1)}

+

Count * 2: {__ctHelpers.derive(state.count, _v1 => _v1 * 2)}

+

Price / 2: {__ctHelpers.derive(state.price, _v1 => _v1 / 2)}

+

Count % 3: {__ctHelpers.derive(state.count, _v1 => _v1 % 3)}

Complex Expressions

-

Discounted Price: {derive({ state_price: state.price, state_discount: state.discount }, ({ state_price: _v1, state_discount: _v2 }) => _v1 - (_v1 * _v2))}

-

Total: {derive({ state_price: state.price, state_quantity: state.quantity }, ({ state_price: _v1, state_quantity: _v2 }) => _v1 * _v2)}

-

With Tax (8%): {derive({ state_price: state.price, state_quantity: state.quantity }, ({ state_price: _v1, state_quantity: _v2 }) => (_v1 * _v2) * 1.08)}

-

Complex: {derive({ state_count: state.count, state_quantity: state.quantity, state_price: state.price, state_discount: state.discount }, ({ state_count: _v1, state_quantity: _v2, state_price: _v3, state_discount: _v4 }) => (_v1 + _v2) * _v3 - (_v3 * _v4))}

+

Discounted Price: {__ctHelpers.derive({ state_price: state.price, state_discount: state.discount }, ({ state_price: _v1, state_discount: _v2 }) => _v1 - (_v1 * _v2))}

+

Total: {__ctHelpers.derive({ state_price: state.price, state_quantity: state.quantity }, ({ state_price: _v1, state_quantity: _v2 }) => _v1 * _v2)}

+

With Tax (8%): {__ctHelpers.derive({ state_price: state.price, state_quantity: state.quantity }, ({ state_price: _v1, state_quantity: _v2 }) => (_v1 * _v2) * 1.08)}

+

Complex: {__ctHelpers.derive({ state_count: state.count, state_quantity: state.quantity, state_price: state.price, state_discount: state.discount }, ({ state_count: _v1, state_quantity: _v2, state_price: _v3, state_discount: _v4 }) => (_v1 + _v2) * _v3 - (_v3 * _v4))}

Multiple Same Ref

-

Count³: {derive(state.count, _v1 => _v1 * _v1 * _v1)}

-

Price Range: ${derive(state.price, _v1 => _v1 - 10)} - ${derive(state.price, _v1 => _v1 + 10)}

+

Count³: {__ctHelpers.derive(state.count, _v1 => _v1 * _v1 * _v1)}

+

Price Range: ${__ctHelpers.derive(state.price, _v1 => _v1 - 10)} - ${__ctHelpers.derive(state.price, _v1 => _v1 + 10)}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-complex-mixed.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-complex-mixed.expected.tsx index 5aa4c70df..a78130b6c 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-complex-mixed.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-complex-mixed.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface Item { id: number; name: string; @@ -53,40 +53,41 @@ export default recipe({ required: ["id", "name", "price", "active"] } } -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Array Operations

Total items: {state.items.length}

-

Filtered count: {derive({ state_items: state.items, state_filter: state.filter }, ({ state_items: _v1, state_filter: _v2 }) => _v1.filter(i => i.name.includes(_v2)).length)}

+

Filtered count: {__ctHelpers.derive({ state_items: state.items, state_filter: state.filter }, ({ state_items: _v1, state_filter: _v2 }) => _v1.filter(i => i.name.includes(_v2)).length)}

Array with Complex Expressions

    {state.items.map(item => (
  • {item.name} - Original: ${item.price} - - Discounted: ${derive({ item_price: item.price, state_discount: state.discount }, ({ item_price: _v1, state_discount: _v2 }) => (_v1 * (1 - _v2)).toFixed(2))} - - With tax: ${derive({ item_price: item.price, state_discount: state.discount, state_taxRate: state.taxRate }, ({ item_price: _v1, state_discount: _v2, state_taxRate: _v3 }) => (_v1 * (1 - _v2) * (1 + _v3)).toFixed(2))} + - Discounted: ${__ctHelpers.derive({ item_price: item.price, state_discount: state.discount }, ({ item_price: _v1, state_discount: _v2 }) => (_v1 * (1 - _v2)).toFixed(2))} + - With tax: ${__ctHelpers.derive({ item_price: item.price, state_discount: state.discount, state_taxRate: state.taxRate }, ({ item_price: _v1, state_discount: _v2, state_taxRate: _v3 }) => (_v1 * (1 - _v2) * (1 + _v3)).toFixed(2))}
  • ))}

Array Methods

Item count: {state.items.length}

-

Active items: {derive(state.items, _v1 => _v1.filter(i => i.active).length)}

+

Active items: {__ctHelpers.derive(state.items, _v1 => _v1.filter(i => i.active).length)}

Simple Operations

-

Discount percent: {derive(state.discount, _v1 => _v1 * 100)}%

-

Tax percent: {derive(state.taxRate, _v1 => _v1 * 100)}%

+

Discount percent: {__ctHelpers.derive(state.discount, _v1 => _v1 * 100)}%

+

Tax percent: {__ctHelpers.derive(state.taxRate, _v1 => _v1 * 100)}%

Array Predicates

-

All active: {ifElse(derive(state.items, _v1 => _v1.every(i => i.active)), "Yes", "No")}

-

Any active: {ifElse(derive(state.items, _v1 => _v1.some(i => i.active)), "Yes", "No")}

-

Has expensive (gt 100): {ifElse(derive(state.items, _v1 => _v1.some(i => i.price > 100)), "Yes", "No")}

+

All active: {__ctHelpers.ifElse(__ctHelpers.derive(state.items, _v1 => _v1.every(i => i.active)), "Yes", "No")}

+

Any active: {__ctHelpers.ifElse(__ctHelpers.derive(state.items, _v1 => _v1.some(i => i.active)), "Yes", "No")}

+

Has expensive (gt 100): {__ctHelpers.ifElse(__ctHelpers.derive(state.items, _v1 => _v1.some(i => i.price > 100)), "Yes", "No")}

Object Operations

-
_v1 > 0)} data-discount={state.discount}> +
_v1 > 0)} data-discount={state.discount}> Object attributes
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-conditional-rendering.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-conditional-rendering.expected.tsx index 28ddfb3ad..a0adf5907 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-conditional-rendering.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-conditional-rendering.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, ifElse, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI, ifElse } from "commontools"; interface State { isActive: boolean; count: number; @@ -31,33 +31,34 @@ export default recipe({ } }, required: ["isActive", "count", "userType", "score", "hasPermission", "isPremium"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Basic Ternary

- {ifElse(state.isActive, "Active", "Inactive")} - {ifElse(state.hasPermission, "Authorized", "Denied")} + {__ctHelpers.ifElse(state.isActive, "Active", "Inactive")} + {__ctHelpers.ifElse(state.hasPermission, "Authorized", "Denied")}

Ternary with Comparisons

- {ifElse(derive(state.count, _v1 => _v1 > 10), "High", "Low")} - {ifElse(derive(state.score, _v1 => _v1 >= 90), "A", derive(state.score, _v1 => _v1 >= 80 ? "B" : "C"))} - {ifElse(derive(state.count, _v1 => _v1 === 0), "Empty", derive(state.count, _v1 => _v1 === 1 ? "Single" : "Multiple"))} + {__ctHelpers.ifElse(__ctHelpers.derive(state.count, _v1 => _v1 > 10), "High", "Low")} + {__ctHelpers.ifElse(__ctHelpers.derive(state.score, _v1 => _v1 >= 90), "A", __ctHelpers.derive(state.score, _v1 => _v1 >= 80 ? "B" : "C"))} + {__ctHelpers.ifElse(__ctHelpers.derive(state.count, _v1 => _v1 === 0), "Empty", __ctHelpers.derive(state.count, _v1 => _v1 === 1 ? "Single" : "Multiple"))}

Nested Ternary

- {ifElse(state.isActive, derive(state.isPremium, _v1 => (_v1 ? "Premium Active" : "Regular Active")), "Inactive")} - {ifElse(derive(state.userType, _v1 => _v1 === "admin"), "Admin", derive(state.userType, _v1 => _v1 === "user" ? "User" : "Guest"))} + {__ctHelpers.ifElse(state.isActive, __ctHelpers.derive(state.isPremium, _v1 => (_v1 ? "Premium Active" : "Regular Active")), "Inactive")} + {__ctHelpers.ifElse(__ctHelpers.derive(state.userType, _v1 => _v1 === "admin"), "Admin", __ctHelpers.derive(state.userType, _v1 => _v1 === "user" ? "User" : "Guest"))}

Complex Conditions

- {ifElse(derive({ state_isActive: state.isActive, state_hasPermission: state.hasPermission }, ({ state_isActive: _v1, state_hasPermission: _v2 }) => _v1 && _v2), "Full Access", "Limited Access")} - {ifElse(derive(state.count, _v1 => _v1 > 0 && _v1 < 10), "In Range", "Out of Range")} - {ifElse(derive({ state_isPremium: state.isPremium, state_score: state.score }, ({ state_isPremium: _v1, state_score: _v2 }) => _v1 || _v2 > 100), "Premium Features", "Basic Features")} + {__ctHelpers.ifElse(__ctHelpers.derive({ state_isActive: state.isActive, state_hasPermission: state.hasPermission }, ({ state_isActive: _v1, state_hasPermission: _v2 }) => _v1 && _v2), "Full Access", "Limited Access")} + {__ctHelpers.ifElse(__ctHelpers.derive(state.count, _v1 => _v1 > 0 && _v1 < 10), "In Range", "Out of Range")} + {__ctHelpers.ifElse(__ctHelpers.derive({ state_isPremium: state.isPremium, state_score: state.score }, ({ state_isPremium: _v1, state_score: _v2 }) => _v1 || _v2 > 100), "Premium Features", "Basic Features")}

IfElse Component

{ifElse(state.isActive,
User is active with {state.count} items
,
User is inactive
)} - {ifElse(derive(state.count, _v1 => _v1 > 5),
    + {ifElse(__ctHelpers.derive(state.count, _v1 => _v1 > 5),
    • Many items: {state.count}
    ,

    Few items: {state.count}

    )}
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-function-calls.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-function-calls.expected.tsx index 4f804c453..a69f1883c 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-function-calls.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-function-calls.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { a: number; b: number; @@ -38,44 +38,45 @@ export default recipe({ } }, required: ["a", "b", "price", "text", "values", "name", "float"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Math Functions

-

Max: {derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.max(_v1, _v2))}

-

Min: {derive(state.a, _v1 => Math.min(_v1, 10))}

-

Abs: {derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.abs(_v1 - _v2))}

-

Round: {derive(state.price, _v1 => Math.round(_v1))}

-

Floor: {derive(state.price, _v1 => Math.floor(_v1))}

-

Ceiling: {derive(state.price, _v1 => Math.ceil(_v1))}

-

Square root: {derive(state.a, _v1 => Math.sqrt(_v1))}

+

Max: {__ctHelpers.derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.max(_v1, _v2))}

+

Min: {__ctHelpers.derive(state.a, _v1 => Math.min(_v1, 10))}

+

Abs: {__ctHelpers.derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.abs(_v1 - _v2))}

+

Round: {__ctHelpers.derive(state.price, _v1 => Math.round(_v1))}

+

Floor: {__ctHelpers.derive(state.price, _v1 => Math.floor(_v1))}

+

Ceiling: {__ctHelpers.derive(state.price, _v1 => Math.ceil(_v1))}

+

Square root: {__ctHelpers.derive(state.a, _v1 => Math.sqrt(_v1))}

String Methods as Function Calls

-

Uppercase: {derive(state.name, _v1 => _v1.toUpperCase())}

-

Lowercase: {derive(state.name, _v1 => _v1.toLowerCase())}

-

Substring: {derive(state.text, _v1 => _v1.substring(0, 5))}

-

Replace: {derive(state.text, _v1 => _v1.replace("old", "new"))}

-

Includes: {ifElse(derive(state.text, _v1 => _v1.includes("test")), "Yes", "No")}

-

Starts with: {ifElse(derive(state.name, _v1 => _v1.startsWith("A")), "Yes", "No")}

+

Uppercase: {__ctHelpers.derive(state.name, _v1 => _v1.toUpperCase())}

+

Lowercase: {__ctHelpers.derive(state.name, _v1 => _v1.toLowerCase())}

+

Substring: {__ctHelpers.derive(state.text, _v1 => _v1.substring(0, 5))}

+

Replace: {__ctHelpers.derive(state.text, _v1 => _v1.replace("old", "new"))}

+

Includes: {__ctHelpers.ifElse(__ctHelpers.derive(state.text, _v1 => _v1.includes("test")), "Yes", "No")}

+

Starts with: {__ctHelpers.ifElse(__ctHelpers.derive(state.name, _v1 => _v1.startsWith("A")), "Yes", "No")}

Number Methods

-

To Fixed: {derive(state.price, _v1 => _v1.toFixed(2))}

-

To Precision: {derive(state.price, _v1 => _v1.toPrecision(4))}

+

To Fixed: {__ctHelpers.derive(state.price, _v1 => _v1.toFixed(2))}

+

To Precision: {__ctHelpers.derive(state.price, _v1 => _v1.toPrecision(4))}

Parse Functions

-

Parse Int: {derive(state.float, _v1 => parseInt(_v1))}

-

Parse Float: {derive(state.float, _v1 => parseFloat(_v1))}

+

Parse Int: {__ctHelpers.derive(state.float, _v1 => parseInt(_v1))}

+

Parse Float: {__ctHelpers.derive(state.float, _v1 => parseFloat(_v1))}

Array Method Calls

-

Sum: {derive(state.values, _v1 => _v1.reduce((a, b) => a + b, 0))}

-

Max value: {derive(state.values, _v1 => Math.max(..._v1))}

-

Joined: {derive(state.values, _v1 => _v1.join(", "))}

+

Sum: {__ctHelpers.derive(state.values, _v1 => _v1.reduce((a, b) => a + b, 0))}

+

Max value: {__ctHelpers.derive(state.values, _v1 => Math.max(..._v1))}

+

Joined: {__ctHelpers.derive(state.values, _v1 => _v1.join(", "))}

Complex Function Calls

-

Multiple args: {derive(state.a, _v1 => Math.pow(_v1, 2))}

-

Nested calls: {derive(state.a, _v1 => Math.round(Math.sqrt(_v1)))}

-

Chained calls: {derive(state.name, _v1 => _v1.trim().toUpperCase())}

-

With expressions: {derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.max(_v1 + 1, _v2 * 2))}

+

Multiple args: {__ctHelpers.derive(state.a, _v1 => Math.pow(_v1, 2))}

+

Nested calls: {__ctHelpers.derive(state.a, _v1 => Math.round(Math.sqrt(_v1)))}

+

Chained calls: {__ctHelpers.derive(state.name, _v1 => _v1.trim().toUpperCase())}

+

With expressions: {__ctHelpers.derive({ state_a: state.a, state_b: state.b }, ({ state_a: _v1, state_b: _v2 }) => Math.max(_v1 + 1, _v2 * 2))}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-property-access.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-property-access.expected.tsx index 6f561fa8b..1c6a59b66 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-property-access.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-property-access.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, ifElse, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface User { name: string; age: number; @@ -132,50 +132,51 @@ export default recipe({ required: ["name", "age", "active", "profile"] } } -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Basic Property Access

{state.user.name}

Age: {state.user.age}

-

Active: {ifElse(state.user.active, "Yes", "No")}

+

Active: {__ctHelpers.ifElse(state.user.active, "Yes", "No")}

Nested Property Access

Bio: {state.user.profile.bio}

Location: {state.user.profile.location}

Theme: {state.user.profile.settings.theme}

-

Notifications: {ifElse(state.user.profile.settings.notifications, "On", "Off")}

+

Notifications: {__ctHelpers.ifElse(state.user.profile.settings.notifications, "On", "Off")}

Property Access with Operations

-

Age + 1: {derive(state.user.age, _v1 => _v1 + 1)}

+

Age + 1: {__ctHelpers.derive(state.user.age, _v1 => _v1 + 1)}

Name length: {state.user.name.length}

-

Uppercase name: {derive(state.user.name, _v1 => _v1.toUpperCase())}

-

Location includes city: {ifElse(derive(state.user.profile.location, _v1 => _v1.includes("City")), "Yes", "No")}

+

Uppercase name: {__ctHelpers.derive(state.user.name, _v1 => _v1.toUpperCase())}

+

Location includes city: {__ctHelpers.ifElse(__ctHelpers.derive(state.user.profile.location, _v1 => _v1.includes("City")), "Yes", "No")}

Array Element Access

-

Item at index: {derive({ state_items: state.items, state_index: state.index }, ({ state_items: _v1, state_index: _v2 }) => _v1[_v2])}

+

Item at index: {__ctHelpers.derive({ state_items: state.items, state_index: state.index }, ({ state_items: _v1, state_index: _v2 }) => _v1[_v2])}

First item: {state.items[0]}

-

Last item: {derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

-

Number at index: {derive({ state_numbers: state.numbers, state_index: state.index }, ({ state_numbers: _v1, state_index: _v2 }) => _v1[_v2])}

+

Last item: {__ctHelpers.derive({ state_items: state.items, state_items_length: state.items.length }, ({ state_items: _v1, state_items_length: _v2 }) => _v1[_v2 - 1])}

+

Number at index: {__ctHelpers.derive({ state_numbers: state.numbers, state_index: state.index }, ({ state_numbers: _v1, state_index: _v2 }) => _v1[_v2])}

Config Access with Styles

_v1 + "px") + fontSize: __ctHelpers.derive(state.config.theme.fontSize, _v1 => _v1 + "px") }}> Styled text

Theme-aware box

Complex Property Chains

-

{derive({ state_user_name: state.user.name, state_user_profile_location: state.user.profile.location }, ({ state_user_name: _v1, state_user_profile_location: _v2 }) => _v1 + " from " + _v2)}

-

Font size + 2: {derive(state.config.theme.fontSize, _v1 => _v1 + 2)}px

-

Has beta and dark mode: {ifElse(derive({ state_config_features_beta: state.config.features.beta, state_config_features_darkMode: state.config.features.darkMode }, ({ state_config_features_beta: _v1, state_config_features_darkMode: _v2 }) => _v1 && _v2), "Yes", "No")}

+

{__ctHelpers.derive({ state_user_name: state.user.name, state_user_profile_location: state.user.profile.location }, ({ state_user_name: _v1, state_user_profile_location: _v2 }) => _v1 + " from " + _v2)}

+

Font size + 2: {__ctHelpers.derive(state.config.theme.fontSize, _v1 => _v1 + 2)}px

+

Has beta and dark mode: {__ctHelpers.ifElse(__ctHelpers.derive({ state_config_features_beta: state.config.features.beta, state_config_features_darkMode: state.config.features.darkMode }, ({ state_config_features_beta: _v1, state_config_features_darkMode: _v2 }) => _v1 && _v2), "Yes", "No")}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-string-operations.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-string-operations.expected.tsx index 178b01a32..3c6cc9f8a 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-string-operations.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/jsx-string-operations.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { firstName: string; lastName: string; @@ -27,29 +27,30 @@ export default recipe({ } }, required: ["firstName", "lastName", "title", "message", "count"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

String Concatenation

-

{derive({ state_title: state.title, state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_title: _v1, state_firstName: _v2, state_lastName: _v3 }) => _v1 + ": " + _v2 + " " + _v3)}

-

{derive({ state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_firstName: _v1, state_lastName: _v2 }) => _v1 + _v2)}

-

{derive(state.firstName, _v1 => "Hello, " + _v1 + "!")}

+

{__ctHelpers.derive({ state_title: state.title, state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_title: _v1, state_firstName: _v2, state_lastName: _v3 }) => _v1 + ": " + _v2 + " " + _v3)}

+

{__ctHelpers.derive({ state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_firstName: _v1, state_lastName: _v2 }) => _v1 + _v2)}

+

{__ctHelpers.derive(state.firstName, _v1 => "Hello, " + _v1 + "!")}

Template Literals

-

{derive(state.firstName, _v1 => `Welcome, ${_v1}!`)}

-

{derive({ state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_firstName: _v1, state_lastName: _v2 }) => `Full name: ${_v1} ${_v2}`)}

-

{derive({ state_title: state.title, state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_title: _v1, state_firstName: _v2, state_lastName: _v3 }) => `${_v1}: ${_v2} ${_v3}`)}

+

{__ctHelpers.derive(state.firstName, _v1 => `Welcome, ${_v1}!`)}

+

{__ctHelpers.derive({ state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_firstName: _v1, state_lastName: _v2 }) => `Full name: ${_v1} ${_v2}`)}

+

{__ctHelpers.derive({ state_title: state.title, state_firstName: state.firstName, state_lastName: state.lastName }, ({ state_title: _v1, state_firstName: _v2, state_lastName: _v3 }) => `${_v1}: ${_v2} ${_v3}`)}

String Methods

-

Uppercase: {derive(state.firstName, _v1 => _v1.toUpperCase())}

-

Lowercase: {derive(state.title, _v1 => _v1.toLowerCase())}

+

Uppercase: {__ctHelpers.derive(state.firstName, _v1 => _v1.toUpperCase())}

+

Lowercase: {__ctHelpers.derive(state.title, _v1 => _v1.toLowerCase())}

Length: {state.message.length}

-

Substring: {derive(state.message, _v1 => _v1.substring(0, 5))}

+

Substring: {__ctHelpers.derive(state.message, _v1 => _v1.substring(0, 5))}

Mixed String and Number

-

{derive({ state_firstName: state.firstName, state_count: state.count }, ({ state_firstName: _v1, state_count: _v2 }) => _v1 + " has " + _v2 + " items")}

-

{derive({ state_firstName: state.firstName, state_count: state.count }, ({ state_firstName: _v1, state_count: _v2 }) => `${_v1} has ${_v2} items`)}

-

Count as string: {derive(state.count, _v1 => "Count: " + _v1)}

+

{__ctHelpers.derive({ state_firstName: state.firstName, state_count: state.count }, ({ state_firstName: _v1, state_count: _v2 }) => _v1 + " has " + _v2 + " items")}

+

{__ctHelpers.derive({ state_firstName: state.firstName, state_count: state.count }, ({ state_firstName: _v1, state_count: _v2 }) => `${_v1} has ${_v2} items`)}

+

Count as string: {__ctHelpers.derive(state.count, _v1 => "Count: " + _v1)}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx index 8d5672328..58100dd53 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { text: string; searchTerm: string; @@ -102,85 +102,86 @@ export default recipe({ } }, required: ["text", "searchTerm", "items", "start", "end", "threshold", "factor", "names", "prefix", "prices", "discount", "taxRate", "users", "minAge", "words", "separator"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Chained String Methods

{/* Simple chain */} -

Trimmed lower: {derive(state.text, _v1 => _v1.trim().toLowerCase())}

+

Trimmed lower: {__ctHelpers.derive(state.text, _v1 => _v1.trim().toLowerCase())}

{/* Chain with reactive argument */} -

Contains search: {derive({ state_text: state.text, state_searchTerm: state.searchTerm }, ({ state_text: _v1, state_searchTerm: _v2 }) => _v1.toLowerCase().includes(_v2.toLowerCase()))}

+

Contains search: {__ctHelpers.derive({ state_text: state.text, state_searchTerm: state.searchTerm }, ({ state_text: _v1, state_searchTerm: _v2 }) => _v1.toLowerCase().includes(_v2.toLowerCase()))}

{/* Longer chain */} -

Processed: {derive(state.text, _v1 => _v1.trim().toLowerCase().replace("old", "new").toUpperCase())}

+

Processed: {__ctHelpers.derive(state.text, _v1 => _v1.trim().toLowerCase().replace("old", "new").toUpperCase())}

Array Method Chains

{/* Filter then length */} -

Count above threshold: {derive({ state_items: state.items, state_threshold: state.threshold }, ({ state_items: _v1, state_threshold: _v2 }) => _v1.filter(x => x > _v2).length)}

+

Count above threshold: {__ctHelpers.derive({ state_items: state.items, state_threshold: state.threshold }, ({ state_items: _v1, state_threshold: _v2 }) => _v1.filter(x => x > _v2).length)}

{/* Filter then map */}
    - {derive({ state_items: state.items, state_threshold: state.threshold }, ({ state_items: _v1, state_threshold: _v2 }) => _v1.filter(x => x > _v2)).map(x => (
  • Value: {derive({ x, state_factor: state.factor }, ({ x: x, state_factor: _v2 }) => x * _v2)}
  • ))} + {__ctHelpers.derive({ state_items: state.items, state_threshold: state.threshold }, ({ state_items: _v1, state_threshold: _v2 }) => _v1.filter(x => x > _v2)).map(x => (
  • Value: {__ctHelpers.derive({ x, state_factor: state.factor }, ({ x: x, state_factor: _v2 }) => x * _v2)}
  • ))}
{/* Multiple filters */} -

Double filter count: {derive({ state_items: state.items, state_start: state.start, state_end: state.end }, ({ state_items: _v1, state_start: _v2, state_end: _v3 }) => _v1.filter(x => x > _v2).filter(x => x < _v3).length)}

+

Double filter count: {__ctHelpers.derive({ state_items: state.items, state_start: state.start, state_end: state.end }, ({ state_items: _v1, state_start: _v2, state_end: _v3 }) => _v1.filter(x => x > _v2).filter(x => x < _v3).length)}

Methods with Reactive Arguments

{/* Slice with reactive indices */} -

Sliced items: {derive({ state_items: state.items, state_start: state.start, state_end: state.end }, ({ state_items: _v1, state_start: _v2, state_end: _v3 }) => _v1.slice(_v2, _v3).join(", "))}

+

Sliced items: {__ctHelpers.derive({ state_items: state.items, state_start: state.start, state_end: state.end }, ({ state_items: _v1, state_start: _v2, state_end: _v3 }) => _v1.slice(_v2, _v3).join(", "))}

{/* String methods with reactive args */} -

Starts with: {derive({ state_names: state.names, state_prefix: state.prefix }, ({ state_names: _v1, state_prefix: _v2 }) => _v1.filter(n => n.startsWith(_v2)).join(", "))}

+

Starts with: {__ctHelpers.derive({ state_names: state.names, state_prefix: state.prefix }, ({ state_names: _v1, state_prefix: _v2 }) => _v1.filter(n => n.startsWith(_v2)).join(", "))}

{/* Array find with reactive predicate */} -

First match: {derive({ state_names: state.names, state_searchTerm: state.searchTerm }, ({ state_names: _v1, state_searchTerm: _v2 }) => _v1.find(n => n.includes(_v2)))}

+

First match: {__ctHelpers.derive({ state_names: state.names, state_searchTerm: state.searchTerm }, ({ state_names: _v1, state_searchTerm: _v2 }) => _v1.find(n => n.includes(_v2)))}

Complex Method Combinations

{/* Map with chained operations inside */}
    - {state.names.map(name => (
  • {derive(name, name => name.trim().toLowerCase().replace(" ", "-"))}
  • ))} + {state.names.map(name => (
  • {__ctHelpers.derive(name, name => name.trim().toLowerCase().replace(" ", "-"))}
  • ))}
{/* Reduce with reactive accumulator */} -

Total with discount: {derive({ state_prices: state.prices, state_discount: state.discount }, ({ state_prices: _v1, state_discount: _v2 }) => _v1.reduce((sum, price) => sum + price * (1 - _v2), 0))}

+

Total with discount: {__ctHelpers.derive({ state_prices: state.prices, state_discount: state.discount }, ({ state_prices: _v1, state_discount: _v2 }) => _v1.reduce((sum, price) => sum + price * (1 - _v2), 0))}

{/* Method result used in computation */} -

Average * factor: {derive({ state_items: state.items, state_items_length: state.items.length, state_factor: state.factor }, ({ state_items: _v1, state_items_length: _v2, state_factor: _v3 }) => (_v1.reduce((a, b) => a + b, 0) / _v2) * _v3)}

+

Average * factor: {__ctHelpers.derive({ state_items: state.items, state_items_length: state.items.length, state_factor: state.factor }, ({ state_items: _v1, state_items_length: _v2, state_factor: _v3 }) => (_v1.reduce((a, b) => a + b, 0) / _v2) * _v3)}

Methods on Computed Values

{/* Method on binary expression result */} -

Formatted price: {derive({ state_prices: state.prices, state_discount: state.discount }, ({ state_prices: _v1, state_discount: _v2 }) => (_v1[0] * (1 - _v2)).toFixed(2))}

+

Formatted price: {__ctHelpers.derive({ state_prices: state.prices, state_discount: state.discount }, ({ state_prices: _v1, state_discount: _v2 }) => (_v1[0] * (1 - _v2)).toFixed(2))}

{/* Method on conditional result */} -

Conditional trim: {derive({ state_text: state.text, state_text_length: state.text.length, state_prefix: state.prefix }, ({ state_text: _v1, state_text_length: _v2, state_prefix: _v3 }) => (_v2 > 10 ? _v1 : _v3).trim())}

+

Conditional trim: {__ctHelpers.derive({ state_text: state.text, state_text_length: state.text.length, state_prefix: state.prefix }, ({ state_text: _v1, state_text_length: _v2, state_prefix: _v3 }) => (_v2 > 10 ? _v1 : _v3).trim())}

{/* Method chain on computed value */} -

Complex: {derive({ state_text: state.text, state_prefix: state.prefix }, ({ state_text: _v1, state_prefix: _v2 }) => (_v1 + " " + _v2).trim().toLowerCase().split(" ").join("-"))}

+

Complex: {__ctHelpers.derive({ state_text: state.text, state_prefix: state.prefix }, ({ state_text: _v1, state_prefix: _v2 }) => (_v1 + " " + _v2).trim().toLowerCase().split(" ").join("-"))}

Array Methods with Complex Predicates

{/* Filter with multiple conditions */} -

Active adults: {derive({ state_users: state.users, state_minAge: state.minAge }, ({ state_users: _v1, state_minAge: _v2 }) => _v1.filter(u => u.age >= _v2 && u.active).length)}

+

Active adults: {__ctHelpers.derive({ state_users: state.users, state_minAge: state.minAge }, ({ state_users: _v1, state_minAge: _v2 }) => _v1.filter(u => u.age >= _v2 && u.active).length)}

{/* Map with conditional logic */}
    - {state.users.map(u => (
  • {ifElse(u.active, derive(u.name, _v1 => _v1.toUpperCase()), derive(u.name, _v1 => _v1.toLowerCase()))}
  • ))} + {state.users.map(u => (
  • {__ctHelpers.ifElse(u.active, __ctHelpers.derive(u.name, _v1 => _v1.toUpperCase()), __ctHelpers.derive(u.name, _v1 => _v1.toLowerCase()))}
  • ))}
{/* Some/every with reactive predicates */} -

Has adults: {ifElse(derive({ state_users: state.users, state_minAge: state.minAge }, ({ state_users: _v1, state_minAge: _v2 }) => _v1.some(u => u.age >= _v2)), "Yes", "No")}

-

All active: {ifElse(derive(state.users, _v1 => _v1.every(u => u.active)), "Yes", "No")}

+

Has adults: {__ctHelpers.ifElse(__ctHelpers.derive({ state_users: state.users, state_minAge: state.minAge }, ({ state_users: _v1, state_minAge: _v2 }) => _v1.some(u => u.age >= _v2)), "Yes", "No")}

+

All active: {__ctHelpers.ifElse(__ctHelpers.derive(state.users, _v1 => _v1.every(u => u.active)), "Yes", "No")}

Method Calls in Expressions

{/* Method result in arithmetic */} -

Length sum: {derive({ state_text: state.text, state_prefix: state.prefix }, ({ state_text: _v1, state_prefix: _v2 }) => _v1.trim().length + _v2.trim().length)}

+

Length sum: {__ctHelpers.derive({ state_text: state.text, state_prefix: state.prefix }, ({ state_text: _v1, state_prefix: _v2 }) => _v1.trim().length + _v2.trim().length)}

{/* Method result in comparison */} -

Is long: {ifElse(derive({ state_text: state.text, state_threshold: state.threshold }, ({ state_text: _v1, state_threshold: _v2 }) => _v1.trim().length > _v2), "Yes", "No")}

+

Is long: {__ctHelpers.ifElse(__ctHelpers.derive({ state_text: state.text, state_threshold: state.threshold }, ({ state_text: _v1, state_threshold: _v2 }) => _v1.trim().length > _v2), "Yes", "No")}

{/* Multiple method results combined */} -

Joined: {derive({ state_words: state.words, state_separator: state.separator }, ({ state_words: _v1, state_separator: _v2 }) => _v1.join(_v2).toUpperCase())}

+

Joined: {__ctHelpers.derive({ state_words: state.words, state_separator: state.separator }, ({ state_words: _v1, state_separator: _v2 }) => _v1.join(_v2).toUpperCase())}

), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.input.tsx index 711045632..fd3271a0f 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.input.tsx @@ -1,5 +1,5 @@ /// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { text: string; diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/no-double-derive.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/no-double-derive.expected.tsx index 96b036003..dcc682f22 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/no-double-derive.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/no-double-derive.expected.tsx @@ -1,4 +1,4 @@ -/// +import * as __ctHelpers from "commontools"; import { derive, h } from "commontools"; // Test case: User-written derive calls should not be double-wrapped // This tests that derive(index, (i) => i + 1) doesn't become derive(index, index => derive(index, (i) => i + 1)) @@ -19,4 +19,5 @@ export default function TestComponent({ items, cellRef }) { {/* Simple property access - should NOT be transformed */} Direct access: {cellRef.value}
); -} \ No newline at end of file +} +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/no-transform-simple-ref.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/no-transform-simple-ref.expected.tsx index e83e3b0eb..3ed5bb828 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/no-transform-simple-ref.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/no-transform-simple-ref.expected.tsx @@ -1,4 +1,4 @@ -/// +import * as __ctHelpers from "commontools"; import { recipe, NAME, OpaqueRef, h } from "commontools"; const count: OpaqueRef = {} as any; const element =
{count}
; @@ -7,3 +7,4 @@ export default recipe("test", (state) => { [NAME]: "test", }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx index 71e4bc3ba..fe15ac2a4 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx @@ -1,5 +1,5 @@ -/// -import { Cell, cell, createCell, derive, h, handler, ifElse, lift, NAME, navigateTo, recipe, UI, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, cell, createCell, h, handler, ifElse, lift, NAME, navigateTo, recipe, UI, } from "commontools"; // the simple charm (to which we'll store references within a cell) const SimpleRecipe = recipe("Simple Recipe", () => ({ [NAME]: "Some Simple Recipe", @@ -57,7 +57,7 @@ const addCharmAndNavigate = lift({ return undefined; }); // Create a new SimpleRecipe and add it to the array -const createSimpleRecipe = handler(true as const satisfies JSONSchema, { +const createSimpleRecipe = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { cellRef: { @@ -67,7 +67,7 @@ const createSimpleRecipe = handler(true as const satisfies JSONSchema, { } }, required: ["cellRef"] -} as const satisfies JSONSchema, (_, { cellRef }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { cellRef }) => { // Create isInitialized cell for this charm addition const isInitialized = cell(false); // Create the charm @@ -76,13 +76,13 @@ const createSimpleRecipe = handler(true as const satisfies JSONSchema, { return addCharmAndNavigate({ charm, cellRef, isInitialized }); }); // Handler to navigate to a specific charm from the list -const goToCharm = handler(true as const satisfies JSONSchema, { +const goToCharm = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { charm: true }, required: ["charm"] -} as const satisfies JSONSchema, (_, { charm }) => { +} as const satisfies __ctHelpers.JSONSchema, (_, { charm }) => { console.log("goToCharm clicked"); return navigateTo(charm); }); @@ -100,9 +100,9 @@ export default recipe("Charms Launcher", () => { {ifElse(!cellRef?.length,
No charms created yet
,
    {cellRef.map((charm: any, index: number) => (
  • - Go to Charm {derive(index, index => index + 1)} + Go to Charm {__ctHelpers.derive(index, index => index + 1)} - Charm {derive(index, index => index + 1)}: {derive(charm, charm => charm[NAME] || "Unnamed")} + Charm {__ctHelpers.derive(index, index => index + 1)}: {__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")}
  • ))}
)} @@ -113,4 +113,4 @@ export default recipe("Charms Launcher", () => { cellRef, }; }); - +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.input.tsx index 3f35a33ef..bbc2caabb 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.input.tsx @@ -3,7 +3,6 @@ import { Cell, cell, createCell, - derive, h, handler, ifElse, diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx index 2959e6869..3c7f65890 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx @@ -1,14 +1,15 @@ -/// -import { cell, h, recipe, UI, derive } from "commontools"; +import * as __ctHelpers from "commontools"; +import { cell, h, recipe, UI } from "commontools"; export default recipe("OpaqueRefOperations", (state) => { const count = cell(10); const price = cell(10); return { [UI]: (

Count: {count}

-

Next: {derive(count, count => count + 1)}

-

Double: {derive(count, count => count * 2)}

-

Total: {derive(price, price => price * 1.1)}

+

Next: {__ctHelpers.derive(count, count => count + 1)}

+

Double: {__ctHelpers.derive(count, count => count * 2)}

+

Total: {__ctHelpers.derive(price, price => price * 1.1)}

) }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx index a736ec69a..b11bcc68f 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx @@ -1,11 +1,12 @@ -/// -import { cell, h, recipe, NAME, UI, derive } from "commontools"; +import * as __ctHelpers from "commontools"; +import { cell, h, recipe, NAME, UI } from "commontools"; export default recipe("Optional Chain Predicate", () => { const items = cell([]); return { [NAME]: "Optional chain predicate", [UI]: (
- {derive(items, items => !items?.length && No items)} + {__ctHelpers.derive(items, items => !items?.length && No items)}
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx index 6c093b5ab..5b94052fd 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx @@ -1,11 +1,12 @@ -/// -import { cell, h, recipe, NAME, UI, derive } from "commontools"; +import * as __ctHelpers from "commontools"; +import { cell, h, recipe, NAME, UI } from "commontools"; export default recipe("Optional Element Access", () => { const list = cell(undefined); return { [NAME]: "Optional element access", [UI]: (
- {derive(list, list => !list?.[0] && No first entry)} + {__ctHelpers.derive(list, list => !list?.[0] && No first entry)}
), }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.expected.tsx index 121aca65d..84f65ddec 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { user: { name: string; @@ -314,7 +314,7 @@ export default recipe({ } }, required: ["user", "config", "data", "deeply", "arrays"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { return { [UI]: (

Same Base, Different Properties

@@ -322,10 +322,10 @@ export default recipe({

User info: {state.user.name} (age: {state.user.age}, email: {state.user.email})

{/* String concatenation with multiple property accesses */} -

Full profile: {derive({ state_user_name: state.user.name, state_user_profile_location: state.user.profile.location, state_user_profile_bio: state.user.profile.bio }, ({ state_user_name: _v1, state_user_profile_location: _v2, state_user_profile_bio: _v3 }) => _v1 + " from " + _v2 + " - " + _v3)}

+

Full profile: {__ctHelpers.derive({ state_user_name: state.user.name, state_user_profile_location: state.user.profile.location, state_user_profile_bio: state.user.profile.bio }, ({ state_user_name: _v1, state_user_profile_location: _v2, state_user_profile_bio: _v3 }) => _v1 + " from " + _v2 + " - " + _v3)}

{/* Arithmetic with multiple properties from same base */} -

Age calculation: {derive(state.user.age, _v1 => _v1 * 12)} months, or {derive(state.user.age, _v1 => _v1 * 365)} days

+

Age calculation: {__ctHelpers.derive(state.user.age, _v1 => _v1 * 12)} months, or {__ctHelpers.derive(state.user.age, _v1 => _v1 * 365)} days

Deeply Nested Property Chains

{/* Multiple references to deeply nested object */} @@ -335,7 +335,7 @@ export default recipe({

Typography: Headings in {state.config.theme.fonts.heading}, body in {state.config.theme.fonts.body}, code in {state.config.theme.fonts.mono}

{/* Mixed depth accesses */} -

Config summary: Dark mode {ifElse(state.config.features.darkMode, "enabled", "disabled")} with {state.config.theme.colors.primary} primary color

+

Config summary: Dark mode {__ctHelpers.ifElse(state.config.features.darkMode, "enabled", "disabled")} with {state.config.theme.colors.primary} primary color

Very Deep Nesting with Multiple References

{/* Accessing different properties at same deep level */} @@ -356,17 +356,17 @@ export default recipe({

Complex Expressions with Shared Bases

{/* Conditional with multiple property accesses */} -

Status: {ifElse(state.user.settings.notifications, derive({ state_user_name: state.user.name, state_user_settings_theme: state.user.settings.theme }, ({ state_user_name: _v1, state_user_settings_theme: _v2 }) => _v1 + " has notifications on with " + _v2 + " theme"), derive(state.user.name, _v1 => _v1 + " has notifications off"))}

+

Status: {__ctHelpers.ifElse(state.user.settings.notifications, __ctHelpers.derive({ state_user_name: state.user.name, state_user_settings_theme: state.user.settings.theme }, ({ state_user_name: _v1, state_user_settings_theme: _v2 }) => _v1 + " has notifications on with " + _v2 + " theme"), __ctHelpers.derive(state.user.name, _v1 => _v1 + " has notifications off"))}

{/* Computed expression with shared base */} -

Spacing calc: {derive({ state_config_theme_spacing_small: state.config.theme.spacing.small, state_config_theme_spacing_medium: state.config.theme.spacing.medium, state_config_theme_spacing_large: state.config.theme.spacing.large }, ({ state_config_theme_spacing_small: _v1, state_config_theme_spacing_medium: _v2, state_config_theme_spacing_large: _v3 }) => _v1 + _v2 + _v3)} total

+

Spacing calc: {__ctHelpers.derive({ state_config_theme_spacing_small: state.config.theme.spacing.small, state_config_theme_spacing_medium: state.config.theme.spacing.medium, state_config_theme_spacing_large: state.config.theme.spacing.large }, ({ state_config_theme_spacing_small: _v1, state_config_theme_spacing_medium: _v2, state_config_theme_spacing_large: _v3 }) => _v1 + _v2 + _v3)} total

{/* Boolean expressions with multiple properties */} -

Features: {ifElse(derive({ state_config_features_darkMode: state.config.features.darkMode, state_config_features_animations: state.config.features.animations }, ({ state_config_features_darkMode: _v1, state_config_features_animations: _v2 }) => _v1 && _v2), "Full features", "Limited features")}

+

Features: {__ctHelpers.ifElse(__ctHelpers.derive({ state_config_features_darkMode: state.config.features.darkMode, state_config_features_animations: state.config.features.animations }, ({ state_config_features_darkMode: _v1, state_config_features_animations: _v2 }) => _v1 && _v2), "Full features", "Limited features")}

Method Calls on Shared Bases

{/* Multiple method calls on properties from same base */} -

Formatted: {derive(state.user.name, _v1 => _v1.toUpperCase())} - {derive(state.user.email, _v1 => _v1.toLowerCase())}

+

Formatted: {__ctHelpers.derive(state.user.name, _v1 => _v1.toUpperCase())} - {__ctHelpers.derive(state.user.email, _v1 => _v1.toLowerCase())}

{/* Property access and method calls mixed */}

Profile length: {state.user.profile.bio.length} chars in bio, {state.user.profile.location.length} chars in location

@@ -379,16 +379,16 @@ export default recipe({

Data summary: {state.data.items.length} items with average {state.data.totals.average}

{/* Multiple levels of the same chain */} -

Nested refs: {state.config.theme.colors.primary} in {state.config.theme.fonts.body} with {ifElse(state.config.features.animations, "animations", "no animations")}

+

Nested refs: {state.config.theme.colors.primary} in {state.config.theme.fonts.body} with {__ctHelpers.ifElse(state.config.features.animations, "animations", "no animations")}

Extreme Parent Suppression Test

{/* Using every level of a deep chain */}

All levels: - Root: {ifElse(state.deeply, "exists", "missing")}, - Nested: {ifElse(state.deeply.nested, "exists", "missing")}, + Root: {__ctHelpers.ifElse(state.deeply, "exists", "missing")}, + Nested: {__ctHelpers.ifElse(state.deeply.nested, "exists", "missing")}, Value: {state.deeply.nested.structure.with.many.levels.value}

), }; }); - +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.input.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.input.tsx index 95bf627c4..2b566d7c7 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.input.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/parent-suppression-edge.input.tsx @@ -1,5 +1,5 @@ /// -import { h, recipe, UI, derive, ifElse, JSONSchema } from "commontools"; +import { h, recipe, UI } from "commontools"; interface State { user: { diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-statements-vs-jsx.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-statements-vs-jsx.expected.tsx index c9458d6bf..6a5a0138c 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-statements-vs-jsx.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-statements-vs-jsx.expected.tsx @@ -1,9 +1,9 @@ -/// -import { recipe, UI, NAME, str, handler, h, Cell, derive, ifElse, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { recipe, UI, NAME, str, handler, h, Cell } from "commontools"; interface RecipeState { value: number; } -const increment = handler(true as const satisfies JSONSchema, { +const increment = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -12,12 +12,12 @@ const increment = handler(true as const satisfies JSONSchema, { } }, required: ["value"] -} as const satisfies JSONSchema, (e, state: { +} as const satisfies __ctHelpers.JSONSchema, (e, state: { value: Cell; }) => { state.value.set(state.value.get() + 1); }); -const decrement = handler(true as const satisfies JSONSchema, { +const decrement = handler(true as const satisfies __ctHelpers.JSONSchema, { type: "object", properties: { value: { @@ -26,7 +26,7 @@ const decrement = handler(true as const satisfies JSONSchema, { } }, required: ["value"] -} as const satisfies JSONSchema, (e, state: { +} as const satisfies __ctHelpers.JSONSchema, (e, state: { value: Cell; }) => { state.value.set(state.value.get() - 1); @@ -39,7 +39,7 @@ export default recipe({ } }, required: ["value"] -} as const satisfies JSONSchema, (state) => { +} as const satisfies __ctHelpers.JSONSchema, (state) => { // These should NOT be transformed (statement context) const next = state.value + 1; const previous = state.value - 1; @@ -58,13 +58,13 @@ export default recipe({ {/* These SHOULD be transformed (JSX expression context) */} Current: {state.value}
- Next number: {derive(state.value, _v1 => _v1 + 1)} + Next number: {__ctHelpers.derive(state.value, _v1 => _v1 + 1)}
- Previous: {derive(state.value, _v1 => _v1 - 1)} + Previous: {__ctHelpers.derive(state.value, _v1 => _v1 - 1)}
- Doubled: {derive(state.value, _v1 => _v1 * 2)} + Doubled: {__ctHelpers.derive(state.value, _v1 => _v1 * 2)}
- Status: {ifElse(derive(state.value, _v1 => _v1 > 10), "High", "Low")} + Status: {__ctHelpers.ifElse(__ctHelpers.derive(state.value, _v1 => _v1 > 10), "High", "Low")}

+
), @@ -78,3 +78,4 @@ export default recipe({ } }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-with-cells.expected.tsx b/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-with-cells.expected.tsx index c7c9415eb..0008e7948 100644 --- a/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-with-cells.expected.tsx +++ b/packages/ts-transformers/test/fixtures/jsx-expressions/recipe-with-cells.expected.tsx @@ -1,5 +1,5 @@ -/// -import { h, recipe, UI, derive, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { h, recipe, UI } from "commontools"; export default recipe({ type: "object", properties: { @@ -8,13 +8,14 @@ export default recipe({ } }, required: ["value"] -} as const satisfies JSONSchema, (cell) => { +} as const satisfies __ctHelpers.JSONSchema, (cell) => { return { [UI]: (

Current value: {cell.value}

-

Next value: {derive(cell.value, _v1 => _v1 + 1)}

-

Double: {derive(cell.value, _v1 => _v1 * 2)}

+

Next value: {__ctHelpers.derive(cell.value, _v1 => _v1 + 1)}

+

Double: {__ctHelpers.derive(cell.value, _v1 => _v1 * 2)}

), value: cell.value, }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/schema-transform/nested-default-optional.expected.tsx b/packages/ts-transformers/test/fixtures/schema-transform/nested-default-optional.expected.tsx index 37d48bc58..61f3b6042 100644 --- a/packages/ts-transformers/test/fixtures/schema-transform/nested-default-optional.expected.tsx +++ b/packages/ts-transformers/test/fixtures/schema-transform/nested-default-optional.expected.tsx @@ -1,5 +1,5 @@ -/// -import { type Cell, Default, handler, recipe, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { type Cell, Default, handler, recipe } from "commontools"; interface OptionalBranch { counter?: number; label?: string; @@ -14,7 +14,7 @@ interface NestedOptionalArgs { // deno-lint-ignore ban-types state: Default; } -const increment = handler(true as const satisfies JSONSchema, { +const increment = handler(true as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { @@ -53,7 +53,7 @@ const increment = handler(true as const satisfies JSONSchema, { } } } -} as const satisfies JSONSchema, (_, context: { +} as const satisfies __ctHelpers.JSONSchema, (_, context: { state: Cell; }) => { const current = context.state.get() ?? {}; @@ -100,9 +100,10 @@ export default recipe({ } } } -} as const satisfies JSONSchema, ({ state }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ state }) => { return { state, increment: increment({ state }), }; -}); \ No newline at end of file +}); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/schema-transform/opaque-ref-map.expected.ts b/packages/ts-transformers/test/fixtures/schema-transform/opaque-ref-map.expected.ts index 1b4bb3db1..384a95c33 100644 --- a/packages/ts-transformers/test/fixtures/schema-transform/opaque-ref-map.expected.ts +++ b/packages/ts-transformers/test/fixtures/schema-transform/opaque-ref-map.expected.ts @@ -1,5 +1,5 @@ -/// -import { OpaqueRef, recipe, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { OpaqueRef, recipe } from "commontools"; interface TodoItem { title: string; done: boolean; @@ -30,7 +30,7 @@ export default recipe({ required: ["title", "done"] } } -} as const satisfies JSONSchema, ({ items }) => { +} as const satisfies __ctHelpers.JSONSchema, ({ items }) => { // This should NOT be transformed to items.get().map() // because OpaqueRef has its own map method const mapped = items.map((item) => item.title); @@ -42,3 +42,4 @@ export default recipe({ })); return { mapped, filtered }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/schema-transform/recipe-with-types.expected.tsx b/packages/ts-transformers/test/fixtures/schema-transform/recipe-with-types.expected.tsx index 26ba2a874..361108128 100644 --- a/packages/ts-transformers/test/fixtures/schema-transform/recipe-with-types.expected.tsx +++ b/packages/ts-transformers/test/fixtures/schema-transform/recipe-with-types.expected.tsx @@ -1,5 +1,5 @@ -/// -import { recipe, h, UI, NAME, Cell, Default, handler, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { recipe, h, UI, NAME, toSchema, Cell, Default, handler } from "commontools"; interface Item { text: Default; } @@ -45,7 +45,7 @@ const inputSchema = { required: ["text"] } } -} as const satisfies JSONSchema; +} as const satisfies __ctHelpers.JSONSchema; const outputSchema = { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", @@ -78,7 +78,7 @@ const outputSchema = { required: ["text"] } } -} as const satisfies JSONSchema; +} as const satisfies __ctHelpers.JSONSchema; // Handler that logs the message event const addItem = handler({ type: "object", @@ -94,7 +94,7 @@ const addItem = handler({ } }, required: ["detail"] -} as const satisfies JSONSchema, { +} as const satisfies __ctHelpers.JSONSchema, { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { @@ -119,7 +119,7 @@ const addItem = handler({ required: ["text"] } } -} as const satisfies JSONSchema, (event: InputEventType, { items }: { +} as const satisfies __ctHelpers.JSONSchema, (event: InputEventType, { items }: { items: Cell; }) => { items.push({ text: event.detail.message }); @@ -142,3 +142,4 @@ export default recipe(inputSchema, outputSchema, ({ title, items }) => { items_count }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/schema-transform/with-opaque-ref.expected.tsx b/packages/ts-transformers/test/fixtures/schema-transform/with-opaque-ref.expected.tsx index 28e7527ab..48629eda3 100644 --- a/packages/ts-transformers/test/fixtures/schema-transform/with-opaque-ref.expected.tsx +++ b/packages/ts-transformers/test/fixtures/schema-transform/with-opaque-ref.expected.tsx @@ -1,5 +1,5 @@ -/// -import { Cell, derive, h, recipe, UI, JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { Cell, derive, h, recipe, toSchema, UI } from "commontools"; interface State { value: Cell; } @@ -15,11 +15,11 @@ const model = { default: { value: 0 } -} as const satisfies JSONSchema; +} as const satisfies __ctHelpers.JSONSchema; export default recipe(model, model, (cell) => { - const doubled = derive(true as const satisfies JSONSchema, { + const doubled = derive(true as const satisfies __ctHelpers.JSONSchema, { type: "number" - } as const satisfies JSONSchema, cell.value, (v) => v * 2); + } as const satisfies __ctHelpers.JSONSchema, cell.value, (v) => v * 2); return { [UI]: (

Value: {cell.value}

@@ -28,3 +28,4 @@ export default recipe(model, model, (cell) => { value: cell.value, }; }); +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/fixtures/schema-transform/with-options.expected.ts b/packages/ts-transformers/test/fixtures/schema-transform/with-options.expected.ts index 60866e9d1..b0eecd8fb 100644 --- a/packages/ts-transformers/test/fixtures/schema-transform/with-options.expected.ts +++ b/packages/ts-transformers/test/fixtures/schema-transform/with-options.expected.ts @@ -1,5 +1,5 @@ -/// -import { JSONSchema } from "commontools"; +import * as __ctHelpers from "commontools"; +import { toSchema } from "commontools"; interface Config { value: number; } @@ -15,5 +15,6 @@ const configSchema = { value: 42 }, description: "Configuration schema" -} as const satisfies JSONSchema; +} as const satisfies __ctHelpers.JSONSchema; export { configSchema }; +__ctHelpers.NAME; // diff --git a/packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts b/packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts index 821eda709..0c0a2ea21 100644 --- a/packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts +++ b/packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts @@ -46,15 +46,15 @@ describe("OpaqueRef map callbacks", () => { assertStringIncludes( output, - "derive(index, index => index + 1)", + "__ctHelpers.derive(index, index => index + 1)", ); assertStringIncludes( output, - 'derive(charm, charm => charm[NAME] || "Unnamed")', + '__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")', ); assertStringIncludes( output, - "ifElse(derive(state.charms.length, _v1 => !_v1)", + "ifElse(__ctHelpers.derive(state.charms.length, _v1 => !_v1)", ); }); }); diff --git a/packages/ts-transformers/test/opaque-ref/runtime-style.test.ts b/packages/ts-transformers/test/opaque-ref/runtime-style.test.ts index b25ce86a1..fbb8a8eba 100644 --- a/packages/ts-transformers/test/opaque-ref/runtime-style.test.ts +++ b/packages/ts-transformers/test/opaque-ref/runtime-style.test.ts @@ -10,35 +10,6 @@ const commontools = await staticCache.getText("types/commontools.d.ts"); describe("OpaqueRef transformer (runtime-style API)", () => { const types = { "commontools.d.ts": commontools }; - describe("import management", () => { - it("adds derive import when needed", async () => { - const source = `/// -import { OpaqueRef, h } from "commontools"; -const count: OpaqueRef = {} as any; -const el =
{count + 1}
; -`; - const transformed = await transformSource(source, { types }); - expect(transformed).toContain( - 'import { OpaqueRef, h, derive } from "commontools"', - ); - }); - - it("does not duplicate existing imports", async () => { - const source = `/// -import { OpaqueRef, derive, ifElse, h } from "commontools"; -const count: OpaqueRef = {} as any; -const isActive: OpaqueRef = {} as any; -const el =
{count + 1} {isActive ? 1 : 0}
; -`; - const transformed = await transformSource(source, { types }); - const importMatches = transformed.match(/import.*from "commontools"/g); - expect(importMatches).toHaveLength(1); - expect(transformed).toContain( - 'import { OpaqueRef, derive, ifElse, h } from "commontools"', - ); - }); - }); - describe("error mode", () => { it("reports errors instead of transforming", async () => { const source = `/// diff --git a/packages/ts-transformers/test/transform.test.ts b/packages/ts-transformers/test/transform.test.ts index 466f2acc3..2cf4777b4 100644 --- a/packages/ts-transformers/test/transform.test.ts +++ b/packages/ts-transformers/test/transform.test.ts @@ -22,23 +22,15 @@ describe("CommonToolsTransformerPipeline", () => { "/main.ts": fixture, }); assert( - /toSchema/.test(disabled["/main.ts"]!), - "no replacements without ", - ); - assert( - !/JSONSchema/.test(disabled["/main.ts"]!), + !/import \* as __ctHelpers/.test(disabled["/main.ts"]!), "no replacements without ", ); const enabled = await transformFiles({ "/main.ts": `/// \n` + fixture, }); assert( - !/toSchema/.test(enabled["/main.ts"]!), - "replacements with ", - ); - assert( - /JSONSchema/.test(enabled["/main.ts"]!), - "replacements with ", + /import \* as __ctHelpers/.test(enabled["/main.ts"]!), + "no replacements without ", ); }); }); diff --git a/packages/ts-transformers/test/utils.ts b/packages/ts-transformers/test/utils.ts index 870e986e2..cfb24d230 100644 --- a/packages/ts-transformers/test/utils.ts +++ b/packages/ts-transformers/test/utils.ts @@ -1,7 +1,10 @@ import ts from "typescript"; import { join } from "@std/path"; import { StaticCache } from "@commontools/static"; -import { CommonToolsTransformerPipeline } from "../src/mod.ts"; +import { + CommonToolsTransformerPipeline, + transformCtDirective, +} from "../src/mod.ts"; import { assert } from "@std/assert"; const ENV_TYPE_ENTRIES = ["es2023", "dom", "jsx"] as const; @@ -28,7 +31,7 @@ export async function transformSource( } export async function transformFiles( - files: Record, + inFiles: Record, options: TransformOptions = {}, ): Promise> { const { @@ -40,6 +43,12 @@ export async function transformFiles( envTypesCache = await loadEnvironmentTypes(); } + // Pretransform + const files = Object.entries(inFiles).reduce((files, [key, value]) => { + files[key] = transformCtDirective(value); + return files; + }, {} as Record); + const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS,