typebars 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,12 +188,24 @@ export interface ExecuteOptions {
188
188
  identifierData?: Record<number, Record<string, unknown>>;
189
189
  /** Schemas by identifier (for static validation with identifiers) */
190
190
  identifierSchemas?: Record<number, JSONSchema7>;
191
+ /**
192
+ * Explicit coercion schema for the output value.
193
+ * When provided with a primitive type, the execution result will be
194
+ * coerced to match the declared type instead of using auto-detection.
195
+ */
196
+ coerceSchema?: JSONSchema7;
191
197
  }
192
198
  export interface AnalyzeAndExecuteOptions {
193
199
  /** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */
194
200
  identifierSchemas?: Record<number, JSONSchema7>;
195
201
  /** Data by identifier `{ [id]: { key: value } }` for execution */
196
202
  identifierData?: Record<number, Record<string, unknown>>;
203
+ /**
204
+ * Explicit coercion schema for the output value.
205
+ * When provided with a primitive type, the execution result will be
206
+ * coerced to match the declared type instead of using auto-detection.
207
+ */
208
+ coerceSchema?: JSONSchema7;
197
209
  }
198
210
  /** Describes a parameter expected by a helper */
199
211
  export interface HelperParam {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { FromSchema, JSONSchema } from \"json-schema-to-ts\";\n\n// ─── Template Input ──────────────────────────────────────────────────────────\n// The engine accepts primitive values in addition to template strings.\n// When a non-string value is passed, it is treated as a literal passthrough:\n// analysis returns the inferred type, and execution returns the value as-is.\n\n/**\n * Object where each property is a `TemplateInput` (recursive).\n *\n * Allows passing an entire structure as a template:\n * ```\n * engine.analyze({\n * userName: \"{{name}}\",\n * userAge: \"{{age}}\",\n * nested: { x: \"{{foo}}\" },\n * }, inputSchema);\n * ```\n */\nexport interface TemplateInputObject {\n\t[key: string]: TemplateInput;\n}\n\n/**\n * Array where each element is a `TemplateInput` (recursive).\n *\n * Allows passing an array as a template:\n * ```\n * engine.analyze([\"{{name}}\", \"{{age}}\"], inputSchema);\n * engine.execute([\"{{name}}\", 42], data);\n * ```\n */\nexport type TemplateInputArray = TemplateInput[];\n\n/**\n * Input type accepted by the template engine.\n *\n * - `string` → standard Handlebars template (parsed and executed)\n * - `number` → numeric literal (passthrough)\n * - `boolean` → boolean literal (passthrough)\n * - `null` → null literal (passthrough)\n * - `TemplateInputArray` → array where each element is a `TemplateInput`\n * - `TemplateInputObject` → object where each property is a `TemplateInput`\n */\nexport type TemplateInput =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| TemplateInputArray\n\t| TemplateInputObject;\n\n/**\n * Checks whether a value is a non-string primitive literal (number, boolean, null).\n * These values are treated as passthrough by the engine.\n *\n * Note: objects (`TemplateInputObject`) and arrays (`TemplateInputArray`) are NOT literals.\n */\nexport function isLiteralInput(\n\tinput: TemplateInput,\n): input is number | boolean | null {\n\treturn (\n\t\tinput === null || (typeof input !== \"string\" && typeof input !== \"object\")\n\t);\n}\n\n/**\n * Checks whether a value is a template array (`TemplateInputArray`).\n * Template arrays are processed recursively by the engine:\n * each element is analyzed/executed individually and the result is an array.\n */\nexport function isArrayInput(\n\tinput: TemplateInput,\n): input is TemplateInputArray {\n\treturn Array.isArray(input);\n}\n\n/**\n * Checks whether a value is a template object (`TemplateInputObject`).\n * Template objects are processed recursively by the engine:\n * each property is analyzed/executed individually.\n *\n * Note: arrays are excluded — use `isArrayInput()` first.\n */\nexport function isObjectInput(\n\tinput: TemplateInput,\n): input is TemplateInputObject {\n\treturn input !== null && typeof input === \"object\" && !Array.isArray(input);\n}\n\n/**\n * Infers the JSON Schema of a non-string primitive value.\n *\n * @param value - The primitive value (number, boolean, null)\n * @returns The corresponding JSON Schema\n *\n * @example\n * ```\n * inferPrimitiveSchema(42) // → { type: \"number\" }\n * inferPrimitiveSchema(true) // → { type: \"boolean\" }\n * inferPrimitiveSchema(null) // → { type: \"null\" }\n * ```\n */\nexport function inferPrimitiveSchema(\n\tvalue: number | boolean | null,\n): JSONSchema7 {\n\tif (value === null) return { type: \"null\" };\n\tif (typeof value === \"boolean\") return { type: \"boolean\" };\n\tif (typeof value === \"number\") {\n\t\treturn Number.isInteger(value) ? { type: \"integer\" } : { type: \"number\" };\n\t}\n\t// Exhaustiveness check — all branches are covered above.\n\t// If the type of `value` changes, TypeScript will raise an error here.\n\tvalue satisfies never;\n\treturn { type: \"null\" };\n}\n\n// ─── Diagnostic Codes ────────────────────────────────────────────────────────\n// Machine-readable codes for each error/warning type, enabling the frontend\n// to react programmatically without parsing the human-readable message.\n\nexport type DiagnosticCode =\n\t/** The referenced property does not exist in the context schema */\n\t| \"UNKNOWN_PROPERTY\"\n\t/** Type mismatch (e.g. #each on a non-array) */\n\t| \"TYPE_MISMATCH\"\n\t/** A block helper is used without a required argument */\n\t| \"MISSING_ARGUMENT\"\n\t/** Unknown block helper (neither built-in nor registered) */\n\t| \"UNKNOWN_HELPER\"\n\t/** The expression cannot be statically analyzed */\n\t| \"UNANALYZABLE\"\n\t/** The {{key:N}} syntax is used but no identifierSchemas were provided */\n\t| \"MISSING_IDENTIFIER_SCHEMAS\"\n\t/** The identifier N does not exist in the provided identifierSchemas */\n\t| \"UNKNOWN_IDENTIFIER\"\n\t/** The property does not exist in the identifier's schema */\n\t| \"IDENTIFIER_PROPERTY_NOT_FOUND\"\n\t/** Syntax error in the template */\n\t| \"PARSE_ERROR\";\n\n// ─── Diagnostic Details ──────────────────────────────────────────────────────\n// Supplementary information to understand the exact cause of the error.\n// Designed to be easily JSON-serializable and consumable by a frontend.\n\nexport interface DiagnosticDetails {\n\t/** Path of the expression that caused the error (e.g. `\"user.name.foo\"`) */\n\tpath?: string;\n\t/** Name of the helper involved (for helper-related errors) */\n\thelperName?: string;\n\t/** What was expected (e.g. `\"array\"`, `\"property to exist\"`) */\n\texpected?: string;\n\t/** What was found (e.g. `\"string\"`, `\"undefined\"`) */\n\tactual?: string;\n\t/** Available properties in the current schema (for suggestions) */\n\tavailableProperties?: string[];\n\t/** Template identifier number (for `{{key:N}}` errors) */\n\tidentifier?: number;\n}\n\n// ─── Static Analysis Result ──────────────────────────────────────────────────\n\n/** Diagnostic produced by the static analyzer */\nexport interface TemplateDiagnostic {\n\t/** \"error\" blocks execution, \"warning\" is informational */\n\tseverity: \"error\" | \"warning\";\n\n\t/** Machine-readable code identifying the error type */\n\tcode: DiagnosticCode;\n\n\t/** Human-readable message describing the problem */\n\tmessage: string;\n\n\t/** Position in the template source (if available from the AST) */\n\tloc?: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t};\n\n\t/** Fragment of the template source around the error */\n\tsource?: string;\n\n\t/** Structured information for debugging and frontend display */\n\tdetails?: DiagnosticDetails;\n}\n\n/** Complete result of the static analysis */\nexport interface AnalysisResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** JSON Schema describing the template's return type */\n\toutputSchema: JSONSchema7;\n}\n\n/** Lightweight validation result (without output type inference) */\nexport interface ValidationResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n}\n\n// ─── Public Engine Options ───────────────────────────────────────────────────\n\nexport interface TemplateEngineOptions {\n\t/**\n\t * Capacity of the parsed AST cache. Each parsed template is cached\n\t * to avoid costly re-parsing on repeated calls.\n\t * @default 256\n\t */\n\tastCacheSize?: number;\n\n\t/**\n\t * Capacity of the compiled Handlebars template cache.\n\t * @default 256\n\t */\n\tcompilationCacheSize?: number;\n\n\t/**\n\t * Custom helpers to register during engine construction.\n\t *\n\t * Each entry describes a helper with its name, implementation,\n\t * expected parameters, and return type.\n\t *\n\t * @example\n\t * ```\n\t * const engine = new Typebars({\n\t * helpers: [\n\t * {\n\t * name: \"uppercase\",\n\t * description: \"Converts a string to uppercase\",\n\t * fn: (value: string) => String(value).toUpperCase(),\n\t * params: [\n\t * { name: \"value\", type: { type: \"string\" }, description: \"The string to convert\" },\n\t * ],\n\t * returnType: { type: \"string\" },\n\t * },\n\t * ],\n\t * });\n\t * ```\n\t */\n\thelpers?: HelperConfig[];\n}\n\n// ─── Execution Options ───────────────────────────────────────────────────────\n// Optional options object for `execute()`, replacing multiple positional\n// parameters for better ergonomics.\n\nexport interface ExecuteOptions {\n\t/** JSON Schema for pre-execution static validation */\n\tschema?: JSONSchema7;\n\t/** Data by identifier `{ [id]: { key: value } }` */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/** Schemas by identifier (for static validation with identifiers) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n}\n\n// ─── Combined Analyze-and-Execute Options ────────────────────────────────────\n// Optional options object for `analyzeAndExecute()`, grouping parameters\n// related to template identifiers.\n\nexport interface AnalyzeAndExecuteOptions {\n\t/** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Data by identifier `{ [id]: { key: value } }` for execution */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n}\n\n// ─── Custom Helpers ──────────────────────────────────────────────────────────\n// Allows registering custom helpers with their type signature for static\n// analysis support.\n\n/** Describes a parameter expected by a helper */\nexport interface HelperParam {\n\t/** Parameter name (for documentation / introspection) */\n\tname: string;\n\n\t/**\n\t * JSON Schema describing the expected type for this parameter.\n\t * Used for documentation and static validation.\n\t */\n\ttype?: JSONSchema7;\n\n\t/** Human-readable description of the parameter */\n\tdescription?: string;\n\n\t/**\n\t * Whether the parameter is optional.\n\t * @default false\n\t */\n\toptional?: boolean;\n}\n\n/**\n * Definition of a helper registerable via `registerHelper()`.\n *\n * Contains the runtime implementation and typing metadata\n * for static analysis.\n */\nexport interface HelperDefinition {\n\t/**\n\t * Runtime implementation of the helper — will be registered with Handlebars.\n\t *\n\t * For an inline helper `{{uppercase name}}`:\n\t * `(value: string) => string`\n\t *\n\t * For a block helper `{{#repeat count}}...{{/repeat}}`:\n\t * `function(this: any, count: number, options: Handlebars.HelperOptions) { ... }`\n\t */\n\t// biome-ignore lint/suspicious/noExplicitAny: Handlebars helper signatures are inherently dynamic\n\tfn: (...args: any[]) => unknown;\n\n\t/**\n\t * Parameters expected by the helper (for documentation and analysis).\n\t *\n\t * @example\n\t * ```\n\t * params: [\n\t * { name: \"value\", type: { type: \"number\" }, description: \"The value to round\" },\n\t * { name: \"precision\", type: { type: \"number\" }, description: \"Decimal places\", optional: true },\n\t * ]\n\t * ```\n\t */\n\tparams?: HelperParam[];\n\n\t/**\n\t * JSON Schema describing the helper's return type for static analysis.\n\t * @default { type: \"string\" }\n\t */\n\treturnType?: JSONSchema7;\n\n\t/** Human-readable description of the helper */\n\tdescription?: string;\n}\n\n/**\n * Full helper configuration for registration via the `Typebars({ helpers: [...] })`\n * constructor options.\n *\n * Extends `HelperDefinition` with a required `name`.\n *\n * @example\n * ```\n * const config: HelperConfig = {\n * name: \"round\",\n * description: \"Rounds a number to a given precision\",\n * fn: (value: number, precision?: number) => { ... },\n * params: [\n * { name: \"value\", type: { type: \"number\" } },\n * { name: \"precision\", type: { type: \"number\" }, optional: true },\n * ],\n * returnType: { type: \"number\" },\n * };\n * ```\n */\nexport interface HelperConfig extends HelperDefinition {\n\t/** Name of the helper as used in templates (e.g. `\"uppercase\"`) */\n\tname: string;\n}\n\n// ─── Automatic Type Inference via json-schema-to-ts ──────────────────────────\n// Allows `defineHelper()` to infer TypeScript types for `fn` arguments\n// from the JSON Schemas declared in `params`.\n\n/**\n * Param definition used for type inference.\n * Accepts `JSONSchema` from `json-schema-to-ts` to allow `FromSchema`\n * to resolve literal types.\n */\ntype TypedHelperParam = {\n\treadonly name: string;\n\treadonly type?: JSONSchema;\n\treadonly description?: string;\n\treadonly optional?: boolean;\n};\n\n/**\n * Infers the TypeScript type of a single parameter from its JSON Schema.\n * - If `optional: true`, the resolved type is unioned with `undefined`.\n * - If `type` is not provided, the type is `unknown`.\n */\ntype InferParamType<P> = P extends {\n\treadonly type: infer S extends JSONSchema;\n\treadonly optional: true;\n}\n\t? FromSchema<S> | undefined\n\t: P extends { readonly type: infer S extends JSONSchema }\n\t\t? FromSchema<S>\n\t\t: unknown;\n\n/**\n * Maps a tuple of `TypedHelperParam` to a tuple of inferred TypeScript types,\n * usable as the `fn` signature.\n *\n * @example\n * ```\n * type Args = InferArgs<readonly [\n * { name: \"a\"; type: { type: \"string\" } },\n * { name: \"b\"; type: { type: \"number\" }; optional: true },\n * ]>;\n * // => [string, number | undefined]\n * ```\n */\ntype InferArgs<P extends readonly TypedHelperParam[]> = {\n\t[K in keyof P]: InferParamType<P[K]>;\n};\n\n/**\n * Helper configuration with generic parameter inference.\n * Used exclusively by `defineHelper()`.\n */\ninterface TypedHelperConfig<P extends readonly TypedHelperParam[]> {\n\tname: string;\n\tdescription?: string;\n\tparams: P;\n\tfn: (...args: InferArgs<P>) => unknown;\n\treturnType?: JSONSchema;\n}\n\n/**\n * Creates a `HelperConfig` with automatic type inference for `fn` arguments\n * based on the JSON Schemas declared in `params`.\n *\n * The generic parameter `const P` preserves schema literal types\n * (equivalent of `as const`), enabling `FromSchema` to resolve the\n * corresponding TypeScript types.\n *\n * @example\n * ```\n * const helper = defineHelper({\n * name: \"concat\",\n * description: \"Concatenates two strings\",\n * params: [\n * { name: \"a\", type: { type: \"string\" }, description: \"First string\" },\n * { name: \"b\", type: { type: \"string\" }, description: \"Second string\" },\n * { name: \"sep\", type: { type: \"string\" }, description: \"Separator\", optional: true },\n * ],\n * fn: (a, b, sep) => {\n * // a: string, b: string, sep: string | undefined\n * const separator = sep ?? \"\";\n * return `${a}${separator}${b}`;\n * },\n * returnType: { type: \"string\" },\n * });\n * ```\n */\nexport function defineHelper<const P extends readonly TypedHelperParam[]>(\n\tconfig: TypedHelperConfig<P>,\n): HelperConfig {\n\treturn config as unknown as HelperConfig;\n}\n"],"names":["defineHelper","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","input","Array","isArray","value","type","Number","isInteger","config"],"mappings":"mPAicgBA,sBAAAA,kBAzVAC,8BAAAA,0BAhCAC,sBAAAA,kBAbAC,wBAAAA,oBA0BAC,uBAAAA,iBA1BT,SAASD,eACfE,KAAoB,EAEpB,OACCA,QAAU,MAAS,OAAOA,QAAU,UAAY,OAAOA,QAAU,QAEnE,CAOO,SAASH,aACfG,KAAoB,EAEpB,OAAOC,MAAMC,OAAO,CAACF,MACtB,CASO,SAASD,cACfC,KAAoB,EAEpB,OAAOA,QAAU,MAAQ,OAAOA,QAAU,UAAY,CAACC,MAAMC,OAAO,CAACF,MACtE,CAeO,SAASJ,qBACfO,KAA8B,EAE9B,GAAIA,QAAU,KAAM,MAAO,CAAEC,KAAM,MAAO,EAC1C,GAAI,OAAOD,QAAU,UAAW,MAAO,CAAEC,KAAM,SAAU,EACzD,GAAI,OAAOD,QAAU,SAAU,CAC9B,OAAOE,OAAOC,SAAS,CAACH,OAAS,CAAEC,KAAM,SAAU,EAAI,CAAEA,KAAM,QAAS,CACzE,CAGAD,MACA,MAAO,CAAEC,KAAM,MAAO,CACvB,CA6UO,SAAST,aACfY,MAA4B,EAE5B,OAAOA,MACR"}
