typebars 1.0.19 → 1.0.21

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { AnalysisResult, TemplateDiagnostic } from \"./types.ts\";\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n// Shared utility functions and classes used across the different modules\n// of the template engine.\n\n// ─── Deep Equality ───────────────────────────────────────────────────────────\n// Deep structural comparison for JSON-compatible values.\n// More robust than `JSON.stringify` because it is independent of key order\n// and does not allocate intermediate strings.\n\n/**\n * Recursively compares two JSON-compatible values.\n *\n * @param a - First value\n * @param b - Second value\n * @returns `true` if the two values are structurally identical\n *\n * @example\n * ```\n * deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // → true\n * deepEqual([1, 2], [1, 2]) // → true\n * deepEqual({ a: 1 }, { a: 2 }) // → false\n * ```\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n\t// Strict identity (covers primitives, same ref; NaN !== NaN is intentional)\n\tif (a === b) return true;\n\n\t// null is typeof \"object\" in JS — handle it separately\n\tif (a === null || b === null) return false;\n\tif (typeof a !== typeof b) return false;\n\n\t// ── Arrays ───────────────────────────────────────────────────────────────\n\tif (Array.isArray(a)) {\n\t\tif (!Array.isArray(b)) return false;\n\t\tif (a.length !== b.length) return false;\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!deepEqual(a[i], b[i])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ── Objects ──────────────────────────────────────────────────────────────\n\tif (typeof a === \"object\") {\n\t\tconst objA = a as Record<string, unknown>;\n\t\tconst objB = b as Record<string, unknown>;\n\t\tconst keysA = Object.keys(objA);\n\t\tconst keysB = Object.keys(objB);\n\n\t\tif (keysA.length !== keysB.length) return false;\n\n\t\tfor (const key of keysA) {\n\t\t\tif (!(key in objB) || !deepEqual(objA[key], objB[key])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// Different primitives (already covered by a === b at the top)\n\treturn false;\n}\n\n// ─── LRU Cache ───────────────────────────────────────────────────────────────\n// Fixed-capacity cache with Least Recently Used (LRU) eviction.\n// Leverages `Map` insertion order to track access: the oldest entry\n// is always in the first position.\n\n/**\n * Simple fixed-capacity LRU cache.\n *\n * @example\n * ```\n * const cache = new LRUCache<string, number>(2);\n * cache.set(\"a\", 1);\n * cache.set(\"b\", 2);\n * cache.get(\"a\"); // → 1 (marks \"a\" as recently used)\n * cache.set(\"c\", 3); // evicts \"b\" (least recently used)\n * cache.get(\"b\"); // → undefined\n * ```\n */\nexport class LRUCache<K, V> {\n\tprivate readonly cache = new Map<K, V>();\n\n\tconstructor(private readonly capacity: number) {\n\t\tif (capacity < 1) {\n\t\t\tthrow new Error(\"LRUCache capacity must be at least 1\");\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves a value from the cache. Returns `undefined` if absent.\n\t * Marks the entry as recently used.\n\t */\n\tget(key: K): V | undefined {\n\t\tif (!this.cache.has(key)) return undefined;\n\n\t\t// Move to the end of the Map (= most recently used)\n\t\tconst value = this.cache.get(key) as V;\n\t\tthis.cache.delete(key);\n\t\tthis.cache.set(key, value);\n\t\treturn value;\n\t}\n\n\t/**\n\t * Inserts or updates a value in the cache.\n\t * If the cache is full, evicts the least recently used entry.\n\t */\n\tset(key: K, value: V): void {\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key);\n\t\t} else if (this.cache.size >= this.capacity) {\n\t\t\t// Evict the first entry (the oldest one)\n\t\t\tconst oldestKey = this.cache.keys().next().value;\n\t\t\tif (oldestKey !== undefined) {\n\t\t\t\tthis.cache.delete(oldestKey);\n\t\t\t}\n\t\t}\n\t\tthis.cache.set(key, value);\n\t}\n\n\t/**\n\t * Checks whether a key exists in the cache (without affecting LRU order).\n\t */\n\thas(key: K): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Removes an entry from the cache.\n\t * @returns `true` if the entry existed and was removed\n\t */\n\tdelete(key: K): boolean {\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/** Clears the entire cache. */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/** Number of entries currently in the cache. */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n\n// ─── Source Snippet Extraction ────────────────────────────────────────────────\n// Used to enrich diagnostics with the template fragment that caused the error.\n\n/**\n * Extracts a template fragment around a given position.\n *\n * @param template - The full template source\n * @param loc - The position (line/column, 1-based) of the error\n * @returns The corresponding code fragment (trimmed)\n */\nexport function extractSourceSnippet(\n\ttemplate: string,\n\tloc: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t},\n): string {\n\tconst lines = template.split(\"\\n\");\n\tconst startLine = loc.start.line - 1; // 0-based\n\tconst endLine = loc.end.line - 1;\n\n\tif (startLine < 0 || startLine >= lines.length) return \"\";\n\n\tif (startLine === endLine) {\n\t\t// Same line — extract the portion between start.column and end.column\n\t\tconst line = lines[startLine] ?? \"\";\n\t\treturn line.trim();\n\t}\n\n\t// Multi-line — return the affected lines\n\tconst clampedEnd = Math.min(endLine, lines.length - 1);\n\treturn lines\n\t\t.slice(startLine, clampedEnd + 1)\n\t\t.map((l) => l.trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\n// ─── Schema Properties ──────────────────────────────────────────────────────\n// Utility for listing available properties in a schema, used to enrich\n// error messages with suggestions.\n\n/**\n * Lists the declared property names in a JSON Schema.\n * Returns an empty array if the schema has no `properties`.\n */\nexport function getSchemaPropertyNames(schema: JSONSchema7): string[] {\n\tconst names = new Set<string>();\n\n\t// Direct properties\n\tif (schema.properties) {\n\t\tfor (const key of Object.keys(schema.properties)) {\n\t\t\tnames.add(key);\n\t\t}\n\t}\n\n\t// Properties within combinators\n\tfor (const combinator of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n\t\tconst branches = schema[combinator];\n\t\tif (branches) {\n\t\t\tfor (const branch of branches) {\n\t\t\t\tif (typeof branch === \"boolean\") continue;\n\t\t\t\tif (branch.properties) {\n\t\t\t\t\tfor (const key of Object.keys(branch.properties)) {\n\t\t\t\t\t\tnames.add(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Array.from(names).sort();\n}\n\n// ─── Object Analysis Aggregation ─────────────────────────────────────────────\n// Factorizes the common recursion pattern over template objects:\n// iterate the keys, analyze each entry via a callback, accumulate\n// diagnostics, and build the output object schema.\n//\n// Used by:\n// - `analyzer.ts` (analyzeObjectTemplate)\n// - `Typebars.analyzeObject()` (typebars.ts)\n// - `CompiledTemplate.analyze()` in object mode (compiled-template.ts)\n\n/**\n * Aggregates analysis results from a set of named entries into a single\n * `AnalysisResult` with an object-typed `outputSchema`.\n *\n * @param keys - The keys of the object to analyze\n * @param analyzeEntry - Callback that analyzes an entry by its key\n * @returns An aggregated `AnalysisResult`\n *\n * @example\n * ```\n * aggregateObjectAnalysis(\n * Object.keys(template),\n * (key) => analyze(template[key], inputSchema),\n * );\n * ```\n */\nexport function aggregateObjectAnalysis(\n\tkeys: string[],\n\tanalyzeEntry: (key: string) => AnalysisResult,\n): AnalysisResult {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = analyzeEntry(key);\n\t\tif (!child.valid) allValid = false;\n\t\tallDiagnostics.push(...child.diagnostics);\n\t\tproperties[key] = child.outputSchema;\n\t}\n\n\treturn {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n}\n\n/**\n * Aggregates both analysis **and** execution results from a set of named\n * entries. Returns the aggregated `AnalysisResult` and the object of\n * executed values (or `undefined` if at least one entry is invalid).\n *\n * @param keys - The keys of the object\n * @param processEntry - Callback that analyzes and executes an entry by its key\n * @returns Aggregated `{ analysis, value }`\n */\nexport function aggregateObjectAnalysisAndExecution(\n\tkeys: string[],\n\tprocessEntry: (key: string) => { analysis: AnalysisResult; value: unknown },\n): { analysis: AnalysisResult; value: unknown } {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tconst resultValues: Record<string, unknown> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = processEntry(key);\n\t\tif (!child.analysis.valid) allValid = false;\n\t\tallDiagnostics.push(...child.analysis.diagnostics);\n\t\tproperties[key] = child.analysis.outputSchema;\n\t\tresultValues[key] = child.value;\n\t}\n\n\tconst analysis: AnalysisResult = {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n\n\treturn {\n\t\tanalysis,\n\t\tvalue: allValid ? resultValues : undefined,\n\t};\n}\n\n// ─── Array Analysis Aggregation ──────────────────────────────────────────────\n// Factorizes the common recursion pattern over template arrays:\n// iterate the elements, analyze each entry via a callback, accumulate\n// diagnostics, and build the output array schema with a proper `items`.\n//\n// Used by:\n// - `analyzer.ts` (analyzeArrayTemplate)\n// - `Typebars.analyze()` (typebars.ts)\n// - `CompiledTemplate.analyze()` in array mode (compiled-template.ts)\n\n/**\n * Computes the `items` schema for an array from the output schemas of its\n * elements:\n * - If all elements share the same schema → that schema\n * - If there are multiple distinct schemas → `{ oneOf: [...] }`\n * - If the array is empty → `{}` (any type)\n */\nfunction computeArrayItemsSchema(elementSchemas: JSONSchema7[]): JSONSchema7 {\n\tif (elementSchemas.length === 0) return {};\n\n\t// Deduplicate schemas using deep equality\n\tconst unique: JSONSchema7[] = [];\n\tfor (const schema of elementSchemas) {\n\t\tif (!unique.some((u) => deepEqual(u, schema))) {\n\t\t\tunique.push(schema);\n\t\t}\n\t}\n\n\tif (unique.length === 1) {\n\t\treturn unique[0] as JSONSchema7;\n\t}\n\n\treturn { oneOf: unique };\n}\n\n/**\n * Aggregates analysis results from a set of array elements into a single\n * `AnalysisResult` with an array-typed `outputSchema`.\n *\n * @param length - The number of elements in the array\n * @param analyzeEntry - Callback that analyzes an element by its index\n * @returns An aggregated `AnalysisResult`\n *\n * @example\n * ```\n * aggregateArrayAnalysis(\n * template.length,\n * (index) => analyze(template[index], inputSchema),\n * );\n * ```\n */\nexport function aggregateArrayAnalysis(\n\tlength: number,\n\tanalyzeEntry: (index: number) => AnalysisResult,\n): AnalysisResult {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst elementSchemas: JSONSchema7[] = [];\n\tlet allValid = true;\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst child = analyzeEntry(i);\n\t\tif (!child.valid) allValid = false;\n\t\tallDiagnostics.push(...child.diagnostics);\n\t\telementSchemas.push(child.outputSchema);\n\t}\n\n\treturn {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"array\",\n\t\t\titems: computeArrayItemsSchema(elementSchemas),\n\t\t},\n\t};\n}\n\n/**\n * Aggregates both analysis **and** execution results from a set of array\n * elements. Returns the aggregated `AnalysisResult` and the array of\n * executed values (or `undefined` if at least one element is invalid).\n *\n * @param length - The number of elements\n * @param processEntry - Callback that analyzes and executes an element by index\n * @returns Aggregated `{ analysis, value }`\n */\nexport function aggregateArrayAnalysisAndExecution(\n\tlength: number,\n\tprocessEntry: (index: number) => { analysis: AnalysisResult; value: unknown },\n): { analysis: AnalysisResult; value: unknown } {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst elementSchemas: JSONSchema7[] = [];\n\tconst resultValues: unknown[] = [];\n\tlet allValid = true;\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst child = processEntry(i);\n\t\tif (!child.analysis.valid) allValid = false;\n\t\tallDiagnostics.push(...child.analysis.diagnostics);\n\t\telementSchemas.push(child.analysis.outputSchema);\n\t\tresultValues.push(child.value);\n\t}\n\n\tconst analysis: AnalysisResult = {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"array\",\n\t\t\titems: computeArrayItemsSchema(elementSchemas),\n\t\t},\n\t};\n\n\treturn {\n\t\tanalysis,\n\t\tvalue: allValid ? resultValues : undefined,\n\t};\n}\n"],"names":["LRUCache","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","deepEqual","extractSourceSnippet","getSchemaPropertyNames","a","b","Array","isArray","length","i","objA","objB","keysA","Object","keys","keysB","key","get","cache","has","undefined","value","delete","set","size","capacity","oldestKey","next","clear","Map","Error","template","loc","lines","split","startLine","start","line","endLine","end","trim","clampedEnd","Math","min","slice","map","l","trimEnd","join","schema","names","Set","properties","add","combinator","branches","branch","from","sort","analyzeEntry","allDiagnostics","allValid","child","valid","push","diagnostics","outputSchema","type","required","processEntry","resultValues","analysis","computeArrayItemsSchema","elementSchemas","unique","some","u","oneOf","items"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utils.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { AnalysisResult, TemplateDiagnostic } from \"./types.ts\";\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n// Shared utility functions and classes used across the different modules\n// of the template engine.\n\n// ─── Deep Equality ───────────────────────────────────────────────────────────\n// Deep structural comparison for JSON-compatible values.\n// More robust than `JSON.stringify` because it is independent of key order\n// and does not allocate intermediate strings.\n\n/**\n * Recursively compares two JSON-compatible values.\n *\n * @param a - First value\n * @param b - Second value\n * @returns `true` if the two values are structurally identical\n *\n * @example\n * ```\n * deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // → true\n * deepEqual([1, 2], [1, 2]) // → true\n * deepEqual({ a: 1 }, { a: 2 }) // → false\n * ```\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n\t// Strict identity (covers primitives, same ref; NaN !== NaN is intentional)\n\tif (a === b) return true;\n\n\t// null is typeof \"object\" in JS — handle it separately\n\tif (a === null || b === null) return false;\n\tif (typeof a !== typeof b) return false;\n\n\t// ── Arrays ───────────────────────────────────────────────────────────────\n\tif (Array.isArray(a)) {\n\t\tif (!Array.isArray(b)) return false;\n\t\tif (a.length !== b.length) return false;\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!deepEqual(a[i], b[i])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ── Objects ──────────────────────────────────────────────────────────────\n\tif (typeof a === \"object\") {\n\t\tconst objA = a as Record<string, unknown>;\n\t\tconst objB = b as Record<string, unknown>;\n\t\tconst keysA = Object.keys(objA);\n\t\tconst keysB = Object.keys(objB);\n\n\t\tif (keysA.length !== keysB.length) return false;\n\n\t\tfor (const key of keysA) {\n\t\t\tif (!(key in objB) || !deepEqual(objA[key], objB[key])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// Different primitives (already covered by a === b at the top)\n\treturn false;\n}\n\n// ─── LRU Cache ───────────────────────────────────────────────────────────────\n// Fixed-capacity cache with Least Recently Used (LRU) eviction.\n// Leverages `Map` insertion order to track access: the oldest entry\n// is always in the first position.\n\n/**\n * Simple fixed-capacity LRU cache.\n *\n * @example\n * ```\n * const cache = new LRUCache<string, number>(2);\n * cache.set(\"a\", 1);\n * cache.set(\"b\", 2);\n * cache.get(\"a\"); // → 1 (marks \"a\" as recently used)\n * cache.set(\"c\", 3); // evicts \"b\" (least recently used)\n * cache.get(\"b\"); // → undefined\n * ```\n */\nexport class LRUCache<K, V> {\n\tprivate readonly cache = new Map<K, V>();\n\n\tconstructor(private readonly capacity: number) {\n\t\tif (capacity < 1) {\n\t\t\tthrow new Error(\"LRUCache capacity must be at least 1\");\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves a value from the cache. Returns `undefined` if absent.\n\t * Marks the entry as recently used.\n\t */\n\tget(key: K): V | undefined {\n\t\tif (!this.cache.has(key)) return undefined;\n\n\t\t// Move to the end of the Map (= most recently used)\n\t\tconst value = this.cache.get(key) as V;\n\t\tthis.cache.delete(key);\n\t\tthis.cache.set(key, value);\n\t\treturn value;\n\t}\n\n\t/**\n\t * Inserts or updates a value in the cache.\n\t * If the cache is full, evicts the least recently used entry.\n\t */\n\tset(key: K, value: V): void {\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key);\n\t\t} else if (this.cache.size >= this.capacity) {\n\t\t\t// Evict the first entry (the oldest one)\n\t\t\tconst oldestKey = this.cache.keys().next().value;\n\t\t\tif (oldestKey !== undefined) {\n\t\t\t\tthis.cache.delete(oldestKey);\n\t\t\t}\n\t\t}\n\t\tthis.cache.set(key, value);\n\t}\n\n\t/**\n\t * Checks whether a key exists in the cache (without affecting LRU order).\n\t */\n\thas(key: K): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Removes an entry from the cache.\n\t * @returns `true` if the entry existed and was removed\n\t */\n\tdelete(key: K): boolean {\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/** Clears the entire cache. */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/** Number of entries currently in the cache. */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n\n// ─── Source Snippet Extraction ────────────────────────────────────────────────\n// Used to enrich diagnostics with the template fragment that caused the error.\n\n/**\n * Extracts a template fragment around a given position.\n *\n * @param template - The full template source\n * @param loc - The position (line/column, 1-based) of the error\n * @returns The corresponding code fragment (trimmed)\n */\nexport function extractSourceSnippet(\n\ttemplate: string,\n\tloc: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t},\n): string {\n\tconst lines = template.split(\"\\n\");\n\tconst startLine = loc.start.line - 1; // 0-based\n\tconst endLine = loc.end.line - 1;\n\n\tif (startLine < 0 || startLine >= lines.length) return \"\";\n\n\tif (startLine === endLine) {\n\t\t// Same line — extract the portion between start.column and end.column\n\t\tconst line = lines[startLine] ?? \"\";\n\t\treturn line.trim();\n\t}\n\n\t// Multi-line — return the affected lines\n\tconst clampedEnd = Math.min(endLine, lines.length - 1);\n\treturn lines\n\t\t.slice(startLine, clampedEnd + 1)\n\t\t.map((l) => l.trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\n// ─── Schema Properties ──────────────────────────────────────────────────────\n// Utility for listing available properties in a schema, used to enrich\n// error messages with suggestions.\n\n/**\n * Lists the declared property names in a JSON Schema.\n * Returns an empty array if the schema has no `properties`.\n */\nexport function getSchemaPropertyNames(schema: JSONSchema7): string[] {\n\tconst names = new Set<string>();\n\n\t// Direct properties\n\tif (schema.properties) {\n\t\tfor (const key of Object.keys(schema.properties)) {\n\t\t\tnames.add(key);\n\t\t}\n\t}\n\n\t// Properties within combinators\n\tfor (const combinator of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n\t\tconst branches = schema[combinator];\n\t\tif (branches) {\n\t\t\tfor (const branch of branches) {\n\t\t\t\tif (typeof branch === \"boolean\") continue;\n\t\t\t\tif (branch.properties) {\n\t\t\t\t\tfor (const key of Object.keys(branch.properties)) {\n\t\t\t\t\t\tnames.add(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Array.from(names).sort();\n}\n\n// ─── Object Analysis Aggregation ─────────────────────────────────────────────\n// Factorizes the common recursion pattern over template objects:\n// iterate the keys, analyze each entry via a callback, accumulate\n// diagnostics, and build the output object schema.\n//\n// Used by:\n// - `analyzer.ts` (analyzeObjectTemplate)\n// - `Typebars.analyzeObject()` (typebars.ts)\n// - `CompiledTemplate.analyze()` in object mode (compiled-template.ts)\n\n/**\n * Aggregates analysis results from a set of named entries into a single\n * `AnalysisResult` with an object-typed `outputSchema`.\n *\n * @param keys - The keys of the object to analyze\n * @param analyzeEntry - Callback that analyzes an entry by its key\n * @returns An aggregated `AnalysisResult`\n *\n * @example\n * ```\n * aggregateObjectAnalysis(\n * Object.keys(template),\n * (key) => analyze(template[key], inputSchema),\n * );\n * ```\n */\nexport function aggregateObjectAnalysis(\n\tkeys: string[],\n\tanalyzeEntry: (key: string) => AnalysisResult,\n): AnalysisResult {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = analyzeEntry(key);\n\t\tif (!child.valid) allValid = false;\n\t\tallDiagnostics.push(...child.diagnostics);\n\t\tproperties[key] = child.outputSchema;\n\t}\n\n\treturn {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n}\n\n/**\n * Aggregates both analysis **and** execution results from a set of named\n * entries. Returns the aggregated `AnalysisResult` and the object of\n * executed values (or `undefined` if at least one entry is invalid).\n *\n * @param keys - The keys of the object\n * @param processEntry - Callback that analyzes and executes an entry by its key\n * @returns Aggregated `{ analysis, value }`\n */\nexport function aggregateObjectAnalysisAndExecution(\n\tkeys: string[],\n\tprocessEntry: (key: string) => { analysis: AnalysisResult; value: unknown },\n): { analysis: AnalysisResult; value: unknown } {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tconst resultValues: Record<string, unknown> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = processEntry(key);\n\t\tif (!child.analysis.valid) allValid = false;\n\t\tallDiagnostics.push(...child.analysis.diagnostics);\n\t\tproperties[key] = child.analysis.outputSchema;\n\t\tresultValues[key] = child.value;\n\t}\n\n\tconst analysis: AnalysisResult = {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n\n\treturn {\n\t\tanalysis,\n\t\tvalue: allValid ? resultValues : undefined,\n\t};\n}\n\n// ─── Array Analysis Aggregation ──────────────────────────────────────────────\n// Factorizes the common recursion pattern over template arrays:\n// iterate the elements, analyze each entry via a callback, accumulate\n// diagnostics, and build the output array schema with a proper `items`.\n//\n// Used by:\n// - `analyzer.ts` (analyzeArrayTemplate)\n// - `Typebars.analyze()` (typebars.ts)\n// - `CompiledTemplate.analyze()` in array mode (compiled-template.ts)\n\n/**\n * Computes the `items` schema for an array from the output schemas of its\n * elements:\n * - If all elements share the same schema → that schema\n * - If there are multiple distinct schemas → `{ oneOf: [...] }`\n * - If the array is empty → `{}` (any type)\n */\nfunction computeArrayItemsSchema(elementSchemas: JSONSchema7[]): JSONSchema7 {\n\tif (elementSchemas.length === 0) return {};\n\n\t// Deduplicate schemas using deep equality\n\tconst unique: JSONSchema7[] = [];\n\tfor (const schema of elementSchemas) {\n\t\tif (!unique.some((u) => deepEqual(u, schema))) {\n\t\t\tunique.push(schema);\n\t\t}\n\t}\n\n\tif (unique.length === 1) {\n\t\treturn unique[0] as JSONSchema7;\n\t}\n\n\treturn { oneOf: unique };\n}\n\n/**\n * Aggregates analysis results from a set of array elements into a single\n * `AnalysisResult` with an array-typed `outputSchema`.\n *\n * @param length - The number of elements in the array\n * @param analyzeEntry - Callback that analyzes an element by its index\n * @returns An aggregated `AnalysisResult`\n *\n * @example\n * ```\n * aggregateArrayAnalysis(\n * template.length,\n * (index) => analyze(template[index], inputSchema),\n * );\n * ```\n */\nexport function aggregateArrayAnalysis(\n\tlength: number,\n\tanalyzeEntry: (index: number) => AnalysisResult,\n): AnalysisResult {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst elementSchemas: JSONSchema7[] = [];\n\tlet allValid = true;\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst child = analyzeEntry(i);\n\t\tif (!child.valid) allValid = false;\n\t\tallDiagnostics.push(...child.diagnostics);\n\t\telementSchemas.push(child.outputSchema);\n\t}\n\n\treturn {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"array\",\n\t\t\titems: computeArrayItemsSchema(elementSchemas),\n\t\t\tminItems: length,\n\t\t\tmaxItems: length,\n\t\t},\n\t};\n}\n\n/**\n * Aggregates both analysis **and** execution results from a set of array\n * elements. Returns the aggregated `AnalysisResult` and the array of\n * executed values (or `undefined` if at least one element is invalid).\n *\n * @param length - The number of elements\n * @param processEntry - Callback that analyzes and executes an element by index\n * @returns Aggregated `{ analysis, value }`\n */\nexport function aggregateArrayAnalysisAndExecution(\n\tlength: number,\n\tprocessEntry: (index: number) => { analysis: AnalysisResult; value: unknown },\n): { analysis: AnalysisResult; value: unknown } {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst elementSchemas: JSONSchema7[] = [];\n\tconst resultValues: unknown[] = [];\n\tlet allValid = true;\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst child = processEntry(i);\n\t\tif (!child.analysis.valid) allValid = false;\n\t\tallDiagnostics.push(...child.analysis.diagnostics);\n\t\telementSchemas.push(child.analysis.outputSchema);\n\t\tresultValues.push(child.value);\n\t}\n\n\tconst analysis: AnalysisResult = {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"array\",\n\t\t\titems: computeArrayItemsSchema(elementSchemas),\n\t\t\tminItems: length,\n\t\t\tmaxItems: length,\n\t\t},\n\t};\n\n\treturn {\n\t\tanalysis,\n\t\tvalue: allValid ? resultValues : undefined,\n\t};\n}\n"],"names":["LRUCache","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","deepEqual","extractSourceSnippet","getSchemaPropertyNames","a","b","Array","isArray","length","i","objA","objB","keysA","Object","keys","keysB","key","get","cache","has","undefined","value","delete","set","size","capacity","oldestKey","next","clear","Map","Error","template","loc","lines","split","startLine","start","line","endLine","end","trim","clampedEnd","Math","min","slice","map","l","trimEnd","join","schema","names","Set","properties","add","combinator","branches","branch","from","sort","analyzeEntry","allDiagnostics","allValid","child","valid","push","diagnostics","outputSchema","type","required","processEntry","resultValues","analysis","computeArrayItemsSchema","elementSchemas","unique","some","u","oneOf","items","minItems","maxItems"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{assertNoConditionalSchema,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":return Number(text);case"boolean":return text==="true";case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}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);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}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 processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}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")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}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(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}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 resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}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);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}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 processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}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{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{findConditionalSchemaLocations,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":{if(text==="")return undefined;const num=Number(text);if(Number.isNaN(num))return undefined;if(targetType==="integer"&&!Number.isInteger(num))return undefined;return num}case"boolean":{const lower=text.toLowerCase();if(lower==="true")return true;if(lower==="false")return false;return undefined}case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}export function analyzeFromAst(ast,template,inputSchema={},options){const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const conditionalLocations=findConditionalSchemaLocations(inputSchema);for(const loc of conditionalLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){const idLocations=findConditionalSchemaLocations(idSchema,`/identifierSchemas/${id}`);for(const loc of idLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}}}if(ctx.diagnostics.length>0){return{valid:false,diagnostics:ctx.diagnostics,outputSchema:{}}}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);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}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 processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}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")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}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(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}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 resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}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);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}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 processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}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