1
+ {"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { FromSchema, JSONSchema } from \"json-schema-to-ts\";\n\n// ─── Template Input ──────────────────────────────────────────────────────────\n// The engine accepts primitive values in addition to template strings.\n// When a non-string value is passed, it is treated as a literal passthrough:\n// analysis returns the inferred type, and execution returns the value as-is.\n\n/**\n * Object where each property is a `TemplateInput` (recursive).\n *\n * Allows passing an entire structure as a template:\n * ```\n * engine.analyze({\n * userName: \"{{name}}\",\n * userAge: \"{{age}}\",\n * nested: { x: \"{{foo}}\" },\n * }, inputSchema);\n * ```\n */\nexport interface TemplateInputObject {\n\t[key: string]: TemplateInput;\n}\n\n/**\n * Array where each element is a `TemplateInput` (recursive).\n *\n * Allows passing an array as a template:\n * ```\n * engine.analyze([\"{{name}}\", \"{{age}}\"], inputSchema);\n * engine.execute([\"{{name}}\", 42], data);\n * ```\n */\nexport type TemplateInputArray = TemplateInput[];\n\n/**\n * Input type accepted by the template engine.\n *\n * - `string` → standard Handlebars template (parsed and executed)\n * - `number` → numeric literal (passthrough)\n * - `boolean` → boolean literal (passthrough)\n * - `null` → null literal (passthrough)\n * - `TemplateInputArray` → array where each element is a `TemplateInput`\n * - `TemplateInputObject` → object where each property is a `TemplateInput`\n */\nexport type TemplateInput =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| TemplateInputArray\n\t| TemplateInputObject;\n\n/**\n * Checks whether a value is a non-string primitive literal (number, boolean, null).\n * These values are treated as passthrough by the engine.\n *\n * Note: objects (`TemplateInputObject`) and arrays (`TemplateInputArray`) are NOT literals.\n */\nexport function isLiteralInput(\n\tinput: TemplateInput,\n): input is number | boolean | null {\n\treturn (\n\t\tinput === null || (typeof input !== \"string\" && typeof input !== \"object\")\n\t);\n}\n\n/**\n * Checks whether a value is a template array (`TemplateInputArray`).\n * Template arrays are processed recursively by the engine:\n * each element is analyzed/executed individually and the result is an array.\n */\nexport function isArrayInput(\n\tinput: TemplateInput,\n): input is TemplateInputArray {\n\treturn Array.isArray(input);\n}\n\n/**\n * Checks whether a value is a template object (`TemplateInputObject`).\n * Template objects are processed recursively by the engine:\n * each property is analyzed/executed individually.\n *\n * Note: arrays are excluded — use `isArrayInput()` first.\n */\nexport function isObjectInput(\n\tinput: TemplateInput,\n): input is TemplateInputObject {\n\treturn input !== null && typeof input === \"object\" && !Array.isArray(input);\n}\n\n/**\n * Infers the JSON Schema of a non-string primitive value.\n *\n * @param value - The primitive value (number, boolean, null)\n * @returns The corresponding JSON Schema\n *\n * @example\n * ```\n * inferPrimitiveSchema(42) // → { type: \"number\" }\n * inferPrimitiveSchema(true) // → { type: \"boolean\" }\n * inferPrimitiveSchema(null) // → { type: \"null\" }\n * ```\n */\nexport function inferPrimitiveSchema(\n\tvalue: number | boolean | null,\n): JSONSchema7 {\n\tif (value === null) return { type: \"null\" };\n\tif (typeof value === \"boolean\") return { type: \"boolean\" };\n\tif (typeof value === \"number\") {\n\t\treturn Number.isInteger(value) ? { type: \"integer\" } : { type: \"number\" };\n\t}\n\t// Exhaustiveness check — all branches are covered above.\n\t// If the type of `value` changes, TypeScript will raise an error here.\n\tvalue satisfies never;\n\treturn { type: \"null\" };\n}\n\n// ─── Diagnostic Codes ────────────────────────────────────────────────────────\n// Machine-readable codes for each error/warning type, enabling the frontend\n// to react programmatically without parsing the human-readable message.\n\nexport type DiagnosticCode =\n\t/** The referenced property does not exist in the context schema */\n\t| \"UNKNOWN_PROPERTY\"\n\t/** Type mismatch (e.g. #each on a non-array) */\n\t| \"TYPE_MISMATCH\"\n\t/** A block helper is used without a required argument */\n\t| \"MISSING_ARGUMENT\"\n\t/** Unknown block helper (neither built-in nor registered) */\n\t| \"UNKNOWN_HELPER\"\n\t/** The expression cannot be statically analyzed */\n\t| \"UNANALYZABLE\"\n\t/** The {{key:N}} syntax is used but no identifierSchemas were provided */\n\t| \"MISSING_IDENTIFIER_SCHEMAS\"\n\t/** The identifier N does not exist in the provided identifierSchemas */\n\t| \"UNKNOWN_IDENTIFIER\"\n\t/** The property does not exist in the identifier's schema */\n\t| \"IDENTIFIER_PROPERTY_NOT_FOUND\"\n\t/** Syntax error in the template */\n\t| \"PARSE_ERROR\";\n\n// ─── Diagnostic Details ──────────────────────────────────────────────────────\n// Supplementary information to understand the exact cause of the error.\n// Designed to be easily JSON-serializable and consumable by a frontend.\n\nexport interface DiagnosticDetails {\n\t/** Path of the expression that caused the error (e.g. `\"user.name.foo\"`) */\n\tpath?: string;\n\t/** Name of the helper involved (for helper-related errors) */\n\thelperName?: string;\n\t/** What was expected (e.g. `\"array\"`, `\"property to exist\"`) */\n\texpected?: string;\n\t/** What was found (e.g. `\"string\"`, `\"undefined\"`) */\n\tactual?: string;\n\t/** Available properties in the current schema (for suggestions) */\n\tavailableProperties?: string[];\n\t/** Template identifier number (for `{{key:N}}` errors) */\n\tidentifier?: number;\n}\n\n// ─── Static Analysis Result ──────────────────────────────────────────────────\n\n/** Diagnostic produced by the static analyzer */\nexport interface TemplateDiagnostic {\n\t/** \"error\" blocks execution, \"warning\" is informational */\n\tseverity: \"error\" | \"warning\";\n\n\t/** Machine-readable code identifying the error type */\n\tcode: DiagnosticCode;\n\n\t/** Human-readable message describing the problem */\n\tmessage: string;\n\n\t/** Position in the template source (if available from the AST) */\n\tloc?: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t};\n\n\t/** Fragment of the template source around the error */\n\tsource?: string;\n\n\t/** Structured information for debugging and frontend display */\n\tdetails?: DiagnosticDetails;\n}\n\n/** Complete result of the static analysis */\nexport interface AnalysisResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** JSON Schema describing the template's return type */\n\toutputSchema: JSONSchema7;\n}\n\n/** Lightweight validation result (without output type inference) */\nexport interface ValidationResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n}\n\n// ─── Public Engine Options ───────────────────────────────────────────────────\n\nexport interface TemplateEngineOptions {\n\t/**\n\t * Capacity of the parsed AST cache. Each parsed template is cached\n\t * to avoid costly re-parsing on repeated calls.\n\t * @default 256\n\t */\n\tastCacheSize?: number;\n\n\t/**\n\t * Capacity of the compiled Handlebars template cache.\n\t * @default 256\n\t */\n\tcompilationCacheSize?: number;\n\n\t/**\n\t * Custom helpers to register during engine construction.\n\t *\n\t * Each entry describes a helper with its name, implementation,\n\t * expected parameters, and return type.\n\t *\n\t * @example\n\t * ```\n\t * const engine = new Typebars({\n\t * helpers: [\n\t * {\n\t * name: \"uppercase\",\n\t * description: \"Converts a string to uppercase\",\n\t * fn: (value: string) => String(value).toUpperCase(),\n\t * params: [\n\t * { name: \"value\", type: { type: \"string\" }, description: \"The string to convert\" },\n\t * ],\n\t * returnType: { type: \"string\" },\n\t * },\n\t * ],\n\t * });\n\t * ```\n\t */\n\thelpers?: HelperConfig[];\n}\n\n// ─── Execution Options ───────────────────────────────────────────────────────\n// Optional options object for `execute()`, replacing multiple positional\n// parameters for better ergonomics.\n\nexport interface ExecuteOptions {\n\t/** JSON Schema for pre-execution static validation */\n\tschema?: JSONSchema7;\n\t/** Data by identifier `{ [id]: { key: value } }` */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/** Schemas by identifier (for static validation with identifiers) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When provided with a primitive type, the execution result will be\n\t * coerced to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n}\n\n// ─── Combined Analyze-and-Execute Options ────────────────────────────────────\n// Optional options object for `analyzeAndExecute()`, grouping parameters\n// related to template identifiers.\n\nexport interface AnalyzeAndExecuteOptions {\n\t/** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Data by identifier `{ [id]: { key: value } }` for execution */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When provided with a primitive type, the execution result will be\n\t * coerced to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n}\n\n// ─── Custom Helpers ──────────────────────────────────────────────────────────\n// Allows registering custom helpers with their type signature for static\n// analysis support.\n\n/** Describes a parameter expected by a helper */\nexport interface HelperParam {\n\t/** Parameter name (for documentation / introspection) */\n\tname: string;\n\n\t/**\n\t * JSON Schema describing the expected type for this parameter.\n\t * Used for documentation and static validation.\n\t */\n\ttype?: JSONSchema7;\n\n\t/** Human-readable description of the parameter */\n\tdescription?: string;\n\n\t/**\n\t * Whether the parameter is optional.\n\t * @default false\n\t */\n\toptional?: boolean;\n}\n\n/**\n * Definition of a helper registerable via `registerHelper()`.\n *\n * Contains the runtime implementation and typing metadata\n * for static analysis.\n */\nexport interface HelperDefinition {\n\t/**\n\t * Runtime implementation of the helper — will be registered with Handlebars.\n\t *\n\t * For an inline helper `{{uppercase name}}`:\n\t * `(value: string) => string`\n\t *\n\t * For a block helper `{{#repeat count}}...{{/repeat}}`:\n\t * `function(this: any, count: number, options: Handlebars.HelperOptions) { ... }`\n\t */\n\t// biome-ignore lint/suspicious/noExplicitAny: Handlebars helper signatures are inherently dynamic\n\tfn: (...args: any[]) => unknown;\n\n\t/**\n\t * Parameters expected by the helper (for documentation and analysis).\n\t *\n\t * @example\n\t * ```\n\t * params: [\n\t * { name: \"value\", type: { type: \"number\" }, description: \"The value to round\" },\n\t * { name: \"precision\", type: { type: \"number\" }, description: \"Decimal places\", optional: true },\n\t * ]\n\t * ```\n\t */\n\tparams?: HelperParam[];\n\n\t/**\n\t * JSON Schema describing the helper's return type for static analysis.\n\t * @default { type: \"string\" }\n\t */\n\treturnType?: JSONSchema7;\n\n\t/** Human-readable description of the helper */\n\tdescription?: string;\n}\n\n/**\n * Full helper configuration for registration via the `Typebars({ helpers: [...] })`\n * constructor options.\n *\n * Extends `HelperDefinition` with a required `name`.\n *\n * @example\n * ```\n * const config: HelperConfig = {\n * name: \"round\",\n * description: \"Rounds a number to a given precision\",\n * fn: (value: number, precision?: number) => { ... },\n * params: [\n * { name: \"value\", type: { type: \"number\" } },\n * { name: \"precision\", type: { type: \"number\" }, optional: true },\n * ],\n * returnType: { type: \"number\" },\n * };\n * ```\n */\nexport interface HelperConfig extends HelperDefinition {\n\t/** Name of the helper as used in templates (e.g. `\"uppercase\"`) */\n\tname: string;\n}\n\n// ─── Automatic Type Inference via json-schema-to-ts ──────────────────────────\n// Allows `defineHelper()` to infer TypeScript types for `fn` arguments\n// from the JSON Schemas declared in `params`.\n\n/**\n * Param definition used for type inference.\n * Accepts `JSONSchema` from `json-schema-to-ts` to allow `FromSchema`\n * to resolve literal types.\n */\ntype TypedHelperParam = {\n\treadonly name: string;\n\treadonly type?: JSONSchema;\n\treadonly description?: string;\n\treadonly optional?: boolean;\n};\n\n/**\n * Infers the TypeScript type of a single parameter from its JSON Schema.\n * - If `optional: true`, the resolved type is unioned with `undefined`.\n * - If `type` is not provided, the type is `unknown`.\n */\ntype InferParamType<P> = P extends {\n\treadonly type: infer S extends JSONSchema;\n\treadonly optional: true;\n}\n\t? FromSchema<S> | undefined\n\t: P extends { readonly type: infer S extends JSONSchema }\n\t\t? FromSchema<S>\n\t\t: unknown;\n\n/**\n * Maps a tuple of `TypedHelperParam` to a tuple of inferred TypeScript types,\n * usable as the `fn` signature.\n *\n * @example\n * ```\n * type Args = InferArgs<readonly [\n * { name: \"a\"; type: { type: \"string\" } },\n * { name: \"b\"; type: { type: \"number\" }; optional: true },\n * ]>;\n * // => [string, number | undefined]\n * ```\n */\ntype InferArgs<P extends readonly TypedHelperParam[]> = {\n\t[K in keyof P]: InferParamType<P[K]>;\n};\n\n/**\n * Helper configuration with generic parameter inference.\n * Used exclusively by `defineHelper()`.\n */\ninterface TypedHelperConfig<P extends readonly TypedHelperParam[]> {\n\tname: string;\n\tdescription?: string;\n\tparams: P;\n\tfn: (...args: InferArgs<P>) => unknown;\n\treturnType?: JSONSchema;\n}\n\n/**\n * Creates a `HelperConfig` with automatic type inference for `fn` arguments\n * based on the JSON Schemas declared in `params`.\n *\n * The generic parameter `const P` preserves schema literal types\n * (equivalent of `as const`), enabling `FromSchema` to resolve the\n * corresponding TypeScript types.\n *\n * @example\n * ```\n * const helper = defineHelper({\n * name: \"concat\",\n * description: \"Concatenates two strings\",\n * params: [\n * { name: \"a\", type: { type: \"string\" }, description: \"First string\" },\n * { name: \"b\", type: { type: \"string\" }, description: \"Second string\" },\n * { name: \"sep\", type: { type: \"string\" }, description: \"Separator\", optional: true },\n * ],\n * fn: (a, b, sep) => {\n * // a: string, b: string, sep: string | undefined\n * const separator = sep ?? \"\";\n * return `${a}${separator}${b}`;\n * },\n * returnType: { type: \"string\" },\n * });\n * ```\n */\nexport function defineHelper<const P extends readonly TypedHelperParam[]>(\n\tconfig: TypedHelperConfig<P>,\n): HelperConfig {\n\treturn config as unknown as HelperConfig;\n}\n"],"names":["defineHelper","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","input","Array","isArray","value","type","Number","isInteger","config"],"mappings":"mPA6cgBA,sBAAAA,kBArWAC,8BAAAA,0BAhCAC,sBAAAA,kBAbAC,wBAAAA,oBA0BAC,uBAAAA,iBA1BT,SAASD,eACfE,KAAoB,EAEpB,OACCA,QAAU,MAAS,OAAOA,QAAU,UAAY,OAAOA,QAAU,QAEnE,CAOO,SAASH,aACfG,KAAoB,EAEpB,OAAOC,MAAMC,OAAO,CAACF,MACtB,CASO,SAASD,cACfC,KAAoB,EAEpB,OAAOA,QAAU,MAAQ,OAAOA,QAAU,UAAY,CAACC,MAAMC,OAAO,CAACF,MACtE,CAeO,SAASJ,qBACfO,KAA8B,EAE9B,GAAIA,QAAU,KAAM,MAAO,CAAEC,KAAM,MAAO,EAC1C,GAAI,OAAOD,QAAU,UAAW,MAAO,CAAEC,KAAM,SAAU,EACzD,GAAI,OAAOD,QAAU,SAAU,CAC9B,OAAOE,OAAOC,SAAS,CAACH,OAAS,CAAEC,KAAM,SAAU,EAAI,CAAEA,KAAM,QAAS,CACzE,CAGAD,MACA,MAAO,CAAEC,KAAM,MAAO,CACvB,CAyVO,SAAST,aACfY,MAA4B,EAE5B,OAAOA,MACR"}
@@ -14,6 +14,30 @@ interface AnalysisContext {
14
14
  identifierSchemas?: Record<number, JSONSchema7>;
15
15
  /** Registered custom helpers (for static analysis) */
16
16
  helpers?: Map<string, HelperDefinition>;
17
+ /**
18
+ * Explicit coercion schema provided by the caller.
19
+ * When set, static literal values like `"123"` will respect the type
20
+ * declared in this schema instead of being auto-detected by
21
+ * `detectLiteralType`. Unlike the previous `expectedOutputType`,
22
+ * this is NEVER derived from the inputSchema — it must be explicitly
23
+ * provided via the `coerceSchema` option.
24
+ */
25
+ coerceSchema?: JSONSchema7;
26
+ }
27
+ /** Options for the standalone `analyze()` function */
28
+ export interface AnalyzeOptions {
29
+ /** Schemas by template identifier (for the `{{key:N}}` syntax) */
30
+ identifierSchemas?: Record<number, JSONSchema7>;
31
+ /**
32
+ * Explicit coercion schema. When provided, static literal values
33
+ * will respect the types declared in this schema instead of being
34
+ * auto-detected by `detectLiteralType`.
35
+ *
36
+ * This schema is independent from the `inputSchema` (which describes
37
+ * available variables) — it only controls the output type inference
38
+ * for static content.
39
+ */
40
+ coerceSchema?: JSONSchema7;
17
41
  }
18
42
  /**
19
43
  * Statically analyzes a template against a JSON Schema v7 describing the
@@ -23,11 +47,11 @@ interface AnalysisContext {
23
47
  *
24
48
  * @param template - The template string (e.g. `"Hello {{user.name}}"`)
25
49
  * @param inputSchema - JSON Schema v7 describing the available variables
26
- * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`
50
+ * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)
27
51
  * @returns An `AnalysisResult` containing validity, diagnostics, and the
28
52
  * inferred output schema.
29
53
  */
30
- export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
54
+ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, options?: AnalyzeOptions): AnalysisResult;
31
55
  /**
32
56
  * Statically analyzes a template from an already-parsed AST.
33
57
  *
@@ -43,6 +67,12 @@ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema
43
67
  export declare function analyzeFromAst(ast: hbs.AST.Program, template: string, inputSchema: JSONSchema7, options?: {
44
68
  identifierSchemas?: Record<number, JSONSchema7>;
45
69
  helpers?: Map<string, HelperDefinition>;
70
+ /**
71
+ * Explicit coercion schema. When set, static literal values will
72
+ * respect the types declared in this schema instead of auto-detecting.
73
+ * Unlike `expectedOutputType`, this is NEVER derived from inputSchema.
74
+ */
75
+ coerceSchema?: JSONSchema7;
46
76
  }): AnalysisResult;
47
77
  /**
48
78
  * Infers the output type of a BlockStatement and validates its content.
@@ -1,2 +1,2 @@
1
- import{createMissingArgumentMessage,createPropertyNotFoundMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isThisExpression,parse}from"./parser.js";import{assertNoConditionalSchema,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateObjectAnalysis,deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";export function analyze(template,inputSchema,identifierSchemas){if(isArrayInput(template)){return analyzeArrayTemplate(template,inputSchema,identifierSchemas)}if(isObjectInput(template)){return analyzeObjectTemplate(template,inputSchema,identifierSchemas)}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=parse(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas})}function analyzeArrayTemplate(template,inputSchema,identifierSchemas){return aggregateArrayAnalysis(template.length,index=>analyze(template[index],inputSchema,identifierSchemas))}function analyzeObjectTemplate(template,inputSchema,identifierSchemas){return aggregateObjectAnalysis(Object.keys(template),key=>analyze(template[key],inputSchema,identifierSchemas))}export function analyzeFromAst(ast,template,inputSchema,options){assertNoConditionalSchema(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){assertNoConditionalSchema(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers};const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}return resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{}}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
1
+ import{createMissingArgumentMessage,createPropertyNotFoundMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isThisExpression,parse}from"./parser.js";import{assertNoConditionalSchema,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateObjectAnalysis,deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";export function analyze(template,inputSchema,options){if(isArrayInput(template)){return analyzeArrayTemplate(template,inputSchema,options)}if(isObjectInput(template)){return analyzeObjectTemplate(template,inputSchema,options)}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=parse(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema:options?.coerceSchema})}function analyzeArrayTemplate(template,inputSchema,options){return aggregateArrayAnalysis(template.length,index=>analyze(template[index],inputSchema,options))}function analyzeObjectTemplate(template,inputSchema,options){const coerceSchema=options?.coerceSchema;return aggregateObjectAnalysis(Object.keys(template),key=>{const childCoerceSchema=coerceSchema?resolveSchemaPath(coerceSchema,[key]):undefined;return analyze(template[key],inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema:childCoerceSchema})})}export function analyzeFromAst(ast,template,inputSchema,options){assertNoConditionalSchema(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){assertNoConditionalSchema(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}return resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{}}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const coercedType=ctx.coerceSchema?.type;if(typeof coercedType==="string"&&(coercedType==="string"||coercedType==="number"||coercedType==="integer"||coercedType==="boolean"||coercedType==="null")){return{type:coercedType}}const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
2
2
  //# sourceMappingURL=analyzer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\treturn analyzeFromAst(ast, template, inputSchema, { identifierSchemas });\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\tanalyze(template[key] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["createMissingArgumentMessage","createPropertyNotFoundMessage","createTypeMismatchMessage","createUnanalyzableMessage","createUnknownHelperMessage","detectLiteralType","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isThisExpression","parse","assertNoConditionalSchema","resolveArrayItems","resolveSchemaPath","simplifySchema","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateObjectAnalysis","deepEqual","extractSourceSnippet","getSchemaPropertyNames","analyze","template","inputSchema","identifierSchemas","analyzeArrayTemplate","analyzeObjectTemplate","valid","diagnostics","outputSchema","ast","analyzeFromAst","length","index","Object","keys","key","options","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","processStatement","stmt","type","undefined","processMustache","inferBlockType","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","singleExpr","singleBlock","allContent","every","s","text","map","value","join","trim","literalType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","thenType","inverse","elseType","saved","collectionSchema","itemSchema","result","innerSchema","param","expr","parentNode","resolveSubExpression","segments","cleanSegments","identifier","resolveWithIdentifier","fullPath","availableProperties","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","schema","anyOf","allOf","enum"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n\t/**\n\t * Explicit coercion schema provided by the caller.\n\t * When set, static literal values like `\"123\"` will respect the type\n\t * declared in this schema instead of being auto-detected by\n\t * `detectLiteralType`. Unlike the previous `expectedOutputType`,\n\t * this is NEVER derived from the inputSchema — it must be explicitly\n\t * provided via the `coerceSchema` option.\n\t */\n\tcoerceSchema?: JSONSchema7;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/** Options for the standalone `analyze()` function */\nexport interface AnalyzeOptions {\n\t/** Schemas by template identifier (for the `{{key:N}}` syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/**\n\t * Explicit coercion schema. When provided, static literal values\n\t * will respect the types declared in this schema instead of being\n\t * auto-detected by `detectLiteralType`.\n\t *\n\t * This schema is independent from the `inputSchema` (which describes\n\t * available variables) — it only controls the output type inference\n\t * for static content.\n\t */\n\tcoerceSchema?: JSONSchema7;\n}\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\toptions?: AnalyzeOptions,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, options);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(template, inputSchema, options);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\tcoerceSchema: options?.coerceSchema,\n\t});\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\toptions?: AnalyzeOptions,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, options),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\toptions?: AnalyzeOptions,\n): AnalysisResult {\n\tconst coerceSchema = options?.coerceSchema;\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) => {\n\t\t// When a coerceSchema is provided, resolve the child property schema\n\t\t// from the coercion schema. This allows deeply nested objects to\n\t\t// propagate coercion at every level.\n\t\tconst childCoerceSchema = coerceSchema\n\t\t\t? resolveSchemaPath(coerceSchema, [key])\n\t\t\t: undefined;\n\t\treturn analyze(template[key] as TemplateInput, inputSchema, {\n\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\tcoerceSchema: childCoerceSchema,\n\t\t});\n\t});\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t\t/**\n\t\t * Explicit coercion schema. When set, static literal values will\n\t\t * respect the types declared in this schema instead of auto-detecting.\n\t\t * Unlike `expectedOutputType`, this is NEVER derived from inputSchema.\n\t\t */\n\t\tcoerceSchema?: JSONSchema7;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t\tcoerceSchema: options?.coerceSchema,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\t// If an explicit coerceSchema was provided and declares a specific\n\t\t// primitive type, respect it instead of auto-detecting. For example,\n\t\t// \"123\" with coerceSchema `{ type: \"string\" }` should stay \"string\".\n\t\t// This only applies when coerceSchema is explicitly set — the\n\t\t// inputSchema is NEVER used for coercion.\n\t\tconst coercedType = ctx.coerceSchema?.type;\n\t\tif (\n\t\t\ttypeof coercedType === \"string\" &&\n\t\t\t(coercedType === \"string\" ||\n\t\t\t\tcoercedType === \"number\" ||\n\t\t\t\tcoercedType === \"integer\" ||\n\t\t\t\tcoercedType === \"boolean\" ||\n\t\t\t\tcoercedType === \"null\")\n\t\t) {\n\t\t\treturn { type: coercedType };\n\t\t}\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["createMissingArgumentMessage","createPropertyNotFoundMessage","createTypeMismatchMessage","createUnanalyzableMessage","createUnknownHelperMessage","detectLiteralType","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isThisExpression","parse","assertNoConditionalSchema","resolveArrayItems","resolveSchemaPath","simplifySchema","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateObjectAnalysis","deepEqual","extractSourceSnippet","getSchemaPropertyNames","analyze","template","inputSchema","options","analyzeArrayTemplate","analyzeObjectTemplate","valid","diagnostics","outputSchema","ast","analyzeFromAst","identifierSchemas","coerceSchema","length","index","Object","keys","key","childCoerceSchema","undefined","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","processStatement","stmt","type","processMustache","inferBlockType","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","singleExpr","singleBlock","allContent","every","s","text","map","value","join","trim","coercedType","literalType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","thenType","inverse","elseType","saved","collectionSchema","itemSchema","result","innerSchema","param","expr","parentNode","resolveSubExpression","segments","cleanSegments","identifier","resolveWithIdentifier","fullPath","availableProperties","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","schema","anyOf","allOf","enum"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type Handlebars from "handlebars";
2
2
  import type { JSONSchema7 } from "json-schema";
3
+ import type { AnalyzeOptions } from "./analyzer.js";
3
4
  import type { AnalysisResult, ExecuteOptions, HelperDefinition, ValidationResult } from "./types.js";
4
5
  import { type LRUCache } from "./utils.js";
5
6
  /** Internal options passed by Typebars during compilation */
@@ -70,10 +71,10 @@ export declare class CompiledTemplate {
70
71
  *
71
72
  * Since the AST is pre-parsed, this method never re-parses the template.
72
73
  *
73
- * @param inputSchema - JSON Schema describing the available variables
74
- * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`
74
+ * @param inputSchema - JSON Schema describing the available variables
75
+ * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)
75
76
  */
76
- analyze(inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
77
+ analyze(inputSchema: JSONSchema7, options?: AnalyzeOptions): AnalysisResult;
77
78
  /**
78
79
  * Validates the template against a schema without returning the output type.
79
80
  *
@@ -82,10 +83,10 @@ export declare class CompiledTemplate {
82
83
  * inference) is executed internally — this method provides no performance
83
84
  * gain, only a simplified API.
84
85
  *
85
- * @param inputSchema - JSON Schema describing the available variables
86
- * @param identifierSchemas - (optional) Schemas by identifier
86
+ * @param inputSchema - JSON Schema describing the available variables
87
+ * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)
87
88
  */
88
- validate(inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): ValidationResult;
89
+ validate(inputSchema: JSONSchema7, options?: AnalyzeOptions): ValidationResult;
89
90
  /**
90
91
  * Executes this template with the provided data.
91
92
  *
@@ -100,7 +101,7 @@ export declare class CompiledTemplate {
100
101
  * before execution. A `TemplateAnalysisError` is thrown on errors.
101
102
  *
102
103
  * @param data - The context data for rendering
103
- * @param options - Execution options (schema, identifierData, etc.)
104
+ * @param options - Execution options (schema, identifierData, coerceSchema, etc.)
104
105
  * @returns The execution result
105
106
  */
106
107
  execute(data: Record<string, unknown>, options?: ExecuteOptions): unknown;
@@ -110,14 +111,15 @@ export declare class CompiledTemplate {
110
111
  * Returns both the analysis result and the executed value.
111
112
  * If analysis fails, `value` is `undefined`.
112
113
  *
113
- * @param inputSchema - JSON Schema describing the available variables
114
- * @param data - The context data for rendering
115
- * @param options - Additional options
114
+ * @param inputSchema - JSON Schema describing the available variables
115
+ * @param data - The context data for rendering
116
+ * @param options - Additional options (identifierSchemas, identifierData, coerceSchema)
116
117
  * @returns `{ analysis, value }`
117
118
  */
118
119
  analyzeAndExecute(inputSchema: JSONSchema7, data: Record<string, unknown>, options?: {
119
120
  identifierSchemas?: Record<number, JSONSchema7>;
120
121
  identifierData?: Record<number, Record<string, unknown>>;
122
+ coerceSchema?: JSONSchema7;
121
123
  }): {
122
124
  analysis: AnalysisResult;
123
125
  value: unknown;
@@ -1,2 +1,2 @@
1
- function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{analyzeFromAst}from"./analyzer.js";import{TemplateAnalysisError}from"./errors.js";import{executeFromAst}from"./executor.js";import{inferPrimitiveSchema}from"./types.js";import{aggregateArrayAnalysis,aggregateArrayAnalysisAndExecution,aggregateObjectAnalysis,aggregateObjectAnalysisAndExecution}from"./utils.js";export class CompiledTemplate{get ast(){return this.state.kind==="template"?this.state.ast:null}get template(){return this.state.kind==="template"?this.state.source:""}static fromTemplate(ast,source,options){return new CompiledTemplate({kind:"template",ast,source},options)}static fromLiteral(value,options){return new CompiledTemplate({kind:"literal",value},options)}static fromObject(children,options){return new CompiledTemplate({kind:"object",children},options)}static fromArray(elements,options){return new CompiledTemplate({kind:"array",elements},options)}analyze(inputSchema,identifierSchemas){switch(this.state.kind){case"array":{const{elements}=this.state;return aggregateArrayAnalysis(elements.length,index=>{const element=elements[index];if(!element)throw new Error(`unreachable: missing element at index ${index}`);return element.analyze(inputSchema,identifierSchemas)})}case"object":{const{children}=this.state;return aggregateObjectAnalysis(Object.keys(children),key=>{const child=children[key];if(!child)throw new Error(`unreachable: missing child "${key}"`);return child.analyze(inputSchema,identifierSchemas)})}case"literal":return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(this.state.value)};case"template":return analyzeFromAst(this.state.ast,this.state.source,inputSchema,{identifierSchemas,helpers:this.options.helpers})}}validate(inputSchema,identifierSchemas){const analysis=this.analyze(inputSchema,identifierSchemas);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}execute(data,options){switch(this.state.kind){case"array":{const{elements}=this.state;const result=[];for(const element of elements){result.push(element.execute(data,options))}return result}case"object":{const{children}=this.state;const result={};for(const[key,child]of Object.entries(children)){result[key]=child.execute(data,options)}return result}case"literal":return this.state.value;case"template":{if(options?.schema){const analysis=this.analyze(options.schema,options.identifierSchemas);if(!analysis.valid){throw new TemplateAnalysisError(analysis.diagnostics)}}return executeFromAst(this.state.ast,this.state.source,data,this.buildExecutorContext(options))}}}analyzeAndExecute(inputSchema,data,options){switch(this.state.kind){case"array":{const{elements}=this.state;return aggregateArrayAnalysisAndExecution(elements.length,index=>{const element=elements[index];if(!element)throw new Error(`unreachable: missing element at index ${index}`);return element.analyzeAndExecute(inputSchema,data,options)})}case"object":{const{children}=this.state;return aggregateObjectAnalysisAndExecution(Object.keys(children),key=>children[key].analyzeAndExecute(inputSchema,data,options))}case"literal":return{analysis:{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(this.state.value)},value:this.state.value};case"template":{const analysis=this.analyze(inputSchema,options?.identifierSchemas);if(!analysis.valid){return{analysis,value:undefined}}const value=executeFromAst(this.state.ast,this.state.source,data,this.buildExecutorContext({identifierData:options?.identifierData}));return{analysis,value}}}}buildExecutorContext(options){return{identifierData:options?.identifierData,compiledTemplate:this.getOrCompileHbs(),hbs:this.options.hbs,compilationCache:this.options.compilationCache}}getOrCompileHbs(){if(!this.hbsCompiled){this.hbsCompiled=this.options.hbs.compile(this.template,{noEscape:true,strict:false})}return this.hbsCompiled}constructor(state,options){_define_property(this,"state",void 0);_define_property(this,"options",void 0);_define_property(this,"hbsCompiled",null);this.state=state;this.options=options}}
1
+ function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{analyzeFromAst}from"./analyzer.js";import{TemplateAnalysisError}from"./errors.js";import{executeFromAst}from"./executor.js";import{resolveSchemaPath}from"./schema-resolver.js";import{inferPrimitiveSchema}from"./types.js";import{aggregateArrayAnalysis,aggregateArrayAnalysisAndExecution,aggregateObjectAnalysis,aggregateObjectAnalysisAndExecution}from"./utils.js";export class CompiledTemplate{get ast(){return this.state.kind==="template"?this.state.ast:null}get template(){return this.state.kind==="template"?this.state.source:""}static fromTemplate(ast,source,options){return new CompiledTemplate({kind:"template",ast,source},options)}static fromLiteral(value,options){return new CompiledTemplate({kind:"literal",value},options)}static fromObject(children,options){return new CompiledTemplate({kind:"object",children},options)}static fromArray(elements,options){return new CompiledTemplate({kind:"array",elements},options)}analyze(inputSchema,options){switch(this.state.kind){case"array":{const{elements}=this.state;return aggregateArrayAnalysis(elements.length,index=>{const element=elements[index];if(!element)throw new Error(`unreachable: missing element at index ${index}`);return element.analyze(inputSchema,options)})}case"object":{const{children}=this.state;const coerceSchema=options?.coerceSchema;return aggregateObjectAnalysis(Object.keys(children),key=>{const child=children[key];if(!child)throw new Error(`unreachable: missing child "${key}"`);const childCoerceSchema=coerceSchema?resolveSchemaPath(coerceSchema,[key]):undefined;return child.analyze(inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema:childCoerceSchema})})}case"literal":return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(this.state.value)};case"template":return analyzeFromAst(this.state.ast,this.state.source,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.options.helpers,coerceSchema:options?.coerceSchema})}}validate(inputSchema,options){const analysis=this.analyze(inputSchema,options);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}execute(data,options){switch(this.state.kind){case"array":{const{elements}=this.state;const result=[];for(const element of elements){result.push(element.execute(data,options))}return result}case"object":{const{children}=this.state;const coerceSchema=options?.coerceSchema;const result={};for(const[key,child]of Object.entries(children)){const childCoerceSchema=coerceSchema?resolveSchemaPath(coerceSchema,[key]):undefined;result[key]=child.execute(data,{...options,coerceSchema:childCoerceSchema})}return result}case"literal":return this.state.value;case"template":{if(options?.schema){const analysis=this.analyze(options.schema,{identifierSchemas:options.identifierSchemas,coerceSchema:options.coerceSchema});if(!analysis.valid){throw new TemplateAnalysisError(analysis.diagnostics)}}return executeFromAst(this.state.ast,this.state.source,data,this.buildExecutorContext(options))}}}analyzeAndExecute(inputSchema,data,options){switch(this.state.kind){case"array":{const{elements}=this.state;return aggregateArrayAnalysisAndExecution(elements.length,index=>{const element=elements[index];if(!element)throw new Error(`unreachable: missing element at index ${index}`);return element.analyzeAndExecute(inputSchema,data,options)})}case"object":{const{children}=this.state;const coerceSchema=options?.coerceSchema;return aggregateObjectAnalysisAndExecution(Object.keys(children),key=>{const child=children[key];if(!child)throw new Error(`unreachable: missing child "${key}"`);const childCoerceSchema=coerceSchema?resolveSchemaPath(coerceSchema,[key]):undefined;return child.analyzeAndExecute(inputSchema,data,{identifierSchemas:options?.identifierSchemas,identifierData:options?.identifierData,coerceSchema:childCoerceSchema})})}case"literal":return{analysis:{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(this.state.value)},value:this.state.value};case"template":{const analysis=this.analyze(inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema:options?.coerceSchema});if(!analysis.valid){return{analysis,value:undefined}}const value=executeFromAst(this.state.ast,this.state.source,data,this.buildExecutorContext({identifierData:options?.identifierData,coerceSchema:options?.coerceSchema}));return{analysis,value}}}}buildExecutorContext(options){return{identifierData:options?.identifierData,compiledTemplate:this.getOrCompileHbs(),hbs:this.options.hbs,compilationCache:this.options.compilationCache,coerceSchema:options?.coerceSchema}}getOrCompileHbs(){if(!this.hbsCompiled){this.hbsCompiled=this.options.hbs.compile(this.template,{noEscape:true,strict:false})}return this.hbsCompiled}constructor(state,options){_define_property(this,"state",void 0);_define_property(this,"options",void 0);_define_property(this,"hbsCompiled",null);this.state=state;this.options=options}}
2
2
  //# sourceMappingURL=compiled-template.js.map