Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement →
Sign In

@contractkit/core

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contractkit/core - npm Package Compare versions

Comparing version
0.18.0
to
0.19.0
+370
coverage/src/apply-options-defaults.ts.html
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/apply-options-defaults.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> apply-options-defaults.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">98% </span>
<span class="quiet">Statements</span>
<span class='fraction'>49/50</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">95.34% </span>
<span class="quiet">Branches</span>
<span class='fraction'>41/43</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>9/9</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>38/38</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Normalization pass — merges options-level request/response headers into each operation's AST
* so downstream codegen plugins remain unaware of the options-vs-operation distinction.
*
* Runs after parsing and before validation. Mutates the root in place.
*
* Merge rules:
* - Request headers: applied to every operation. Op-level headers with the same name win.
* If the op declares `headers: none`, the merge is skipped. If the op uses a referenced
* or compound type for headers (rather than inline params), the merge is skipped with a warning.
* - Response headers: applied to every status code on every operation, regardless of body
* presence or status class. Per-status `headers: none` skips the merge for that code.
* Per-status header with same name wins.
*/
import type { CkRootNode, OpOperationNode, OpParamNode, OpResponseHeaderNode, OpResponseNode, OpRootNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
type RootWithGlobals = Pick&lt;CkRootNode, 'file' | 'routes' | 'requestHeaders' | 'responseHeaders'&gt; | Pick&lt;OpRootNode, 'file' | 'routes' | 'requestHeaders' | 'responseHeaders'&gt;;
&nbsp;
export function applyOptionsDefaults(root: RootWithGlobals, diag: DiagnosticCollector): void {
const reqGlobals = root.requestHeaders ?? [];
const resGlobals = root.responseHeaders ?? [];
if (reqGlobals.length === 0 &amp;&amp; resGlobals.length === 0) return;
&nbsp;
for (const route of root.routes) {
const pathParams = new Set([...route.path.matchAll(/\{(\w+)\}/g)].map(m =&gt; m[1]!));
for (const g of reqGlobals) {
if (pathParams.has(g.name)) {
diag.error(root.file, route.loc.line, `Global request header '${g.name}' collides with path parameter on '${route.path}'`);
}
}
&nbsp;
for (const op of route.operations) {
mergeRequestHeaders(op, reqGlobals, root.file, diag);
for (const res of op.responses) mergeResponseHeaders(res, resGlobals);
}
}
}
&nbsp;
function mergeRequestHeaders(op: OpOperationNode, globals: OpResponseHeaderNode[], file: string, diag: DiagnosticCollector): void {
if (globals.length === 0 || op.requestHeadersOptOut) return;
&nbsp;
const src = op.headers;
if (src &amp;&amp; (src.kind === 'ref' || src.kind === 'type')) {
diag.warn(
file,
op.loc.line,
`Operation uses a referenced headers type — global request headers from options are not merged. Inline the headers or use 'headers: none' to silence.`,
);
return;
}
&nbsp;
const existing: OpParamNode[] = src?.kind === 'params' ? src.nodes : [];
const existingNames = new Set(existing.map(p =&gt; p.name));
const additions: OpParamNode[] = [];
const overridden: string[] = [];
&nbsp;
for (const g of globals) {
if (existingNames.has(g.name)) {
overridden.push(g.name);
continue;
}
additions.push(headerToParam(g, op));
}
&nbsp;
if (overridden.length &gt; 0) {
diag.warn(file, op.loc.line, `Operation overrides global request header${overridden.length &gt; 1 ? <span class="branch-0 cbranch-no" title="branch not covered" >'s' : '</span>'} ${overridden.map(n =&gt; `'${n}'`).join(', ')}`);
}
&nbsp;
if (additions.length === 0 &amp;&amp; src) return;
op.headers = { kind: 'params', nodes: [...additions, ...existing] };
}
&nbsp;
function mergeResponseHeaders(res: OpResponseNode, globals: OpResponseHeaderNode[]): void {
if (globals.length === 0 || res.headersOptOut) return;
&nbsp;
const existing = res.headers ?? [];
const existingNames = new Set(existing.map(h =&gt; h.name));
const additions = globals.filter(g =&gt; !existingNames.has(g.name));
if (additions.length === 0 &amp;&amp; res.headers) return;
&nbsp;
res.headers = [...additions, ...existing];
}
&nbsp;
function headerToParam(h: OpResponseHeaderNode, op: OpOperationNode): OpParamNode {
const param: OpParamNode = {
name: h.name,
optional: h.optional,
nullable: false,
type: h.type,
loc: op.loc,
};
<span class="missing-if-branch" title="if path not taken" >I</span>if (h.description) <span class="cstat-no" title="statement not covered" >param.description = h.description;</span>
return param;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/apply-variable-substitution.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> apply-variable-substitution.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.56% </span>
<span class="quiet">Statements</span>
<span class='fraction'>40/41</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">94.87% </span>
<span class="quiet">Branches</span>
<span class='fraction'>37/39</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>33/33</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">349x</span>
<span class="cline-any cline-yes">319x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">270x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Normalization pass — substitutes `{{name}}` references in every string-bearing field
* of the AST with values from `root.meta` (the file's `options { keys }` block) or, when
* absent, from a workspace-wide `fallbackKeys` map (typically merged from each plugin's
* `options.keys` in `contractkit.config.json`).
*
* Runs in the CLI between `parseCk` and `decomposeCk`, after `applyOptionsDefaults`. It
* deliberately does NOT run inside `parseCk` so the prettier plugin sees the un-substituted
* source form and can round-trip the file.
*
* Substitution rules:
* - `{{name}}` → value lookup; warns and emits the literal string `undefined` when missing.
* - `\{{name}}` → literal `{{name}}` (no substitution, no warning).
*
* `root.meta` is itself excluded from the walk — keys are not recursively substituted.
*/
import type { CkRootNode, OpRootNode, SourceLocation } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
const SUBSTITUTION_RE = /\\\{\{(\w+)\}\}|\{\{(\w+)\}\}/g;
&nbsp;
type Root = CkRootNode | OpRootNode;
&nbsp;
export function applyVariableSubstitution(root: Root, diag: DiagnosticCollector, fallbackKeys: Record&lt;string, string&gt; = {}): void {
const file = root.file;
const meta = root.meta ?? <span class="branch-1 cbranch-no" title="branch not covered" >{};</span>
&nbsp;
const lookup = (name: string): string | undefined =&gt; {
if (Object.prototype.hasOwnProperty.call(meta, name)) return meta[name];
if (Object.prototype.hasOwnProperty.call(fallbackKeys, name)) return fallbackKeys[name];
return undefined;
};
&nbsp;
const substitute = (input: string, line: number): string =&gt; {
if (!input.includes('{{')) return input;
return input.replace(SUBSTITUTION_RE, (_match, escapedName: string | undefined, varName: string | undefined) =&gt; {
if (escapedName !== undefined) return `{{${escapedName}}}`;
const value = lookup(varName!);
if (value === undefined) {
diag.warn(file, line, `Unknown variable '{{${varName}}}'`);
return 'undefined';
}
return value;
});
};
&nbsp;
walk(root, substitute, 0, /* isRoot */ true);
}
&nbsp;
function isLoc(value: unknown): value is SourceLocation {
return typeof value === 'object' &amp;&amp; value !== null &amp;&amp; typeof (value as SourceLocation).line === 'number' &amp;&amp; typeof (value as SourceLocation).file === 'string';
}
&nbsp;
function walk(node: unknown, substitute: (s: string, line: number) =&gt; string, currentLine: number, isRoot: boolean): void {
<span class="missing-if-branch" title="if path not taken" >I</span>if (node === null || typeof node !== 'object') <span class="cstat-no" title="statement not covered" >return;</span>
&nbsp;
if (Array.isArray(node)) {
for (const item of node) walk(item, substitute, currentLine, false);
return;
}
&nbsp;
const obj = node as Record&lt;string, unknown&gt;;
&nbsp;
// Promote `loc.line` to the running context so warnings emitted while walking
// this node's descendants can attribute themselves accurately.
const ownLoc = obj['loc'];
const lineHere = isLoc(ownLoc) ? ownLoc.line : currentLine;
&nbsp;
for (const key of Object.keys(obj)) {
// Skip book-keeping fields and the substitution source itself.
if (key === 'loc' || key === 'file') continue;
if (isRoot &amp;&amp; key === 'meta') continue;
&nbsp;
const value = obj[key];
if (typeof value === 'string') {
obj[key] = substitute(value, lineHere);
} else if (value !== null &amp;&amp; typeof value === 'object') {
walk(value, substitute, lineHere, false);
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/ast.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> ast.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Statements</span>
<span class='fraction'>9/10</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">57.14% </span>
<span class="quiet">Branches</span>
<span class='fraction'>4/7</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>3/3</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">85.71% </span>
<span class="quiet">Lines</span>
<span class='fraction'>6/7</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a>
<a name='L239'></a><a href='#L239'>239</a>
<a name='L240'></a><a href='#L240'>240</a>
<a name='L241'></a><a href='#L241'>241</a>
<a name='L242'></a><a href='#L242'>242</a>
<a name='L243'></a><a href='#L243'>243</a>
<a name='L244'></a><a href='#L244'>244</a>
<a name='L245'></a><a href='#L245'>245</a>
<a name='L246'></a><a href='#L246'>246</a>
<a name='L247'></a><a href='#L247'>247</a>
<a name='L248'></a><a href='#L248'>248</a>
<a name='L249'></a><a href='#L249'>249</a>
<a name='L250'></a><a href='#L250'>250</a>
<a name='L251'></a><a href='#L251'>251</a>
<a name='L252'></a><a href='#L252'>252</a>
<a name='L253'></a><a href='#L253'>253</a>
<a name='L254'></a><a href='#L254'>254</a>
<a name='L255'></a><a href='#L255'>255</a>
<a name='L256'></a><a href='#L256'>256</a>
<a name='L257'></a><a href='#L257'>257</a>
<a name='L258'></a><a href='#L258'>258</a>
<a name='L259'></a><a href='#L259'>259</a>
<a name='L260'></a><a href='#L260'>260</a>
<a name='L261'></a><a href='#L261'>261</a>
<a name='L262'></a><a href='#L262'>262</a>
<a name='L263'></a><a href='#L263'>263</a>
<a name='L264'></a><a href='#L264'>264</a>
<a name='L265'></a><a href='#L265'>265</a>
<a name='L266'></a><a href='#L266'>266</a>
<a name='L267'></a><a href='#L267'>267</a>
<a name='L268'></a><a href='#L268'>268</a>
<a name='L269'></a><a href='#L269'>269</a>
<a name='L270'></a><a href='#L270'>270</a>
<a name='L271'></a><a href='#L271'>271</a>
<a name='L272'></a><a href='#L272'>272</a>
<a name='L273'></a><a href='#L273'>273</a>
<a name='L274'></a><a href='#L274'>274</a>
<a name='L275'></a><a href='#L275'>275</a>
<a name='L276'></a><a href='#L276'>276</a>
<a name='L277'></a><a href='#L277'>277</a>
<a name='L278'></a><a href='#L278'>278</a>
<a name='L279'></a><a href='#L279'>279</a>
<a name='L280'></a><a href='#L280'>280</a>
<a name='L281'></a><a href='#L281'>281</a>
<a name='L282'></a><a href='#L282'>282</a>
<a name='L283'></a><a href='#L283'>283</a>
<a name='L284'></a><a href='#L284'>284</a>
<a name='L285'></a><a href='#L285'>285</a>
<a name='L286'></a><a href='#L286'>286</a>
<a name='L287'></a><a href='#L287'>287</a>
<a name='L288'></a><a href='#L288'>288</a>
<a name='L289'></a><a href='#L289'>289</a>
<a name='L290'></a><a href='#L290'>290</a>
<a name='L291'></a><a href='#L291'>291</a>
<a name='L292'></a><a href='#L292'>292</a>
<a name='L293'></a><a href='#L293'>293</a>
<a name='L294'></a><a href='#L294'>294</a>
<a name='L295'></a><a href='#L295'>295</a>
<a name='L296'></a><a href='#L296'>296</a>
<a name='L297'></a><a href='#L297'>297</a>
<a name='L298'></a><a href='#L298'>298</a>
<a name='L299'></a><a href='#L299'>299</a>
<a name='L300'></a><a href='#L300'>300</a>
<a name='L301'></a><a href='#L301'>301</a>
<a name='L302'></a><a href='#L302'>302</a>
<a name='L303'></a><a href='#L303'>303</a>
<a name='L304'></a><a href='#L304'>304</a>
<a name='L305'></a><a href='#L305'>305</a>
<a name='L306'></a><a href='#L306'>306</a>
<a name='L307'></a><a href='#L307'>307</a>
<a name='L308'></a><a href='#L308'>308</a>
<a name='L309'></a><a href='#L309'>309</a>
<a name='L310'></a><a href='#L310'>310</a>
<a name='L311'></a><a href='#L311'>311</a>
<a name='L312'></a><a href='#L312'>312</a>
<a name='L313'></a><a href='#L313'>313</a>
<a name='L314'></a><a href='#L314'>314</a>
<a name='L315'></a><a href='#L315'>315</a>
<a name='L316'></a><a href='#L316'>316</a>
<a name='L317'></a><a href='#L317'>317</a>
<a name='L318'></a><a href='#L318'>318</a>
<a name='L319'></a><a href='#L319'>319</a>
<a name='L320'></a><a href='#L320'>320</a>
<a name='L321'></a><a href='#L321'>321</a>
<a name='L322'></a><a href='#L322'>322</a>
<a name='L323'></a><a href='#L323'>323</a>
<a name='L324'></a><a href='#L324'>324</a>
<a name='L325'></a><a href='#L325'>325</a>
<a name='L326'></a><a href='#L326'>326</a>
<a name='L327'></a><a href='#L327'>327</a>
<a name='L328'></a><a href='#L328'>328</a>
<a name='L329'></a><a href='#L329'>329</a>
<a name='L330'></a><a href='#L330'>330</a>
<a name='L331'></a><a href='#L331'>331</a>
<a name='L332'></a><a href='#L332'>332</a>
<a name='L333'></a><a href='#L333'>333</a>
<a name='L334'></a><a href='#L334'>334</a>
<a name='L335'></a><a href='#L335'>335</a>
<a name='L336'></a><a href='#L336'>336</a>
<a name='L337'></a><a href='#L337'>337</a>
<a name='L338'></a><a href='#L338'>338</a>
<a name='L339'></a><a href='#L339'>339</a>
<a name='L340'></a><a href='#L340'>340</a>
<a name='L341'></a><a href='#L341'>341</a>
<a name='L342'></a><a href='#L342'>342</a>
<a name='L343'></a><a href='#L343'>343</a>
<a name='L344'></a><a href='#L344'>344</a>
<a name='L345'></a><a href='#L345'>345</a>
<a name='L346'></a><a href='#L346'>346</a>
<a name='L347'></a><a href='#L347'>347</a>
<a name='L348'></a><a href='#L348'>348</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// ─── Shared ────────────────────────────────────────────────────────────────
&nbsp;
export interface SourceLocation {
file: string;
line: number;
}
&nbsp;
export const SCALAR_NAMES: ReadonlySet&lt;string&gt; = new Set&lt;ScalarTypeNode['name']&gt;([
'string',
'number',
'int',
'bigint',
'boolean',
'date',
'time',
'datetime',
'duration',
'interval',
'email',
'url',
'uuid',
'unknown',
'null',
'object',
'binary',
'json',
]);
&nbsp;
// ─── Contracts AST (.ck) ──────────────────────────────────────────────────
&nbsp;
export type ContractTypeNode =
| ScalarTypeNode
| ArrayTypeNode
| TupleTypeNode
| RecordTypeNode
| EnumTypeNode
| LiteralTypeNode
| UnionTypeNode
| DiscriminatedUnionTypeNode
| IntersectionTypeNode
| ModelRefTypeNode
| InlineObjectTypeNode
| LazyTypeNode;
&nbsp;
export interface ScalarTypeNode {
kind: 'scalar';
name:
| 'string'
| 'number'
| 'int'
| 'bigint'
| 'boolean'
| 'date'
| 'time'
| 'datetime'
| 'duration'
| 'interval'
| 'email'
| 'url'
| 'uuid'
| 'unknown'
| 'null'
| 'object'
| 'binary'
| 'json';
min?: number | bigint | string;
max?: number | bigint | string;
len?: number;
regex?: string;
format?: string;
}
&nbsp;
export interface ArrayTypeNode {
kind: 'array';
item: ContractTypeNode;
min?: number;
max?: number;
}
&nbsp;
export interface TupleTypeNode {
kind: 'tuple';
items: ContractTypeNode[];
}
&nbsp;
export interface RecordTypeNode {
kind: 'record';
key: ContractTypeNode;
value: ContractTypeNode;
}
&nbsp;
export interface EnumTypeNode {
kind: 'enum';
values: string[];
}
&nbsp;
export interface LiteralTypeNode {
kind: 'literal';
value: string | number | boolean;
}
&nbsp;
export interface UnionTypeNode {
kind: 'union';
members: ContractTypeNode[];
}
&nbsp;
export interface DiscriminatedUnionTypeNode {
kind: 'discriminatedUnion';
discriminator: string;
members: ContractTypeNode[];
}
&nbsp;
export interface ModelRefTypeNode {
kind: 'ref';
name: string;
lazy?: boolean;
}
&nbsp;
export interface InlineObjectTypeNode {
kind: 'inlineObject';
fields: FieldNode[];
mode?: ObjectMode;
}
&nbsp;
export interface IntersectionTypeNode {
kind: 'intersection';
members: ContractTypeNode[];
}
&nbsp;
export interface LazyTypeNode {
kind: 'lazy';
inner: ContractTypeNode;
}
&nbsp;
export interface FieldNode {
name: string;
optional: boolean;
nullable: boolean;
visibility: 'readonly' | 'writeonly' | 'normal';
type: ContractTypeNode;
default?: string | number | boolean;
deprecated?: boolean;
/** Set when the field is declared with the `override` modifier — used by inheritance validation
* to confirm the field is intentionally redeclaring a conflicting base field. */
override?: boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface ModelNode {
kind: 'model';
name: string;
/** Names of base contracts this model extends, in left-to-right declaration order.
* `contract C: A &amp; B &amp; { ... }` produces `bases: ['A', 'B']`. Empty/undefined for non-inherited models. */
bases?: string[];
fields: FieldNode[];
type?: ContractTypeNode; // type alias: Name: typeExpression (fields will be empty)
mode?: ObjectMode; // object validation mode — defaults to 'strict'
inputCase?: 'camel' | 'snake' | 'pascal'; // format(input=) — key casing of incoming data
outputCase?: 'camel' | 'snake' | 'pascal'; // format(output=) — key casing of emitted data
deprecated?: boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface ContractRootNode {
kind: 'contractRoot';
meta: Record&lt;string, string&gt;;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record&lt;string, string&gt;;
models: ModelNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array&lt;{ line: number; text: string }&gt;;
}
&nbsp;
// ─── Operations AST (.op) ──────────────────────────────────────────────────
&nbsp;
/** Constrained security declaration. */
export interface SecurityFields {
/** Named policy required for this endpoint, or `false` to explicitly bypass policy enforcement. */
policy?: string | false;
/** Inline comment attached to the `policy:` line. */
policyDescription?: string;
loc: SourceLocation;
}
&nbsp;
/** Sentinel value for explicitly public endpoints (`security: none`). */
export const SECURITY_NONE = 'none' as const;
export type SecurityNone = typeof SECURITY_NONE;
&nbsp;
/** Security declaration: explicit public (`none`), or constrained auth fields. */
export type SecurityNode = SecurityNone | SecurityFields;
&nbsp;
export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
&nbsp;
/** Controls how Zod handles unknown keys on an object schema. */
export type ObjectMode = 'strict' | 'strip' | 'loose';
&nbsp;
/** Visibility/lifecycle modifiers on routes and operations.
* `public` is operation-only: overrides inherited route-level modifiers. */
export type RouteModifier = 'internal' | 'deprecated' | 'public';
&nbsp;
/** JSON-like value tree used for `plugins` entries — strings, numbers, booleans, null, nested objects, and arrays. */
export type PluginValue = string | number | boolean | null | PluginValue[] | { [key: string]: PluginValue };
&nbsp;
export interface OpParamNode {
name: string;
optional: boolean;
nullable: boolean;
type: ContractTypeNode;
default?: string | number | boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
/** Either inline param declarations, a single type reference name, or a ContractTypeNode. */
export type ParamSource = { kind: 'params'; nodes: OpParamNode[] } | { kind: 'ref'; name: string } | { kind: 'type'; node: ContractTypeNode };
&nbsp;
/**
* Recognized request mime types that codegen has dedicated handling for. Other strings are
* still permitted (any RFC 6838-shaped `type/subtype`) and pass through unchanged; codegen
* falls back to a JSON-ish default for `+json` suffixes and a generic body for everything else.
*/
export type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
&nbsp;
export interface OpRequestBodyNode {
contentType: string;
bodyType: ContractTypeNode;
}
&nbsp;
export interface OpRequestNode {
bodies: OpRequestBodyNode[];
}
&nbsp;
export interface OpResponseHeaderNode {
/** Header name as written in the .ck source (preserves casing/hyphens, e.g. `preference-applied`, `ETag`). */
name: string;
optional: boolean;
type: ContractTypeNode;
description?: string;
}
&nbsp;
export interface OpResponseNode {
statusCode: number;
contentType?: string;
bodyType?: ContractTypeNode;
/** Declared response headers for this status code. Undefined = none declared. */
headers?: OpResponseHeaderNode[];
/** Set when the status code body declares `headers: none` — suppresses options-level response header merge for this code. */
headersOptOut?: boolean;
}
&nbsp;
export interface OpOperationNode {
method: HttpMethod;
name?: string; // e.g. "Create an Offer" — human-readable name for docs/collections
service?: string; // e.g. "LedgerService.updateCategoryNesting"
sdk?: string; // e.g. "getUser" — explicit SDK method name
/** HMAC signature key name for this endpoint (e.g. `WEBHOOK_SECRET`). */
signature?: string;
/** Inline comment attached to the `signature:` line. */
signatureDescription?: string;
request?: OpRequestNode;
responses: OpResponseNode[];
query?: ParamSource;
queryMode?: ObjectMode;
headers?: ParamSource;
headersMode?: ObjectMode;
/** Set when the operation declares `headers: none` — suppresses options-level request header merge for this op. */
requestHeadersOptOut?: boolean;
security?: SecurityNode; // overrides config default; "none" = explicitly public
/** Explicit modifiers. undefined = inherit from route; [] or array = override. */
modifiers?: RouteModifier[];
/** Raw plugin values from the grammar, e.g. `{ bruno: { template: "file://request-token.yml" } }`. */
plugins?: Record&lt;string, PluginValue&gt;;
/** Resolved plugin extension values keyed by plugin name. Populated by the CLI resolver — same shape as `plugins`, but every `file://` URL string is replaced with the file's contents. Never set by the parser. */
pluginExtensions?: Record&lt;string, PluginValue&gt;;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface OpRouteNode {
path: string;
params?: ParamSource;
paramsMode?: ObjectMode;
operations: OpOperationNode[];
/** Route-level modifiers — cascade to all operations unless overridden. */
modifiers?: RouteModifier[];
/** Route-level security default — cascades to operations that have no explicit security declaration. */
security?: SecurityNode;
description?: string;
loc: SourceLocation;
}
&nbsp;
/**
* Resolves the effective modifiers for an operation, applying route-level cascade.
* If the operation specifies any explicit modifiers, those replace (not merge) the route's.
* `public` on an operation acts as an explicit override that clears inherited modifiers;
* it is stripped from the returned array (it is not a codegen modifier itself).
*/
export function resolveModifiers(route: OpRouteNode, op: OpOperationNode): RouteModifier[] {
const raw = op.modifiers ?? <span class="branch-1 cbranch-no" title="branch not covered" >route.modifiers </span>?? <span class="branch-2 cbranch-no" title="branch not covered" >[];</span>
return raw.filter(m =&gt; m !== 'public');
}
&nbsp;
/**
* Resolves the effective security for an operation, applying cascade from operation → route → file.
* Operation-level security always wins; if absent, the route's security is used; if absent, the file's.
*/
export function resolveSecurity(route: OpRouteNode, op: OpOperationNode, root?: OpRootNode): SecurityNode | undefined {
if (op.security !== undefined) return op.security;
<span class="missing-if-branch" title="else path not taken" >E</span>if (route.security !== undefined) return route.security;
<span class="cstat-no" title="statement not covered" > return root?.security;</span>
}
&nbsp;
export interface OpRootNode {
kind: 'opRoot';
meta: Record&lt;string, string&gt;;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record&lt;string, string&gt;;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
routes: OpRouteNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array&lt;{ line: number; text: string }&gt;;
}
&nbsp;
// ─── Unified AST (.ck) ───────────────────────────────────────────────────
&nbsp;
export interface CkRootNode {
kind: 'ckRoot';
meta: Record&lt;string, string&gt;;
services: Record&lt;string, string&gt;;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
models: ModelNode[];
routes: OpRouteNode[];
file: string;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/content-type.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> content-type.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/10</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/7</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Helpers for classifying mime types declared on `request:` / `response:` blocks.
*
* Mime types are stored on the AST as lowercase strings (see `normalizeContentType` in
* `semantics.ts`). The grammar's `mimeType` rule accepts any RFC 6838-shaped `type/subtype`
* — codegen branches on the well-known shapes below and falls through to JSON-style
* serialization for vendor JSON types like `application/vnd.api+json`.
*/
&nbsp;
/**
* True if the mime is JSON-shaped: exactly `application/json`, or any `+json` structured
* suffix per RFC 6839 (e.g. `application/vnd.api+json`, `application/ld+json`). Codegen
* uses this to decide between JSON serialization and the form/multipart paths.
*/
export function <span class="fstat-no" title="function not covered" >isJsonMime(c</span>ontentType: string): boolean {
<span class="cstat-no" title="statement not covered" > if (contentType === 'application/json') <span class="cstat-no" title="statement not covered" >return true;</span></span>
<span class="cstat-no" title="statement not covered" > return /^[a-z0-9.+_-]+\/[a-z0-9.+_-]+\+json$/.test(contentType);</span>
}
&nbsp;
/**
* Classify a mime into the codegen category that drives serialization, validation,
* and the language types emitted for request bodies and response bodies.
*
* - `json` — `application/json` or any `+json` structured suffix; full schema validation,
* JSON.stringify on the wire, model types in source.
* - `urlencoded` / `multipart` — form-style request bodies with dedicated handling.
* - `text` — `text/*`; body is a raw string with no schema enforcement.
* - `binary` — anything else (`application/octet-stream`, `image/*`, etc.); body is opaque
* bytes (`Blob` / `bytes`) with no schema enforcement.
*/
export type ContentTypeCategory = 'json' | 'urlencoded' | 'multipart' | 'text' | 'binary';
&nbsp;
export function <span class="fstat-no" title="function not covered" >classifyContentType(c</span>ontentType: string): ContentTypeCategory {
<span class="cstat-no" title="statement not covered" > if (contentType === 'application/x-www-form-urlencoded') <span class="cstat-no" title="statement not covered" >return 'urlencoded';</span></span>
<span class="cstat-no" title="statement not covered" > if (contentType === 'multipart/form-data') <span class="cstat-no" title="statement not covered" >return 'multipart';</span></span>
<span class="cstat-no" title="statement not covered" > if (isJsonMime(contentType)) <span class="cstat-no" title="statement not covered" >return 'json';</span></span>
<span class="cstat-no" title="statement not covered" > if (contentType.startsWith('text/')) <span class="cstat-no" title="statement not covered" >return 'text';</span></span>
<span class="cstat-no" title="statement not covered" > return 'binary';</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/decompose.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> decompose.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>3/3</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>3/3</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Decompose a unified CkRootNode into separate ContractRootNode + OpRootNode
* for downstream codegen functions that operate on models or routes independently.
*/
import type { CkRootNode, ContractRootNode, OpRootNode } from './ast.js';
&nbsp;
export function decomposeCk(ck: CkRootNode): { contract: ContractRootNode; op: OpRootNode } {
const contract: ContractRootNode = {
kind: 'contractRoot',
meta: { ...ck.meta },
services: { ...ck.services },
models: ck.models,
file: ck.file,
};
const op: OpRootNode = {
kind: 'opRoot',
meta: { ...ck.meta },
services: { ...ck.services },
security: ck.security,
routes: ck.routes,
file: ck.file,
};
return { contract, op };
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/diagnostics.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> diagnostics.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>15/15</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>8/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>12/12</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">239x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">44x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export interface Diagnostic {
file: string;
line: number;
message: string;
severity: 'error' | 'warning';
/** Optional stable identifier — consumed by editor tooling to dispatch quick-fixes. */
code?: string;
}
&nbsp;
export class DiagnosticCollector {
private diagnostics: Diagnostic[] = [];
&nbsp;
error(file: string, line: number, message: string, code?: string): void {
this.diagnostics.push({ file, line, message, severity: 'error', code });
}
&nbsp;
warn(file: string, line: number, message: string, code?: string): void {
this.diagnostics.push({ file, line, message, severity: 'warning', code });
}
&nbsp;
hasErrors(): boolean {
return this.diagnostics.some(d =&gt; d.severity === 'error');
}
&nbsp;
getAll(): Diagnostic[] {
return [...this.diagnostics];
}
&nbsp;
report(): void {
for (const d of this.diagnostics) {
const prefix = d.severity === 'error' ? '\x1b[31mERROR\x1b[0m' : '\x1b[33mWARN\x1b[0m';
console.error(`${prefix} ${d.file}:${d.line} ${d.message}`);
}
const errors = this.diagnostics.filter(d =&gt; d.severity === 'error').length;
const warns = this.diagnostics.filter(d =&gt; d.severity === 'warning').length;
if (errors &gt; 0 || warns &gt; 0) {
console.error(`\n${errors} error(s), ${warns} warning(s)`);
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/grammar.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> grammar.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">75% </span>
<span class="quiet">Statements</span>
<span class='fraction'>6/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">75% </span>
<span class="quiet">Lines</span>
<span class='fraction'>6/8</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Grammar loader — compiles the Ohm grammar and exports the grammar object.
* The .ohm file is the source of truth for the Contract DSL syntax.
*/
import * as ohm from 'ohm-js';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
&nbsp;
// Load the grammar source at module init (singleton pattern).
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
&nbsp;
// In dev (src/), the .ohm file is a sibling.
// In dist, the .ohm file is copied to the dist directory by the build.
let grammarPath = join(__dirname, 'contractkit.ohm');
&nbsp;
let grammarSource: string;
try {
grammarSource = readFileSync(grammarPath, 'utf-8');
} catch {
// Fallback: try relative to cwd (for tests running from source)
<span class="cstat-no" title="statement not covered" > grammarPath = join(process.cwd(), 'src', 'contractkit.ohm');</span>
<span class="cstat-no" title="statement not covered" > grammarSource = readFileSync(grammarPath, 'utf-8');</span>
}
&nbsp;
export const grammar: ohm.Grammar = ohm.grammar(grammarSource);
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/incremental.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> incremental.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.1% </span>
<span class="quiet">Statements</span>
<span class='fraction'>67/69</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">93.47% </span>
<span class="quiet">Branches</span>
<span class='fraction'>43/46</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">90.9% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/11</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">98.21% </span>
<span class="quiet">Lines</span>
<span class='fraction'>55/56</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { createHash } from 'node:crypto';
&nbsp;
/**
* Schema version for the on-disk manifest. Bumped if the manifest shape itself changes.
* Plugin codegen changes are tracked separately via `codegenVersion`.
*/
export const INCREMENTAL_MANIFEST_VERSION = 2;
&nbsp;
/** A single output file produced by a plugin: relative path within the plugin's output dir + content. */
export interface IncrementalOutputFile {
relativePath: string;
content: string;
}
&nbsp;
/** Cache record for one plugin "unit" — the smallest thing a plugin can decide to regenerate or reuse. */
export interface IncrementalUnitRecord {
/** Hash covering every input that affects this unit's output. */
fingerprint: string;
/** Relative paths this unit emitted last time. Used to verify on-disk presence on cache hit, and to detect file removals. */
files: string[];
}
&nbsp;
/**
* Persistent on-disk record describing what a plugin produced last run. Plugins write
* one of these per output dir. The `version` and `codegenVersion` fields together
* decide whether the cache can be honored at all on the current run.
*/
export interface IncrementalManifest {
version: number;
/** Plugin-defined version string. Bump in the plugin code to force a full regen across all units. */
codegenVersion: string;
/** Every relative path the plugin tracks (units' files plus global files plus the manifest itself). Used to compute deletions on the next run. */
files: string[];
/** Per-unit cache records keyed by stable unit ID. */
units: Record&lt;string, IncrementalUnitRecord&gt;;
}
&nbsp;
/** A cacheable codegen unit — a stable key + the inputs to fingerprint + a deferred renderer that's only invoked on cache miss. */
export interface IncrementalUnit {
/** Stable ID across runs (e.g. `&lt;file&gt;::&lt;METHOD&gt; &lt;path&gt;` for ops, `&lt;file&gt;` for per-file outputs). Renaming the source breaks the key, which is the correct behavior — old files get cleaned up, new ones are emitted. */
key: string;
/** Pre-computed fingerprint covering every input that affects this unit's output. The caller is responsible for hashing in any cross-unit inputs (e.g. transitively-referenced models, plugin config). */
fingerprint: string;
/** Renders the unit's output(s). Only called on cache miss. May produce zero, one, or many files (e.g. paired router + types files). */
render: () =&gt; IncrementalOutputFile[];
}
&nbsp;
/** What {@link runIncrementalCodegen} produces — output files the caller must write, the new manifest the caller must persist (separately, typically in the build cache directory), paths that should be deleted, and a count of skipped units (useful for logging). */
export interface IncrementalResult {
/** Output files the caller should write (changed/new units' files plus global files). The manifest is **not** included — persist it separately via {@link IncrementalResult.manifest}. */
filesToWrite: IncrementalOutputFile[];
/** New manifest the caller should persist (typically as JSON to a path under the build cache directory). */
manifest: IncrementalManifest;
/** Relative paths from the prior run that no longer appear in the new run. The caller should delete these from disk. */
deletedPaths: string[];
/** Number of units whose codegen was skipped because their fingerprint matched. */
skippedUnitCount: number;
}
&nbsp;
/** Construct a no-op manifest, used when no prior run exists or when the cache is being intentionally bypassed (`--force`, `cacheEnabled=false`). */
export function emptyIncrementalManifest(codegenVersion: string): IncrementalManifest {
return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files: [], units: {} };
}
&nbsp;
/**
* Parse a previously-persisted manifest file. Returns an empty manifest on any
* shape error (malformed JSON, wrong version, missing fields). Stale files
* never block a build: the worst case is a full regen.
*/
export function parseIncrementalManifest(content: string): IncrementalManifest {
try {
const parsed = JSON.parse(content);
if (!parsed || typeof parsed !== 'object' || parsed.version !== INCREMENTAL_MANIFEST_VERSION) {
return emptyIncrementalManifest('');
}
const codegenVersion = typeof parsed.codegenVersion === 'string' ? parsed.codegenVersion : <span class="branch-1 cbranch-no" title="branch not covered" >'';</span>
const files = Array.isArray(parsed.files) &amp;&amp; parsed.files.every((f: unknown) =&gt; typeof f === 'string') ? (parsed.files as string[]) : <span class="branch-1 cbranch-no" title="branch not covered" >[];</span>
const units: Record&lt;string, IncrementalUnitRecord&gt; = {};
if (parsed.units &amp;&amp; typeof parsed.units === 'object' &amp;&amp; !Array.isArray(parsed.units)) {
for (const [key, raw] of Object.entries(parsed.units as Record&lt;string, unknown&gt;)) {
<span class="missing-if-branch" title="if path not taken" >I</span>if (!raw || typeof raw !== 'object') <span class="cstat-no" title="statement not covered" >continue;</span>
const entry = raw as Record&lt;string, unknown&gt;;
const fp = entry['fingerprint'];
const fs = entry['files'];
if (typeof fp !== 'string') continue;
if (!Array.isArray(fs) || !fs.every(p =&gt; typeof p === 'string')) continue;
units[key] = { fingerprint: fp, files: fs as string[] };
}
}
return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files, units };
} catch {
return emptyIncrementalManifest('');
}
}
&nbsp;
/** sha256 hex of `value` after stable JSON serialization. Two payloads with the same content always hash the same. */
export function hashFingerprint(value: unknown): string {
return createHash('sha256').update(stableStringify(value)).digest('hex');
}
&nbsp;
/**
* JSON.stringify variant that sorts object keys recursively, so structurally
* equivalent values always serialize identically.
*
* Handles `bigint` values (which native `JSON.stringify` rejects) by emitting
* them as a tagged string `"&lt;bigint:VALUE&gt;"`. Tagging — rather than coercing to
* a plain string or number — keeps `1n` and `"1"` distinguishable in fingerprints.
* `undefined` is normalized to `null` (stable) instead of being dropped (which
* would make `{a: undefined}` and `{}` collide).
*/
export function stableStringify(value: unknown): string {
if (value === undefined) return 'null';
if (typeof value === 'bigint') return JSON.stringify(`&lt;bigint:${value.toString()}&gt;`);
if (value === null || typeof value !== 'object') return JSON.stringify(value);
if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
const keys = Object.keys(value as Record&lt;string, unknown&gt;).sort();
return '{' + keys.map(k =&gt; JSON.stringify(k) + ':' + stableStringify((value as Record&lt;string, unknown&gt;)[k])).join(',') + '}';
}
&nbsp;
/**
* Run incremental codegen.
*
* For each unit, compares its current `fingerprint` against the prior manifest's
* record. On match (and provided every previously-emitted file is still on disk)
* the unit's `render()` is skipped and its prior output paths carry forward.
* On mismatch (or a missing file, or a `codegenVersion` bump) the unit re-renders
* and its files land in `filesToWrite`.
*
* Global files always land in `filesToWrite` — they're for outputs that always
* regenerate (aggregators, barrels, constants).
*
* The caller is responsible for:
* 1. Writing every `filesToWrite` entry to disk.
* 2. Deleting every `deletedPaths` entry from disk.
* 3. Persisting `manifest` to its own location (typically `&lt;cacheDir&gt;/&lt;plugin&gt;-manifest.json`).
* The manifest is NOT in `filesToWrite` — it's deliberately separate so plugins can
* place build state under the CLI cache dir rather than mixing it with output files.
*/
export function runIncrementalCodegen(args: {
codegenVersion: string;
prevManifest: IncrementalManifest;
/** Files always written, regardless of cache state — typically aggregators or constants. */
globalFiles: IncrementalOutputFile[];
/** Cacheable units, in deterministic order. */
units: IncrementalUnit[];
/** Returns `true` if the given relative path currently exists on disk. Used to invalidate cache entries when a previously-emitted file was deleted. */
fileExists: (relativePath: string) =&gt; boolean;
}): IncrementalResult {
const { codegenVersion, prevManifest, globalFiles, units, fileExists } = args;
const filesToWrite: IncrementalOutputFile[] = [];
const trackedPaths = new Set&lt;string&gt;();
const newUnits: Record&lt;string, IncrementalUnitRecord&gt; = {};
let skippedUnitCount = 0;
&nbsp;
const cacheUsable = prevManifest.version === INCREMENTAL_MANIFEST_VERSION &amp;&amp; prevManifest.codegenVersion === codegenVersion;
&nbsp;
for (const file of globalFiles) {
filesToWrite.push(file);
trackedPaths.add(file.relativePath);
}
&nbsp;
for (const unit of units) {
const prev = cacheUsable ? prevManifest.units[unit.key] : undefined;
const cacheHit =
prev !== undefined &amp;&amp; prev.fingerprint === unit.fingerprint &amp;&amp; prev.files.length &gt; 0 &amp;&amp; prev.files.every(p =&gt; fileExists(p));
&nbsp;
if (cacheHit) {
newUnits[unit.key] = { fingerprint: unit.fingerprint, files: prev!.files };
for (const p of prev!.files) trackedPaths.add(p);
skippedUnitCount++;
continue;
}
&nbsp;
const rendered = unit.render();
const renderedPaths: string[] = [];
for (const file of rendered) {
filesToWrite.push(file);
trackedPaths.add(file.relativePath);
renderedPaths.push(file.relativePath);
}
newUnits[unit.key] = { fingerprint: unit.fingerprint, files: renderedPaths };
}
&nbsp;
const sortedFiles = [...trackedPaths].sort();
const manifest: IncrementalManifest = {
version: INCREMENTAL_MANIFEST_VERSION,
codegenVersion,
files: sortedFiles,
units: newUnits,
};
&nbsp;
const deletedPaths = prevManifest.files.filter(p =&gt; !trackedPaths.has(p));
return { filesToWrite, manifest, deletedPaths, skippedUnitCount };
}
&nbsp;
/** Serialize a manifest to the JSON form persisted on disk. */
export function <span class="fstat-no" title="function not covered" >serializeIncrementalManifest(m</span>anifest: IncrementalManifest): string {
<span class="cstat-no" title="statement not covered" > return JSON.stringify(manifest, null, 2) + '\n';</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> src</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">64.92% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1107/1705</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">56.55% </span>
<span class="quiet">Branches</span>
<span class='fraction'>548/969</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">69.54% </span>
<span class="quiet">Functions</span>
<span class='fraction'>185/266</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">68.26% </span>
<span class="quiet">Lines</span>
<span class='fraction'>968/1418</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file high" data-value="apply-options-defaults.ts"><a href="apply-options-defaults.ts.html">apply-options-defaults.ts</a></td>
<td data-value="98" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div>
</td>
<td data-value="98" class="pct high">98%</td>
<td data-value="50" class="abs high">49/50</td>
<td data-value="95.34" class="pct high">95.34%</td>
<td data-value="43" class="abs high">41/43</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="38" class="abs high">38/38</td>
</tr>
<tr>
<td class="file high" data-value="apply-variable-substitution.ts"><a href="apply-variable-substitution.ts.html">apply-variable-substitution.ts</a></td>
<td data-value="97.56" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.56" class="pct high">97.56%</td>
<td data-value="41" class="abs high">40/41</td>
<td data-value="94.87" class="pct high">94.87%</td>
<td data-value="39" class="abs high">37/39</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="33" class="abs high">33/33</td>
</tr>
<tr>
<td class="file high" data-value="ast.ts"><a href="ast.ts.html">ast.ts</a></td>
<td data-value="90" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="10" class="abs high">9/10</td>
<td data-value="57.14" class="pct medium">57.14%</td>
<td data-value="7" class="abs medium">4/7</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
<td data-value="85.71" class="pct high">85.71%</td>
<td data-value="7" class="abs high">6/7</td>
</tr>
<tr>
<td class="file low" data-value="content-type.ts"><a href="content-type.ts.html">content-type.ts</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="12" class="abs low">0/12</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="10" class="abs low">0/10</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="2" class="abs low">0/2</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="7" class="abs low">0/7</td>
</tr>
<tr>
<td class="file high" data-value="decompose.ts"><a href="decompose.ts.html">decompose.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
</tr>
<tr>
<td class="file high" data-value="diagnostics.ts"><a href="diagnostics.ts.html">diagnostics.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="15" class="abs high">15/15</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="8" class="abs high">8/8</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="12" class="abs high">12/12</td>
</tr>
<tr>
<td class="file medium" data-value="grammar.ts"><a href="grammar.ts.html">grammar.ts</a></td>
<td data-value="75" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 75%"></div><div class="cover-empty" style="width: 25%"></div></div>
</td>
<td data-value="75" class="pct medium">75%</td>
<td data-value="8" class="abs medium">6/8</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="75" class="pct medium">75%</td>
<td data-value="8" class="abs medium">6/8</td>
</tr>
<tr>
<td class="file high" data-value="incremental.ts"><a href="incremental.ts.html">incremental.ts</a></td>
<td data-value="97.1" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.1" class="pct high">97.1%</td>
<td data-value="69" class="abs high">67/69</td>
<td data-value="93.47" class="pct high">93.47%</td>
<td data-value="46" class="abs high">43/46</td>
<td data-value="90.9" class="pct high">90.9%</td>
<td data-value="11" class="abs high">10/11</td>
<td data-value="98.21" class="pct high">98.21%</td>
<td data-value="56" class="abs high">55/56</td>
</tr>
<tr>
<td class="file empty" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
<td data-value="0" class="pic empty">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
</tr>
<tr>
<td class="file high" data-value="parser.ts"><a href="parser.ts.html">parser.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
<td data-value="66.66" class="pct medium">66.66%</td>
<td data-value="6" class="abs medium">4/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
</tr>
<tr>
<td class="file empty" data-value="plugin.ts"><a href="plugin.ts.html">plugin.ts</a></td>
<td data-value="0" class="pic empty">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
<td data-value="0" class="pct empty">0%</td>
<td data-value="0" class="abs empty">0/0</td>
</tr>
<tr>
<td class="file high" data-value="semantics.ts"><a href="semantics.ts.html">semantics.ts</a></td>
<td data-value="92.68" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
</td>
<td data-value="92.68" class="pct high">92.68%</td>
<td data-value="656" class="abs high">608/656</td>
<td data-value="79.37" class="pct medium">79.37%</td>
<td data-value="286" class="abs medium">227/286</td>
<td data-value="93.51" class="pct high">93.51%</td>
<td data-value="108" class="abs high">101/108</td>
<td data-value="95.67" class="pct high">95.67%</td>
<td data-value="578" class="abs high">553/578</td>
</tr>
<tr>
<td class="file high" data-value="type-builders.ts"><a href="type-builders.ts.html">type-builders.ts</a></td>
<td data-value="87.38" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
</td>
<td data-value="87.38" class="pct high">87.38%</td>
<td data-value="111" class="abs high">97/111</td>
<td data-value="68.21" class="pct medium">68.21%</td>
<td data-value="129" class="abs medium">88/129</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="20" class="abs high">20/20</td>
<td data-value="87.91" class="pct high">87.91%</td>
<td data-value="91" class="abs high">80/91</td>
</tr>
<tr>
<td class="file low" data-value="type-utils.ts"><a href="type-utils.ts.html">type-utils.ts</a></td>
<td data-value="12.96" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 12%"></div><div class="cover-empty" style="width: 88%"></div></div>
</td>
<td data-value="12.96" class="pct low">12.96%</td>
<td data-value="401" class="abs low">52/401</td>
<td data-value="9.23" class="pct low">9.23%</td>
<td data-value="195" class="abs low">18/195</td>
<td data-value="12.28" class="pct low">12.28%</td>
<td data-value="57" class="abs low">7/57</td>
<td data-value="14.19" class="pct low">14.19%</td>
<td data-value="317" class="abs low">45/317</td>
</tr>
<tr>
<td class="file medium" data-value="validate-inheritance.ts"><a href="validate-inheritance.ts.html">validate-inheritance.ts</a></td>
<td data-value="68.45" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 68%"></div><div class="cover-empty" style="width: 32%"></div></div>
</td>
<td data-value="68.45" class="pct medium">68.45%</td>
<td data-value="149" class="abs medium">102/149</td>
<td data-value="58.51" class="pct medium">58.51%</td>
<td data-value="94" class="abs medium">55/94</td>
<td data-value="71.42" class="pct medium">71.42%</td>
<td data-value="14" class="abs medium">10/14</td>
<td data-value="70.68" class="pct medium">70.68%</td>
<td data-value="116" class="abs medium">82/116</td>
</tr>
<tr>
<td class="file low" data-value="validate-operation.ts"><a href="validate-operation.ts.html">validate-operation.ts</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="47" class="abs low">0/47</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="41" class="abs low">0/41</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="8" class="abs low">0/8</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="35" class="abs low">0/35</td>
</tr>
<tr>
<td class="file low" data-value="validate-refs.ts"><a href="validate-refs.ts.html">validate-refs.ts</a></td>
<td data-value="40.32" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 40%"></div><div class="cover-empty" style="width: 60%"></div></div>
</td>
<td data-value="40.32" class="pct low">40.32%</td>
<td data-value="124" class="abs low">50/124</td>
<td data-value="37.31" class="pct low">37.31%</td>
<td data-value="67" class="abs low">25/67</td>
<td data-value="50" class="pct medium">50%</td>
<td data-value="18" class="abs medium">9/18</td>
<td data-value="42.59" class="pct low">42.59%</td>
<td data-value="108" class="abs low">46/108</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/index.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> index.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/0</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export * from './ast.js';
export * from './diagnostics.js';
export * from './grammar.js';
export * from './type-builders.js';
export * from './parser.js';
export * from './decompose.js';
export * from './type-utils.js';
export * from './validate-operation.js';
export * from './validate-inheritance.js';
export * from './apply-options-defaults.js';
export * from './apply-variable-substitution.js';
export * from './validate-refs.js';
export * from './plugin.js';
export * from './content-type.js';
export * from './incremental.js';
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/parser.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> parser.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>9/9</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">66.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>4/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>9/9</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">229x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">229x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">225x</span>
<span class="cline-any cline-yes">225x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Contract DSL Parser — Ohm grammar-driven implementation.
* Parses .ck files containing contract and operation declarations.
*/
import { grammar } from './grammar.js';
import { createSemantics } from './semantics.js';
import { DiagnosticCollector } from './diagnostics.js';
import type { CkRootNode } from './ast.js';
&nbsp;
const semantics = createSemantics(grammar);
&nbsp;
/**
* Parse a .ck source file into a CkRootNode AST.
*
* Does NOT merge options-level header globals into operations — that is a separate
* normalization step (see `applyOptionsDefaults`) that codegen pipelines opt into.
* Tools that need the original source shape (e.g. the prettier plugin for round-trip
* formatting) should use this raw output.
*/
export function parseCk(source: string, file: string, diag: DiagnosticCollector): CkRootNode {
const match = grammar.match(source, 'Root');
&nbsp;
if (match.failed()) {
const lineMatch = match.message?.match(/Line (\d+)/);
const line = lineMatch ? parseInt(lineMatch[1]!, 10) : <span class="branch-1 cbranch-no" title="branch not covered" >0;</span>
diag.error(file, line, match.message ?? <span class="branch-1 cbranch-no" title="branch not covered" >'Parse error');</span>
return { kind: 'ckRoot', meta: {}, services: {}, models: [], routes: [], file };
}
&nbsp;
const ast = semantics(match).toAst(file, diag) as CkRootNode;
return ast;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/plugin.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> plugin.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/0</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { CkRootNode, ContractRootNode, OpRootNode, PluginValue } from './ast.js';
&nbsp;
export interface PluginContext {
/** Absolute resolved rootDir from config. */
rootDir: string;
/** The plugin's own `options` from config (empty object if not set). */
options: Record&lt;string, unknown&gt;;
/**
* Whether plugin-internal caches should be honored. Set to `false` when the user
* passes `--force` or when `cache: false` is configured. Plugins that maintain
* their own incremental-build state (e.g. per-op manifests) should bypass it
* when this is `false`. The CLI-level plugin cache is governed separately by
* the plugin's `cacheKey`.
*/
cacheEnabled: boolean;
/**
* Absolute path to the build-cache directory (default `&lt;rootDir&gt;/.contractkit/cache`,
* configurable via `config.cache.dir`). Plugins that persist incremental-build state
* across runs should write their manifest here so it co-locates with the rest of the
* CLI's cache and is ignored by source control alongside it.
*/
cacheDir: string;
/** Register a file to be written to disk. Only available in generateTargets. */
emitFile(outPath: string, content: string): void;
}
&nbsp;
/** Context passed to a plugin's command handler. */
export interface CommandContext {
/** Absolute resolved rootDir from config (best-effort; falls back to cwd). */
rootDir: string;
/** Directory containing contractkit.config.json (best-effort; falls back to cwd). */
configDir: string;
}
&nbsp;
export interface ContractKitPlugin {
/** Human-readable name used in error messages. */
name: string;
&nbsp;
/**
* Declared cache key — used to fingerprint plugin outputs.
* Increment when the plugin's output shape changes to bust the cache.
* Example: 'grpc-v1'
*/
cacheKey?: string;
&nbsp;
/**
* Transform a parsed CkRootNode (unified AST, pre-decompose).
* Called once per .ck file, before validateRefs/validateOp.
* Return the mutated node, or unmutated node for pass-through.
*/
transform?: (ast: CkRootNode, ctx: PluginContext) =&gt; Promise&lt;CkRootNode&gt;;
&nbsp;
/**
* Validate a parsed CkRootNode. Throw an Error to fail compilation.
* Called once per .ck file, before validateRefs/validateOp.
*/
validate?: (ast: CkRootNode, ctx: PluginContext) =&gt; Promise&lt;void&gt;;
&nbsp;
/**
* Validate an operation's `pluginExtensions[name]` entry, where `name` matches this
* plugin's `name`. Called after `file://` URL resolution, once per matching entry.
* Return `errors` to fail compilation, `warnings` to log without failing. Both arrays
* are joined into single diagnostic messages prefixed with `plugins.&lt;name&gt;:`.
*/
validateExtension?: (value: PluginValue) =&gt; { errors?: string[]; warnings?: string[] } | void;
&nbsp;
/**
* Primary codegen hook — called once after ALL files are parsed and
* cross-file state is resolved. Call ctx.emitFile() for each output.
*/
generateTargets?: (
inputs: {
contractRoots: ContractRootNode[];
opRoots: OpRootNode[];
modelsWithInput: ReadonlySet&lt;string&gt;;
modelsWithOutput: ReadonlySet&lt;string&gt;;
},
ctx: PluginContext,
) =&gt; Promise&lt;void&gt;;
&nbsp;
/**
* Register a CLI subcommand exposed as `contractkit &lt;name&gt; [args...]`.
* Built-in plugins (imported directly by the CLI) can use this to add
* first-class subcommands without any config wiring.
*/
command?: {
/** The subcommand name, e.g. "import-openapi". */
name: string;
/** One-line description shown in the top-level --help listing. */
description: string;
/**
* Full usage text shown when the subcommand is invoked with --help/-h.
* Should include the usage line, argument descriptions, and option flags.
*/
usage: string;
/** Handler — receives raw argv after the subcommand name. */
run: (args: string[], ctx: CommandContext) =&gt; Promise&lt;void&gt;;
};
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>

Sorry, the diff of this file is too big to display

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/type-builders.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> type-builders.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">87.38% </span>
<span class="quiet">Statements</span>
<span class='fraction'>97/111</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">68.21% </span>
<span class="quiet">Branches</span>
<span class='fraction'>88/129</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>20/20</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">87.91% </span>
<span class="quiet">Lines</span>
<span class='fraction'>80/91</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">311x</span>
<span class="cline-any cline-yes">212x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">99x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">216x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">213x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">214x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Shared type-building utilities used by both contract and operation semantic actions.
* Extracted from visitor-contract.ts and visitor-op.ts to eliminate duplication.
*/
import type { ContractTypeNode, ScalarTypeNode, UnionTypeNode } from './ast.js';
import { SCALAR_NAMES } from './ast.js';
&nbsp;
export const OBJECT_MODES = new Set&lt;string&gt;(['strict', 'strip', 'loose']);
export const ROUTE_MODIFIERS = new Set&lt;string&gt;(['internal', 'deprecated', 'public']);
export const HTTP_METHODS = new Set&lt;string&gt;(['get', 'post', 'put', 'patch', 'delete']);
&nbsp;
/** Parsed type argument — either a key=value constraint or a positional value. */
export type TypeArgKeyValue = { key: string; value: string | number | boolean };
export type TypeArgString = { type: 'string'; value: string };
export type TypeArgNumber = { type: 'number'; value: number };
export type TypeArgBoolean = { type: 'boolean'; value: boolean };
export type TypeArgType = { type: 'type'; value: ContractTypeNode };
export type TypeArg = TypeArgKeyValue | TypeArgString | TypeArgNumber | TypeArgBoolean | TypeArgType;
&nbsp;
/** Resolve a simple type name to a ContractTypeNode (scalar or model ref). */
export function resolveSimpleType(name: string): ContractTypeNode {
if (SCALAR_NAMES.has(name)) {
return { kind: 'scalar', name: name as ScalarTypeNode['name'] };
}
return { kind: 'ref', name };
}
&nbsp;
/** Build a compound or constrained type from a type name and parsed arguments. */
export function buildCompoundType(name: string, args: TypeArg[]): ContractTypeNode {
switch (name) {
case 'array':
return buildArrayType(args);
case 'tuple':
return buildTupleType(args);
case 'record':
return buildRecordType(args);
case 'enum':
return buildEnumType(args);
case 'literal':
return buildLiteralType(args);
case 'lazy':
return buildLazyType(args);
case 'discriminated':
return buildDiscriminatedUnionType(args);
default: {
<span class="missing-if-branch" title="else path not taken" >E</span>if (SCALAR_NAMES.has(name)) {
return buildScalarWithModifiers(name as ScalarTypeNode['name'], args);
}
<span class="cstat-no" title="statement not covered" > return { kind: 'ref', name };</span>
}
}
}
&nbsp;
function buildArrayType(args: TypeArg[]): ContractTypeNode {
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const item: ContractTypeNode = typeArgs[0]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
let min: number | undefined;
let max: number | undefined;
for (const a of args) {
if ('key' in a &amp;&amp; a.key === 'min') min = Number(a.value);
if ('key' in a &amp;&amp; a.key === 'max') max = Number(a.value);
}
return { kind: 'array', item, min, max };
}
&nbsp;
function buildTupleType(args: TypeArg[]): ContractTypeNode {
const items = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type').map(a =&gt; a.value);
return { kind: 'tuple', items };
}
&nbsp;
function buildRecordType(args: TypeArg[]): ContractTypeNode {
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const key: ContractTypeNode = typeArgs[0]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'string' };</span>
const value: ContractTypeNode = typeArgs[1]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
return { kind: 'record', key, value };
}
&nbsp;
function buildEnumType(args: TypeArg[]): ContractTypeNode {
const values: string[] = [];
for (const a of args) {
if ('type' in a &amp;&amp; a.type === 'type' &amp;&amp; a.value.kind === 'ref') {
values.push(a.value.name);
} else if ('type' in a &amp;&amp; a.type === 'string') {
values.push(a.value);
} else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if ('type' in a &amp;&amp; a.type === 'type' &amp;&amp; a.value.kind === 'scalar') {</span>
<span class="cstat-no" title="statement not covered" > values.push(a.value.name);</span>
}
}
return { kind: 'enum', values };
}
&nbsp;
function buildLiteralType(args: TypeArg[]): ContractTypeNode {
const arg = args[0];
<span class="missing-if-branch" title="if path not taken" >I</span>if (!arg) <span class="cstat-no" title="statement not covered" >return { kind: 'literal', value: '' };</span>
<span class="missing-if-branch" title="else path not taken" >E</span>if ('type' in arg) {
if (arg.type === 'string') return { kind: 'literal', value: arg.value };
if (arg.type === 'number') return { kind: 'literal', value: arg.value };
<span class="missing-if-branch" title="else path not taken" >E</span>if (arg.type === 'boolean') return { kind: 'literal', value: arg.value };
}
<span class="cstat-no" title="statement not covered" > return { kind: 'literal', value: String('value' in arg ? arg.value : '') };</span>
}
&nbsp;
function buildLazyType(args: TypeArg[]): ContractTypeNode {
const typeArg = args.find((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const inner: ContractTypeNode = typeArg?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
return { kind: 'lazy', inner };
}
&nbsp;
function buildDiscriminatedUnionType(args: TypeArg[]): ContractTypeNode {
let discriminator = '';
for (const a of args) {
if ('key' in a &amp;&amp; a.key === 'by') {
discriminator = String(a.value);
}
}
&nbsp;
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const members: ContractTypeNode[] = [];
for (const ta of typeArgs) {
// A single TypeExpression like `A | B | C` arrives as one TypeArgType with a union node.
// Flatten it so members ends up as the leaf types.
if (ta.value.kind === 'union') {
members.push(...ta.value.members);
} else {
members.push(ta.value);
}
}
&nbsp;
return { kind: 'discriminatedUnion', discriminator, members };
}
&nbsp;
function buildScalarWithModifiers(name: ScalarTypeNode['name'], args: TypeArg[]): ScalarTypeNode {
const scalar: ScalarTypeNode = { kind: 'scalar', name };
for (const a of args) {
// Positional string argument (quoted): used as format for date/time types
<span class="missing-if-branch" title="if path not taken" >I</span>if ('type' in a &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >a.type === 'string' &amp;&amp; <span class="branch-2 cbranch-no" title="branch not covered" >!</span>('key' in a)) {</span>
<span class="cstat-no" title="statement not covered" > if (name === 'date' || name === 'time' || name === 'datetime') {</span>
<span class="cstat-no" title="statement not covered" > scalar.format = String(a.value);</span>
}
<span class="cstat-no" title="statement not covered" > continue;</span>
}
// Positional ref argument (unquoted identifier): used as format for date/time types
<span class="missing-if-branch" title="if path not taken" >I</span>if ('type' in a &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >a.type === 'type' &amp;&amp; <span class="branch-2 cbranch-no" title="branch not covered" >a</span>.value?.kind === 'ref' &amp;&amp; <span class="branch-3 cbranch-no" title="branch not covered" >!</span>('key' in a)) {</span>
<span class="cstat-no" title="statement not covered" > if (name === 'date' || name === 'time' || name === 'datetime') {</span>
<span class="cstat-no" title="statement not covered" > scalar.format = String(a.value.name);</span>
}
<span class="cstat-no" title="statement not covered" > continue;</span>
}
<span class="missing-if-branch" title="if path not taken" >I</span>if (!('key' in a)) <span class="cstat-no" title="statement not covered" >continue;</span>
if (a.key === 'min') scalar.min = name === 'bigint' ? <span class="branch-0 cbranch-no" title="branch not covered" >BigInt(a.value) : n</span>ame === 'duration' ? <span class="branch-0 cbranch-no" title="branch not covered" >String(a.value) : N</span>umber(a.value);
if (a.key === 'max') scalar.max = name === 'bigint' ? <span class="branch-0 cbranch-no" title="branch not covered" >BigInt(a.value) : n</span>ame === 'duration' ? <span class="branch-0 cbranch-no" title="branch not covered" >String(a.value) : N</span>umber(a.value);
if (a.key === 'len' || a.key === 'length') scalar.len = Number(a.value);
if (a.key === 'regex') scalar.regex = String(a.value);
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.key === 'format') <span class="cstat-no" title="statement not covered" >scalar.format = String(a.value);</span>
}
return scalar;
}
&nbsp;
/**
* Extract nullability from a type node.
* If the type is a union containing `null`, remove the null member and return nullable=true.
*/
export function extractNullability(type: ContractTypeNode): { type: ContractTypeNode; nullable: boolean } {
if (type.kind === 'union') {
const union = type as UnionTypeNode;
const nullIdx = union.members.findIndex(m =&gt; m.kind === 'scalar' &amp;&amp; (m as ScalarTypeNode).name === 'null');
if (nullIdx !== -1) {
const filtered = [...union.members];
filtered.splice(nullIdx, 1);
return { type: filtered.length === 1 ? filtered[0]! : <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'union', members: filtered }, n</span>ullable: true };
}
} else if (type.kind === 'scalar' &amp;&amp; type.name === 'null') {
return { type, nullable: true };
}
return { type, nullable: false };
}
&nbsp;
/**
* Convert a ContractTypeNode to ParamSource for query/headers blocks.
*/
export function typeNodeToParamSource(node: ContractTypeNode): import('./ast.js').ParamSource {
if (node.kind === 'ref') return { kind: 'ref', name: node.name };
<span class="missing-if-branch" title="else path not taken" >E</span>if (node.kind === 'inlineObject') {
return {
kind: 'params',
nodes: node.fields.map(f =&gt; ({
name: f.name,
optional: f.optional,
nullable: f.nullable,
type: f.type,
default: f.default,
description: f.description,
loc: f.loc,
})),
};
}
<span class="cstat-no" title="statement not covered" > return { kind: 'type', node: node };</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>

Sorry, the diff of this file is too big to display

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/validate-inheritance.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> validate-inheritance.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">68.45% </span>
<span class="quiet">Statements</span>
<span class='fraction'>102/149</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">58.51% </span>
<span class="quiet">Branches</span>
<span class='fraction'>55/94</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">71.42% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/14</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">70.68% </span>
<span class="quiet">Lines</span>
<span class='fraction'>82/116</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">67x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">65x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Validates multi-base inheritance: detects cycles, enforces that conflicting fields across
* bases are explicitly redeclared with the `override` modifier, and rejects spurious `override`
* modifiers on fields not present in any base.
*
* Resolution rules:
* - Each base contributes its fully-resolved field set (recursive). Diamond inheritance is
* deduplicated — a model reachable via multiple paths only contributes its fields once.
* - Two fields with the same name conflict iff their `FieldNode` shapes differ on any of:
* type, optional, nullable, visibility, default, deprecated. `description` and `loc` are ignored.
* - When two or more bases contribute a same-named field, the model must redeclare that field
* in its own inline block with `override`.
* - A field with `override` must shadow at least one base-contributed field.
*
* Runs after `validate-refs` so we know all referenced bases resolve. Cycles short-circuit
* the recursion so a downstream conflict check on a cyclic chain emits one cycle error per cycle,
* not one per node.
*/
import type { ContractRootNode, ContractTypeNode, FieldNode, ModelNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
export function validateInheritance(contractRoots: ContractRootNode[], diag: DiagnosticCollector): void {
const modelMap = new Map&lt;string, ModelNode&gt;();
for (const root of contractRoots) {
for (const model of root.models) modelMap.set(model.name, model);
}
&nbsp;
const cycleNodes = detectCycles(modelMap, diag);
&nbsp;
for (const root of contractRoots) {
for (const model of root.models) {
if (!model.bases || model.bases.length === 0) continue;
if (cycleNodes.has(model.name)) continue;
checkModel(model, modelMap, cycleNodes, diag);
}
}
}
&nbsp;
function detectCycles(modelMap: Map&lt;string, ModelNode&gt;, diag: DiagnosticCollector): Set&lt;string&gt; {
const onStack = new Set&lt;string&gt;();
const visited = new Set&lt;string&gt;();
const inCycle = new Set&lt;string&gt;();
&nbsp;
function visit(name: string, path: string[]): void {
if (onStack.has(name)) {
const start = path.indexOf(name);
const cycle = path.slice(start).concat(name).join(' → ');
for (const n of path.slice(start)) inCycle.add(n);
const model = modelMap.get(name);
<span class="missing-if-branch" title="else path not taken" >E</span>if (model) diag.error(model.loc.file, model.loc.line, `Inheritance cycle: ${cycle}`);
return;
}
if (visited.has(name)) return;
const model = modelMap.get(name);
if (!model || !model.bases) {
visited.add(name);
return;
}
onStack.add(name);
path.push(name);
for (const b of model.bases) visit(b, path);
path.pop();
onStack.delete(name);
visited.add(name);
}
&nbsp;
for (const name of modelMap.keys()) visit(name, []);
return inCycle;
}
&nbsp;
/** Resolve a base model to its **effective** field set: the merge of all its inherited
* fields with its own (own fields shadow inherited by name). Memoized per model. */
function resolveEffectiveFields(
baseName: string,
modelMap: Map&lt;string, ModelNode&gt;,
cycleNodes: Set&lt;string&gt;,
cache: Map&lt;string, Map&lt;string, FieldNode&gt;&gt;,
): Map&lt;string, FieldNode&gt; {
const cached = cache.get(baseName);
if (cached) return cached;
<span class="missing-if-branch" title="if path not taken" >I</span>if (cycleNodes.has(baseName)) {
const empty = <span class="cstat-no" title="statement not covered" >new Map&lt;string, FieldNode&gt;();</span>
<span class="cstat-no" title="statement not covered" > cache.set(baseName, empty);</span>
<span class="cstat-no" title="statement not covered" > return empty;</span>
}
const base = modelMap.get(baseName);
<span class="missing-if-branch" title="if path not taken" >I</span>if (!base) {
const empty = <span class="cstat-no" title="statement not covered" >new Map&lt;string, FieldNode&gt;();</span>
<span class="cstat-no" title="statement not covered" > cache.set(baseName, empty);</span>
<span class="cstat-no" title="statement not covered" > return empty;</span>
}
// Set early to break any unexpected recursion.
const effective = new Map&lt;string, FieldNode&gt;();
cache.set(baseName, effective);
if (base.bases) {
for (const grandparent of base.bases) {
const g = resolveEffectiveFields(grandparent, modelMap, cycleNodes, cache);
for (const [name, field] of g) effective.set(name, field);
}
}
for (const own of base.fields) {
effective.set(own.name, own);
}
return effective;
}
&nbsp;
function checkModel(model: ModelNode, modelMap: Map&lt;string, ModelNode&gt;, cycleNodes: Set&lt;string&gt;, diag: DiagnosticCollector): void {
const cache = new Map&lt;string, Map&lt;string, FieldNode&gt;&gt;();
&nbsp;
// For each direct base, resolve its effective field set. Cross-base conflicts are detected
// by comparing same-named contributions across bases (each base's own overrides already
// applied within its set).
const baseFieldsByName = new Map&lt;string, { source: string; field: FieldNode }[]&gt;();
for (const base of model.bases!) {
const effective = resolveEffectiveFields(base, modelMap, cycleNodes, cache);
for (const [name, field] of effective) {
const list = baseFieldsByName.get(name) ?? [];
list.push({ source: base, field });
baseFieldsByName.set(name, list);
}
}
&nbsp;
const localFieldsByName = new Map&lt;string, FieldNode&gt;();
for (const f of model.fields) localFieldsByName.set(f.name, f);
&nbsp;
// Rule: every cross-base conflict must be redeclared with override.
for (const [name, list] of baseFieldsByName) {
if (list.length &lt; 2) continue;
const allIdentical = list.every(item =&gt; fieldsAreIdentical(item.field, list[0]!.field));
if (allIdentical) continue;
const local = localFieldsByName.get(name);
if (!local) {
const sources = list.map(l =&gt; `'${l.source}'`).join(' and ');
diag.error(
model.loc.file,
model.loc.line,
`Field '${name}' is declared by ${sources} with different shapes — redeclare in '${model.name}' with 'override'`,
'missing-override',
);
continue;
}
if (!local.override) {
const sources = list.map(l =&gt; `'${l.source}'`).join(' and ');
diag.error(
local.loc.file,
local.loc.line,
`Field '${name}' conflicts across bases ${sources} — mark as 'override'`,
'missing-override',
);
}
}
&nbsp;
// Rule: `override` must shadow at least one base-contributed field.
for (const f of model.fields) {
if (!f.override) continue;
if (!baseFieldsByName.has(f.name)) {
diag.error(
f.loc.file,
f.loc.line,
`Field '${f.name}' has 'override' but is not declared in any base of '${model.name}'`,
'spurious-override',
);
}
}
}
&nbsp;
/** Structural deep-equality on a FieldNode for inheritance-conflict purposes.
* Compares: type (deep), optional, nullable, visibility, default, deprecated.
* Ignores: description, loc, override (the marker itself isn't part of "shape"). */
export function fieldsAreIdentical(a: FieldNode, b: FieldNode): boolean {
if (a.optional !== b.optional) return false;
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.nullable !== b.nullable) <span class="cstat-no" title="statement not covered" >return false;</span>
if (a.visibility !== b.visibility) return false;
<span class="missing-if-branch" title="if path not taken" >I</span>if ((a.deprecated ?? false) !== (b.deprecated ?? false)) <span class="cstat-no" title="statement not covered" >return false;</span>
if (a.default !== b.default) return false;
return typesAreIdentical(a.type, b.type);
}
&nbsp;
function typesAreIdentical(a: ContractTypeNode, b: ContractTypeNode): boolean {
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.kind !== b.kind) <span class="cstat-no" title="statement not covered" >return false;</span>
switch (a.kind) {
case 'scalar': {
const bb = b as typeof a;
return a.name === bb.name &amp;&amp; a.min === bb.min &amp;&amp; a.max === bb.max &amp;&amp; a.len === bb.len &amp;&amp; a.regex === bb.regex &amp;&amp; a.format === bb.format;
}
<span class="branch-1 cbranch-no" title="branch not covered" > case 'literal': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.value === bb.value;</span>
}
<span class="branch-2 cbranch-no" title="branch not covered" > case 'enum': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.values.length === bb.values.length &amp;&amp; a.values.every(<span class="fstat-no" title="function not covered" >(v</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >v === bb.values[i])</span>;</span>
}
<span class="branch-3 cbranch-no" title="branch not covered" > case 'ref': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.name === bb.name;</span>
}
<span class="branch-4 cbranch-no" title="branch not covered" > case 'array': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.min !== bb.min || a.max !== bb.max) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.item, bb.item);</span>
}
<span class="branch-5 cbranch-no" title="branch not covered" > case 'tuple': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.items.length === bb.items.length &amp;&amp; a.items.every(<span class="fstat-no" title="function not covered" >(i</span>t, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(it, bb.items[i]!));</span></span>
}
<span class="branch-6 cbranch-no" title="branch not covered" > case 'record': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.key, bb.key) &amp;&amp; typesAreIdentical(a.value, bb.value);</span>
}
<span class="branch-7 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="branch-8 cbranch-no" title="branch not covered" > case 'intersection': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.members.length === bb.members.length &amp;&amp; a.members.every(<span class="fstat-no" title="function not covered" >(m</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(m, bb.members[i]!));</span></span>
}
<span class="branch-9 cbranch-no" title="branch not covered" > case 'discriminatedUnion': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.discriminator !== bb.discriminator) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > return a.members.length === bb.members.length &amp;&amp; a.members.every(<span class="fstat-no" title="function not covered" >(m</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(m, bb.members[i]!));</span></span>
}
<span class="branch-10 cbranch-no" title="branch not covered" > case 'lazy': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.inner, bb.inner);</span>
}
<span class="branch-11 cbranch-no" title="branch not covered" > case 'inlineObject': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.fields.length !== bb.fields.length) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > for (let i = <span class="cstat-no" title="statement not covered" >0; i</span> &lt; a.fields.length; i++) {</span>
const af = <span class="cstat-no" title="statement not covered" >a.fields[i]!</span>;
const bf = <span class="cstat-no" title="statement not covered" >bb.fields[i]!</span>;
<span class="cstat-no" title="statement not covered" > if (af.name !== bf.name) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > if (!fieldsAreIdentical(af, bf)) <span class="cstat-no" title="statement not covered" >return false;</span></span>
}
<span class="cstat-no" title="statement not covered" > return true;</span>
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/validate-operation.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> validate-operation.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/47</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/41</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/35</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { ContractTypeNode, FieldNode, OpRootNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
/** Extract `{paramName}` segments from a route path. */
function <span class="fstat-no" title="function not covered" >extractPathParams(p</span>ath: string): string[] {
<span class="cstat-no" title="statement not covered" > return [...path.matchAll(/\{(\w+)\}/g)].map(<span class="fstat-no" title="function not covered" >m =&gt; <span class="cstat-no" title="statement not covered" >m</span>[1]!</span>);</span>
}
&nbsp;
/**
* Warn when a route path contains `{param}` placeholders that are not
* explicitly declared in a `params` block; warn on empty/invalid request body
* declarations and on `application/x-www-form-urlencoded` bodies that contain
* nested object/array shapes (which don't round-trip cleanly through
* URL-encoded form encoding).
*/
export function <span class="fstat-no" title="function not covered" >validateOp(r</span>oot: OpRootNode, diag: DiagnosticCollector): void {
<span class="cstat-no" title="statement not covered" > for (const route of root.routes) {</span>
const pathParams = <span class="cstat-no" title="statement not covered" >extractPathParams(route.path);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (pathParams.length &gt; 0) {</span>
<span class="cstat-no" title="statement not covered" > if (!route.params) {</span>
<span class="cstat-no" title="statement not covered" > for (const name of pathParams) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(root.file, route.loc.line, `Path parameter '{${name}}' is not explicitly defined in a params block`);</span>
}
} else <span class="cstat-no" title="statement not covered" >if (route.params.kind === 'ref' || route.params.kind === 'type') {</span>
// Type-reference or ContractTypeNode form — all params are covered by the type
} else {
const declared = <span class="cstat-no" title="statement not covered" >new Set(route.params.nodes.map(<span class="fstat-no" title="function not covered" >(p</span>: { name: string }) =&gt; <span class="cstat-no" title="statement not covered" >p.name)</span>);</span>
<span class="cstat-no" title="statement not covered" > for (const name of pathParams) {</span>
<span class="cstat-no" title="statement not covered" > if (!declared.has(name)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(root.file, route.loc.line, `Path parameter '{${name}}' is not explicitly defined in a params block`);</span>
}
}
}
}
&nbsp;
<span class="cstat-no" title="statement not covered" > for (const op of route.operations) {</span>
<span class="cstat-no" title="statement not covered" > if (!op.request) <span class="cstat-no" title="statement not covered" >continue;</span></span>
<span class="cstat-no" title="statement not covered" > if (op.request.bodies.length === 0) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(root.file, op.loc.line, `Operation has an empty request block — declare at least one content type`);</span>
<span class="cstat-no" title="statement not covered" > continue;</span>
}
<span class="cstat-no" title="statement not covered" > for (const body of op.request.bodies) {</span>
<span class="cstat-no" title="statement not covered" > if (body.contentType !== 'application/x-www-form-urlencoded') <span class="cstat-no" title="statement not covered" >continue;</span></span>
<span class="cstat-no" title="statement not covered" > if (typeContainsNestedShape(body.bodyType, root)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(</span>
root.file,
op.loc.line,
`application/x-www-form-urlencoded body contains nested objects or arrays — these don't round-trip cleanly through form encoding`,
);
}
}
}
}
}
&nbsp;
/**
* True if `type` (resolved through model refs) contains any field whose type is itself
* an object, inline object, array of object, or record of object.
*/
function <span class="fstat-no" title="function not covered" >typeContainsNestedShape(t</span>ype: ContractTypeNode, root: OpRootNode, seen: Set&lt;string&gt; = <span class="branch-0 cbranch-no" title="branch not covered" >new Set())</span>: boolean {
const fields = <span class="cstat-no" title="statement not covered" >resolveFields(type, root, seen);</span>
<span class="cstat-no" title="statement not covered" > if (!fields) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > return fields.some(<span class="fstat-no" title="function not covered" >f =&gt; <span class="cstat-no" title="statement not covered" >i</span>sNestedShape(f.type));</span></span>
}
&nbsp;
function <span class="fstat-no" title="function not covered" >isNestedShape(t</span>: ContractTypeNode): boolean {
<span class="cstat-no" title="statement not covered" > if (t.kind === 'inlineObject') <span class="cstat-no" title="statement not covered" >return true;</span></span>
<span class="cstat-no" title="statement not covered" > if (t.kind === 'array') <span class="cstat-no" title="statement not covered" >return t.item.kind === 'inlineObject' || t.item.kind === 'ref' || t.item.kind === 'record';</span></span>
<span class="cstat-no" title="statement not covered" > if (t.kind === 'record') <span class="cstat-no" title="statement not covered" >return true;</span></span>
<span class="cstat-no" title="statement not covered" > if (t.kind === 'union' || t.kind === 'discriminatedUnion' || t.kind === 'intersection') <span class="cstat-no" title="statement not covered" >return true;</span></span>
<span class="cstat-no" title="statement not covered" > return false;</span>
}
&nbsp;
function <span class="fstat-no" title="function not covered" >resolveFields(t</span>ype: ContractTypeNode, root: OpRootNode, seen: Set&lt;string&gt;): FieldNode[] | undefined {
<span class="cstat-no" title="statement not covered" > if (type.kind === 'inlineObject') <span class="cstat-no" title="statement not covered" >return type.fields;</span></span>
<span class="cstat-no" title="statement not covered" > if (type.kind === 'ref') {</span>
<span class="cstat-no" title="statement not covered" > if (seen.has(type.name)) <span class="cstat-no" title="statement not covered" >return undefined;</span></span>
<span class="cstat-no" title="statement not covered" > seen.add(type.name);</span>
// OpRoot doesn't carry models; if running on a CkRoot the caller would resolve. For pure .op
// contexts we can't resolve refs — be conservative and return undefined (no warning).
<span class="cstat-no" title="statement not covered" > return undefined;</span>
}
<span class="cstat-no" title="statement not covered" > return undefined;</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/validate-refs.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> validate-refs.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">40.32% </span>
<span class="quiet">Statements</span>
<span class='fraction'>50/124</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">37.31% </span>
<span class="quiet">Branches</span>
<span class='fraction'>25/67</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">50% </span>
<span class="quiet">Functions</span>
<span class='fraction'>9/18</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">42.59% </span>
<span class="quiet">Lines</span>
<span class='fraction'>46/108</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">30x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">30x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { ContractRootNode, OpRootNode, ContractTypeNode, ModelNode, FieldNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
/**
* After all files are parsed, validate that type references point to
* defined models.
*/
export function validateRefs(contractRoots: ContractRootNode[], opRoots: OpRootNode[], diag: DiagnosticCollector, allContractRoots?: ContractRootNode[]): void {
// Phase 1: Collect all defined model names from ALL contract files (not just changed ones)
// so that cached/unchanged files don't cause false "not defined" warnings.
const modelNames = new Set&lt;string&gt;();
const modelMap = new Map&lt;string, ModelNode&gt;();
for (const root of allContractRoots ?? contractRoots) {
for (const model of root.models) {
modelNames.add(model.name);
modelMap.set(model.name, model);
}
}
&nbsp;
// Phase 2: Check contract type references
for (const root of contractRoots) {
for (const model of root.models) {
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.bases) {
<span class="cstat-no" title="statement not covered" > for (const base of model.bases) {</span>
<span class="cstat-no" title="statement not covered" > if (!modelNames.has(base)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(model.loc.file, model.loc.line, `Base model "${base}" is not defined in any contract file`, 'unknown-model');</span>
}
}
}
if (model.type) {
checkTypeRefs(model.type, model.loc.file, model.loc.line, modelNames, diag);
checkDiscriminatedUnions(model.type, model.loc.file, model.loc.line, modelMap, diag);
}
for (const field of model.fields) {
checkTypeRefs(field.type, field.loc.file, field.loc.line, modelNames, diag);
checkDiscriminatedUnions(field.type, field.loc.file, field.loc.line, modelMap, diag);
}
}
}
&nbsp;
// Phase 3: Check OP type references
for (const root of opRoots) {
<span class="cstat-no" title="statement not covered" > for (const route of root.routes) {</span>
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(route.params, root.file, route.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > for (const op of route.operations) {</span>
<span class="cstat-no" title="statement not covered" > if (op.request) {</span>
<span class="cstat-no" title="statement not covered" > for (const body of op.request.bodies) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(body.bodyType, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(body.bodyType, root.file, op.loc.line, modelMap, diag);</span>
}
}
<span class="cstat-no" title="statement not covered" > for (const resp of op.responses) {</span>
<span class="cstat-no" title="statement not covered" > if (resp.bodyType) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(resp.bodyType, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(resp.bodyType, root.file, op.loc.line, modelMap, diag);</span>
}
}
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(op.query, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(op.headers, root.file, op.loc.line, modelNames, diag);</span>
}
}
}
}
&nbsp;
function checkTypeRefs(type: ContractTypeNode, file: string, line: number, models: Set&lt;string&gt;, diag: DiagnosticCollector): void {
switch (type.kind) {
case 'ref':
<span class="missing-if-branch" title="if path not taken" >I</span>if (!models.has(type.name)) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `Referenced model "${type.name}" is not defined in any contract file`, 'unknown-model');</span>
}
break;
<span class="branch-1 cbranch-no" title="branch not covered" > case 'array':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.item, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-2 cbranch-no" title="branch not covered" > case 'tuple':</span>
<span class="cstat-no" title="statement not covered" > type.items.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-3 cbranch-no" title="branch not covered" > case 'record':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.key, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.value, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-4 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
case 'discriminatedUnion':
type.members.forEach(t =&gt; checkTypeRefs(t, file, line, models, diag));
break;
<span class="branch-6 cbranch-no" title="branch not covered" > case 'intersection':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-7 cbranch-no" title="branch not covered" > case 'lazy':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.inner, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-8 cbranch-no" title="branch not covered" > case 'inlineObject':</span>
<span class="cstat-no" title="statement not covered" > type.fields.forEach(<span class="fstat-no" title="function not covered" >f =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(f.type, file, f.loc.line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
}
}
&nbsp;
/**
* Validate discriminated unions: every member must be a model ref or inline object,
* and every member must contain a literal-typed field matching the discriminator.
*/
function checkDiscriminatedUnions(type: ContractTypeNode, file: string, line: number, models: Map&lt;string, ModelNode&gt;, diag: DiagnosticCollector): void {
switch (type.kind) {
case 'discriminatedUnion': {
<span class="missing-if-branch" title="if path not taken" >I</span>if (!type.discriminator) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `discriminated() requires a "by=&lt;field&gt;" discriminator key`);</span>
}
if (type.members.length &lt; 2) {
diag.warn(file, line, `discriminated() requires at least 2 union members`);
}
for (const member of type.members) {
const fields = resolveMemberFields(member, models);
<span class="missing-if-branch" title="if path not taken" >I</span>if (fields === null) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `discriminated union member must be a model ref or inline object (got ${describeKind(member)})`);</span>
<span class="cstat-no" title="statement not covered" > continue;</span>
}
const field = fields.find(f =&gt; f.name === type.discriminator);
if (!field) {
diag.warn(
file,
line,
`discriminated union member ${describeMember(member)} is missing discriminator field "${type.discriminator}"`,
);
continue;
}
if (field.type.kind !== 'literal' &amp;&amp; field.type.kind !== 'enum') {
diag.warn(
file,
line,
`discriminated union member ${describeMember(member)} field "${type.discriminator}" must be a literal or enum (got ${describeKind(field.type)})`,
);
}
}
// Recurse into nested types as well.
type.members.forEach(m =&gt; checkDiscriminatedUnions(m, file, line, models, diag));
break;
}
<span class="branch-1 cbranch-no" title="branch not covered" > case 'array':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.item, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-2 cbranch-no" title="branch not covered" > case 'tuple':</span>
<span class="cstat-no" title="statement not covered" > type.items.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-3 cbranch-no" title="branch not covered" > case 'record':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.key, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.value, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-4 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="branch-5 cbranch-no" title="branch not covered" > case 'intersection':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-6 cbranch-no" title="branch not covered" > case 'lazy':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.inner, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-7 cbranch-no" title="branch not covered" > case 'inlineObject':</span>
<span class="cstat-no" title="statement not covered" > type.fields.forEach(<span class="fstat-no" title="function not covered" >f =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(f.type, file, f.loc.line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
}
}
&nbsp;
/** Resolve a discriminated-union member to its field list, following ref→model and base inheritance. */
function resolveMemberFields(member: ContractTypeNode, models: Map&lt;string, ModelNode&gt;): FieldNode[] | null {
<span class="missing-if-branch" title="if path not taken" >I</span>if (member.kind === 'inlineObject') <span class="cstat-no" title="statement not covered" >return member.fields;</span>
<span class="missing-if-branch" title="else path not taken" >E</span>if (member.kind === 'ref') {
const model = models.get(member.name);
<span class="missing-if-branch" title="if path not taken" >I</span>if (!model) <span class="cstat-no" title="statement not covered" >return null;</span>
// For aliased models, peer through to the aliased type.
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.type) <span class="cstat-no" title="statement not covered" >return resolveMemberFields(model.type, models);</span>
const fields = [...model.fields];
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.bases) {
<span class="cstat-no" title="statement not covered" > for (const base of model.bases) {</span>
const baseFields = <span class="cstat-no" title="statement not covered" >resolveMemberFields({ kind: 'ref', name: base }, models);</span>
<span class="cstat-no" title="statement not covered" > if (baseFields) <span class="cstat-no" title="statement not covered" >fields.push(...baseFields);</span></span>
}
}
return fields;
}
<span class="cstat-no" title="statement not covered" > return null;</span>
}
&nbsp;
function describeMember(member: ContractTypeNode): string {
<span class="missing-if-branch" title="else path not taken" >E</span>if (member.kind === 'ref') return `"${member.name}"`;
<span class="cstat-no" title="statement not covered" > return `(${describeKind(member)})`;</span>
}
&nbsp;
function describeKind(type: ContractTypeNode): string {
return type.kind;
}
&nbsp;
function <span class="fstat-no" title="function not covered" >checkParamSourceRefs(</span>
source: import('./ast.js').ParamSource | undefined,
file: string,
line: number,
models: Set&lt;string&gt;,
diag: DiagnosticCollector,
): void {
<span class="cstat-no" title="statement not covered" > if (!source) <span class="cstat-no" title="statement not covered" >return;</span></span>
<span class="cstat-no" title="statement not covered" > if (source.kind === 'ref') {</span>
<span class="cstat-no" title="statement not covered" > checkNameRef(source.name, file, line, models, diag);</span>
} else <span class="cstat-no" title="statement not covered" >if (source.kind === 'params') {</span>
<span class="cstat-no" title="statement not covered" > for (const param of source.nodes) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(param.type, file, param.loc.line, models, diag);</span>
}
} else {
<span class="cstat-no" title="statement not covered" > checkTypeRefs(source.node, file, line, models, diag);</span>
}
}
&nbsp;
function <span class="fstat-no" title="function not covered" >checkNameRef(n</span>ame: string, file: string, line: number, models: Set&lt;string&gt;, diag: DiagnosticCollector): void {
<span class="cstat-no" title="statement not covered" > if (/^[A-Z]/.test(name) &amp;&amp; !models.has(name)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `Referenced type "${name}" is not defined in any contract file`, 'unknown-model');</span>
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for tests/helpers.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">tests</a> helpers.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">22.44% </span>
<span class="quiet">Statements</span>
<span class='fraction'>11/49</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">6.45% </span>
<span class="quiet">Branches</span>
<span class='fraction'>2/31</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">24% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/25</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25.58% </span>
<span class="quiet">Lines</span>
<span class='fraction'>11/43</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type {
ContractRootNode,
ModelNode,
FieldNode,
ContractTypeNode,
ScalarTypeNode,
ArrayTypeNode,
TupleTypeNode,
RecordTypeNode,
EnumTypeNode,
LiteralTypeNode,
UnionTypeNode,
ModelRefTypeNode,
InlineObjectTypeNode,
LazyTypeNode,
SourceLocation,
OpRootNode,
OpRouteNode,
OpOperationNode,
OpParamNode,
OpRequestNode,
OpResponseNode,
HttpMethod,
ParamSource,
RouteModifier,
} from '../src/ast.js';
&nbsp;
// ─── AST Builder Helpers ────────────────────────────────────────────────────
&nbsp;
export function loc(line = 1, file = 'test.ck'): SourceLocation {
return { file, line };
}
&nbsp;
export function scalarType(name: ScalarTypeNode['name'], mods?: Partial&lt;ScalarTypeNode&gt;): ScalarTypeNode {
return { kind: 'scalar', name, ...mods };
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >arrayType(i</span>tem: ContractTypeNode, mods?: { min?: number; max?: number }): ArrayTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'array', item, ...mods };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >tupleType(.</span>..items: ContractTypeNode[]): TupleTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'tuple', items };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >recordType(k</span>ey: ContractTypeNode, value: ContractTypeNode): RecordTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'record', key, value };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >enumType(.</span>..values: string[]): EnumTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'enum', values };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >literalType(v</span>alue: string | number | boolean): LiteralTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'literal', value };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >unionType(.</span>..members: ContractTypeNode[]): UnionTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'union', members };</span>
}
&nbsp;
export function refType(name: string): ModelRefTypeNode {
return { kind: 'ref', name };
}
&nbsp;
export function inlineObjectType(fields: FieldNode[]): InlineObjectTypeNode {
return { kind: 'inlineObject', fields };
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >lazyType(i</span>nner: ContractTypeNode): LazyTypeNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'lazy', inner };</span>
}
&nbsp;
export function field(name: string, type: ContractTypeNode, overrides?: Partial&lt;FieldNode&gt;): FieldNode {
return {
name,
optional: false,
nullable: false,
visibility: 'normal',
type,
loc: loc(),
...overrides,
};
}
&nbsp;
export function model(name: string, fields: FieldNode[], overrides?: Partial&lt;ModelNode&gt;): ModelNode {
return {
kind: 'model',
name,
fields,
loc: loc(),
...overrides,
};
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >contractRoot(m</span>odels: ModelNode[], file = <span class="branch-0 cbranch-no" title="branch not covered" >'test.ck')</span>: ContractRootNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'contractRoot', meta: {}, models, file };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opParam(n</span>ame: string, type: ContractTypeNode): OpParamNode {
<span class="cstat-no" title="statement not covered" > return { name, type, loc: loc(1, 'test.op') };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >paramNodes(n</span>odes: OpParamNode[]): ParamSource {
<span class="cstat-no" title="statement not covered" > return { kind: 'params', nodes };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >paramRef(n</span>ame: string): ParamSource {
<span class="cstat-no" title="statement not covered" > return { kind: 'ref', name };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >paramType(n</span>ode: ContractTypeNode): ParamSource {
<span class="cstat-no" title="statement not covered" > return { kind: 'type', node };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opRequest(b</span>odyType: string | ContractTypeNode, contentType: OpRequestNode['contentType'] = <span class="branch-0 cbranch-no" title="branch not covered" >'application/json')</span>: OpRequestNode {
const bt: ContractTypeNode = <span class="cstat-no" title="statement not covered" >typeof bodyType === 'string' ? refType(bodyType) : bodyType;</span>
<span class="cstat-no" title="statement not covered" > return { contentType, bodyType: bt };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opResponse(s</span>tatusCode: number, bodyType?: string | ContractTypeNode, contentType?: 'application/json'): OpResponseNode {
const bt: ContractTypeNode | undefined = <span class="cstat-no" title="statement not covered" >bodyType === undefined ? undefined : typeof bodyType === 'string' ? parseBodyTypeString(bodyType) : bodyType;</span>
<span class="cstat-no" title="statement not covered" > return { statusCode, contentType, bodyType: bt };</span>
}
&nbsp;
function <span class="fstat-no" title="function not covered" >parseBodyTypeString(s</span>: string): ContractTypeNode {
const arrayMatch = <span class="cstat-no" title="statement not covered" >s.match(/^array\((.+)\)$/);</span>
<span class="cstat-no" title="statement not covered" > if (arrayMatch?.[1]) {</span>
<span class="cstat-no" title="statement not covered" > return { kind: 'array', item: refType(arrayMatch[1]) };</span>
}
<span class="cstat-no" title="statement not covered" > return refType(s);</span>
}
&nbsp;
/** Normalize a raw param value (old bare format or new discriminated union) to ParamSource. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function <span class="fstat-no" title="function not covered" >normalizeParamSource(v</span>alue: any): ParamSource {
<span class="cstat-no" title="statement not covered" > if (!value) <span class="cstat-no" title="statement not covered" >return value;</span></span>
<span class="cstat-no" title="statement not covered" > if (typeof value === 'string') <span class="cstat-no" title="statement not covered" >return { kind: 'ref', name: value };</span></span>
<span class="cstat-no" title="statement not covered" > if (Array.isArray(value)) <span class="cstat-no" title="statement not covered" >return { kind: 'params', nodes: value };</span></span>
<span class="cstat-no" title="statement not covered" > if (value.kind === 'params' || value.kind === 'ref' || value.kind === 'type') <span class="cstat-no" title="statement not covered" >return value as ParamSource;</span></span>
<span class="cstat-no" title="statement not covered" > return { kind: 'type', node: value as ContractTypeNode };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opOperation(m</span>ethod: HttpMethod, overrides?: Partial&lt;OpOperationNode&gt; &amp; { query?: unknown; headers?: unknown }): OpOperationNode {
const normalized = <span class="cstat-no" title="statement not covered" >{ ...overrides } as Partial&lt;OpOperationNode&gt;;</span>
<span class="cstat-no" title="statement not covered" > if (overrides?.query !== undefined) <span class="cstat-no" title="statement not covered" >normalized.query = normalizeParamSource(overrides.query);</span></span>
<span class="cstat-no" title="statement not covered" > if (overrides?.headers !== undefined) <span class="cstat-no" title="statement not covered" >normalized.headers = normalizeParamSource(overrides.headers);</span></span>
<span class="cstat-no" title="statement not covered" > return {</span>
method,
responses: [],
loc: loc(1, 'test.op'),
...normalized,
};
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opRoute(</span>
path: string,
operations: OpOperationNode[],
params?: ParamSource | OpParamNode[] | string,
modifiers?: RouteModifier[],
): OpRouteNode {
const normalizedParams = <span class="cstat-no" title="statement not covered" >params !== undefined ? normalizeParamSource(params) : undefined;</span>
<span class="cstat-no" title="statement not covered" > return { path, params: normalizedParams, operations, modifiers, loc: loc(1, 'test.op') };</span>
}
&nbsp;
export function <span class="fstat-no" title="function not covered" >opRoot(r</span>outes: OpRouteNode[], file = <span class="branch-0 cbranch-no" title="branch not covered" >'users.op',</span> meta: Record&lt;string, string&gt; = <span class="branch-0 cbranch-no" title="branch not covered" >{})</span>: OpRootNode {
<span class="cstat-no" title="statement not covered" > return { kind: 'opRoot', meta, routes, file };</span>
}
&nbsp;
// ─── DSL Fixture Strings ────────────────────────────────────────────────────
&nbsp;
export const SIMPLE_USER_CONTRACT = `\
contract User: {
id: readonly uuid
name: string
email: email
age?: number
active: boolean = true
}
`;
&nbsp;
export const VISIBILITY_CONTRACT = `\
contract User: {
id: readonly uuid
name: string
password: writeonly string
}
`;
&nbsp;
export const INHERITANCE_CONTRACT = `\
contract Admin: User &amp; {
role: enum(admin, superadmin)
}
`;
&nbsp;
export const SIMPLE_USERS_OP = `\
operation /users: {
get: {
response: {
200: {
application/json: array(User)
}
}
}
post: {
request: {
application/json: CreateUserInput
}
response: {
201: {
application/json: User
}
}
}
}
`;
&nbsp;
export const PARAMETERIZED_OP = `\
operation /users/{id}: {
params: {
id: uuid
}
get: {
response: {
200: {
application/json: User
}
}
}
delete: {}
}
`;
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for tests</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> tests</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">22.44% </span>
<span class="quiet">Statements</span>
<span class='fraction'>11/49</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">6.45% </span>
<span class="quiet">Branches</span>
<span class='fraction'>2/31</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">24% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/25</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25.58% </span>
<span class="quiet">Lines</span>
<span class='fraction'>11/43</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file low" data-value="helpers.ts"><a href="helpers.ts.html">helpers.ts</a></td>
<td data-value="22.44" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 22%"></div><div class="cover-empty" style="width: 78%"></div></div>
</td>
<td data-value="22.44" class="pct low">22.44%</td>
<td data-value="49" class="abs low">11/49</td>
<td data-value="6.45" class="pct low">6.45%</td>
<td data-value="31" class="abs low">2/31</td>
<td data-value="24" class="pct low">24%</td>
<td data-value="25" class="abs low">6/25</td>
<td data-value="25.58" class="pct low">25.58%</td>
<td data-value="43" class="abs low">11/43</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-15T11:53:20.190Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/ast.ts
var SCALAR_NAMES = /* @__PURE__ */ new Set([
"string",
"number",
"int",
"bigint",
"boolean",
"date",
"time",
"datetime",
"duration",
"interval",
"email",
"url",
"uuid",
"unknown",
"null",
"object",
"binary",
"json"
]);
var SECURITY_NONE = "none";
function resolveModifiers(route, op) {
const raw = op.modifiers ?? route.modifiers ?? [];
return raw.filter((m) => m !== "public");
}
__name(resolveModifiers, "resolveModifiers");
function resolveSecurity(route, op, root) {
if (op.security !== void 0) return op.security;
if (route.security !== void 0) return route.security;
return root?.security;
}
__name(resolveSecurity, "resolveSecurity");
// src/type-utils.ts
function resolveEffectiveFields(target, modelIndex) {
const unresolved = [];
const visited = /* @__PURE__ */ new Set();
const recordUnresolved = /* @__PURE__ */ __name((name) => {
if (!unresolved.includes(name)) unresolved.push(name);
}, "recordUnresolved");
const fromName = /* @__PURE__ */ __name((name) => {
if (visited.has(name)) return [];
visited.add(name);
const model = modelIndex.get(name);
if (!model) {
recordUnresolved(name);
return [];
}
if (model.type) return fromType(model.type);
return collectModelFields(model);
}, "fromName");
const collectModelFields = /* @__PURE__ */ __name((model) => {
const merged = [];
const index = /* @__PURE__ */ new Map();
const push = /* @__PURE__ */ __name((f) => {
const existing = index.get(f.name);
if (existing !== void 0) merged[existing] = f;
else {
index.set(f.name, merged.length);
merged.push(f);
}
}, "push");
if (model.bases) {
for (const base of model.bases) {
for (const f of fromName(base)) push(f);
}
}
for (const f of model.fields) push(f);
return merged;
}, "collectModelFields");
const fromType = /* @__PURE__ */ __name((type) => {
switch (type.kind) {
case "inlineObject":
return type.fields;
case "ref":
return fromName(type.name);
case "intersection": {
const merged = [];
const index = /* @__PURE__ */ new Map();
for (const member of type.members) {
for (const f of fromType(member)) {
const existing = index.get(f.name);
if (existing !== void 0) merged[existing] = f;
else {
index.set(f.name, merged.length);
merged.push(f);
}
}
}
return merged;
}
case "lazy":
return fromType(type.inner);
default:
return [];
}
}, "fromType");
const fields = typeof target === "string" ? fromName(target) : fromType(target);
return {
fields,
unresolved
};
}
__name(resolveEffectiveFields, "resolveEffectiveFields");
function buildModelIndex(models) {
const out = /* @__PURE__ */ new Map();
for (const m of models) out.set(m.name, m);
return out;
}
__name(buildModelIndex, "buildModelIndex");
function collectPublicTypeNames(root, modelsWithInput, modelsWithOutput) {
return new Set(collectTypes(root, modelsWithInput, modelsWithOutput));
}
__name(collectPublicTypeNames, "collectPublicTypeNames");
function collectTypes(root, modelsWithInput, modelsWithOutput) {
const types = /* @__PURE__ */ new Set();
for (const route of root.routes) {
const publicOps = route.operations.filter((op) => !resolveModifiers(route, op).includes("internal"));
if (publicOps.length === 0) continue;
collectParamSourceRefs(route.params, types);
collectParamSourceInputRefs(route.params, types, modelsWithInput);
for (const op of publicOps) {
if (op.request) {
for (const body of op.request.bodies) {
collectTypeNodeRefs(body.bodyType, types);
collectInputTypeNodeRefs(body.bodyType, types, modelsWithInput);
}
}
for (const resp of op.responses) {
if (resp.bodyType) {
collectTypeNodeRefs(resp.bodyType, types);
collectOutputTypeNodeRefs(resp.bodyType, types, modelsWithOutput);
}
}
collectParamSourceRefs(op.query, types);
collectParamSourceInputRefs(op.query, types, modelsWithInput);
collectParamSourceRefs(op.headers, types);
collectParamSourceInputRefs(op.headers, types, modelsWithInput);
}
}
return [
...types
].sort();
}
__name(collectTypes, "collectTypes");
function collectOutputTypeNodeRefs(type, out, modelsWithOutput) {
if (!modelsWithOutput) return;
switch (type.kind) {
case "ref":
if (modelsWithOutput.has(type.name)) out.add(`${type.name}Output`);
break;
case "array":
collectOutputTypeNodeRefs(type.item, out, modelsWithOutput);
break;
case "intersection":
case "union":
case "discriminatedUnion":
type.members.forEach((m) => collectOutputTypeNodeRefs(m, out, modelsWithOutput));
break;
case "inlineObject":
type.fields.forEach((f) => collectOutputTypeNodeRefs(f.type, out, modelsWithOutput));
break;
case "lazy":
collectOutputTypeNodeRefs(type.inner, out, modelsWithOutput);
break;
}
}
__name(collectOutputTypeNodeRefs, "collectOutputTypeNodeRefs");
function collectParamSourceInputRefs(source, out, modelsWithInput) {
if (!source || !modelsWithInput) return;
if (source.kind === "ref") {
if (modelsWithInput.has(source.name)) out.add(`${source.name}Input`);
} else if (source.kind === "params") {
for (const param of source.nodes) {
collectInputTypeNodeRefs(param.type, out, modelsWithInput);
}
} else {
collectInputTypeNodeRefs(source.node, out, modelsWithInput);
}
}
__name(collectParamSourceInputRefs, "collectParamSourceInputRefs");
function collectInputTypeNodeRefs(type, out, modelsWithInput) {
if (!modelsWithInput) return;
switch (type.kind) {
case "ref":
if (modelsWithInput.has(type.name)) out.add(`${type.name}Input`);
break;
case "array":
collectInputTypeNodeRefs(type.item, out, modelsWithInput);
break;
case "intersection":
case "union":
case "discriminatedUnion":
type.members.forEach((m) => collectInputTypeNodeRefs(m, out, modelsWithInput));
break;
case "inlineObject":
type.fields.forEach((f) => collectInputTypeNodeRefs(f.type, out, modelsWithInput));
break;
case "lazy":
collectInputTypeNodeRefs(type.inner, out, modelsWithInput);
break;
}
}
__name(collectInputTypeNodeRefs, "collectInputTypeNodeRefs");
function collectParamSourceRefs(source, out) {
if (!source) return;
if (source.kind === "ref") {
if (/^[A-Z]/.test(source.name)) out.add(source.name);
} else if (source.kind === "params") {
for (const param of source.nodes) {
collectTypeNodeRefs(param.type, out);
}
} else {
collectTypeNodeRefs(source.node, out);
}
}
__name(collectParamSourceRefs, "collectParamSourceRefs");
function collectTypeNodeRefs(type, out) {
switch (type.kind) {
case "ref":
if (/^[A-Z]/.test(type.name)) out.add(type.name);
break;
case "array":
collectTypeNodeRefs(type.item, out);
break;
case "tuple":
type.items.forEach((t) => collectTypeNodeRefs(t, out));
break;
case "record":
collectTypeNodeRefs(type.key, out);
collectTypeNodeRefs(type.value, out);
break;
case "union":
type.members.forEach((t) => collectTypeNodeRefs(t, out));
break;
case "discriminatedUnion":
type.members.forEach((t) => collectTypeNodeRefs(t, out));
break;
case "intersection":
type.members.forEach((t) => collectTypeNodeRefs(t, out));
break;
case "lazy":
collectTypeNodeRefs(type.inner, out);
break;
case "inlineObject":
type.fields.forEach((f) => collectTypeNodeRefs(f.type, out));
break;
}
}
__name(collectTypeNodeRefs, "collectTypeNodeRefs");
function collectTransitiveModelRefs(seedTypes, modelMap) {
const found = /* @__PURE__ */ new Set();
const queue = [
...seedTypes
];
while (queue.length > 0) {
const t = queue.pop();
const refs = /* @__PURE__ */ new Set();
collectTypeRefs(t, refs);
for (const ref of refs) {
if (found.has(ref)) continue;
found.add(ref);
const m = modelMap.get(ref);
if (!m) continue;
if (m.type) queue.push(m.type);
for (const f of m.fields) queue.push(f.type);
if (m.bases) {
for (const base of m.bases) {
if (found.has(base)) continue;
found.add(base);
const bm = modelMap.get(base);
if (!bm) continue;
if (bm.type) queue.push(bm.type);
for (const f of bm.fields) queue.push(f.type);
}
}
}
}
return found;
}
__name(collectTransitiveModelRefs, "collectTransitiveModelRefs");
function collectTypeRefs(type, out) {
switch (type.kind) {
case "ref":
out.add(type.name);
break;
case "array":
collectTypeRefs(type.item, out);
break;
case "tuple":
type.items.forEach((t) => collectTypeRefs(t, out));
break;
case "record":
collectTypeRefs(type.key, out);
collectTypeRefs(type.value, out);
break;
case "union":
type.members.forEach((t) => collectTypeRefs(t, out));
break;
case "discriminatedUnion":
type.members.forEach((t) => collectTypeRefs(t, out));
break;
case "intersection":
type.members.forEach((t) => collectTypeRefs(t, out));
break;
case "lazy":
collectTypeRefs(type.inner, out);
break;
case "inlineObject":
type.fields.forEach((f) => collectTypeRefs(f.type, out));
break;
}
}
__name(collectTypeRefs, "collectTypeRefs");
function computeModelsWithInput(models, externalModelsWithInput = /* @__PURE__ */ new Set()) {
const result = /* @__PURE__ */ new Set();
for (const model of models) {
if (model.fields.some((f) => f.visibility !== "normal")) {
result.add(model.name);
}
}
let changed = true;
while (changed) {
changed = false;
for (const model of models) {
if (result.has(model.name)) continue;
const refs = /* @__PURE__ */ new Set();
for (const field of model.fields) {
collectTypeRefs(field.type, refs);
}
if (model.bases) for (const b of model.bases) refs.add(b);
if (model.type) collectTypeRefs(model.type, refs);
for (const ref of refs) {
if (result.has(ref) || externalModelsWithInput.has(ref)) {
result.add(model.name);
changed = true;
break;
}
}
}
}
return result;
}
__name(computeModelsWithInput, "computeModelsWithInput");
function computeModelsWithOutput(models, externalModelsWithOutput = /* @__PURE__ */ new Set()) {
const result = /* @__PURE__ */ new Set();
for (const model of models) {
if (model.outputCase && model.outputCase !== "camel") {
result.add(model.name);
}
}
let changed = true;
while (changed) {
changed = false;
for (const model of models) {
if (result.has(model.name)) continue;
const refs = /* @__PURE__ */ new Set();
for (const field of model.fields) {
collectTypeRefs(field.type, refs);
}
if (model.bases) for (const b of model.bases) refs.add(b);
if (model.type) collectTypeRefs(model.type, refs);
for (const ref of refs) {
if (result.has(ref) || externalModelsWithOutput.has(ref)) {
result.add(model.name);
changed = true;
break;
}
}
}
}
return result;
}
__name(computeModelsWithOutput, "computeModelsWithOutput");
function collectExternalRefs(root) {
const localNames = new Set(root.models.map((m) => m.name));
const refs = /* @__PURE__ */ new Set();
for (const model of root.models) {
if (model.bases) {
for (const b of model.bases) if (!localNames.has(b)) refs.add(b);
}
if (model.type) collectTypeRefs(model.type, refs);
for (const field of model.fields) {
collectTypeRefs(field.type, refs);
}
}
for (const name of localNames) refs.delete(name);
return [
...refs
].sort();
}
__name(collectExternalRefs, "collectExternalRefs");
function collectExternalOutputRefs(root, modelsWithOutput) {
const localNames = new Set(root.models.map((m) => m.name));
const refs = /* @__PURE__ */ new Set();
for (const model of root.models) {
if (!modelsWithOutput.has(model.name)) continue;
if (model.type) {
collectOutputTypeRefsForExport(model.type, refs, modelsWithOutput);
continue;
}
if (model.bases) {
for (const base of model.bases) {
if (modelsWithOutput.has(base) && !localNames.has(base)) refs.add(`${base}Output`);
}
}
for (const field of model.fields) {
collectOutputTypeRefsForExport(field.type, refs, modelsWithOutput);
}
}
for (const name of localNames) {
refs.delete(`${name}Output`);
}
return [
...refs
].sort();
}
__name(collectExternalOutputRefs, "collectExternalOutputRefs");
function collectOutputTypeRefsForExport(type, out, modelsWithOutput) {
switch (type.kind) {
case "ref":
if (modelsWithOutput.has(type.name)) out.add(`${type.name}Output`);
break;
case "array":
collectOutputTypeRefsForExport(type.item, out, modelsWithOutput);
break;
case "tuple":
type.items.forEach((i) => collectOutputTypeRefsForExport(i, out, modelsWithOutput));
break;
case "record":
collectOutputTypeRefsForExport(type.key, out, modelsWithOutput);
collectOutputTypeRefsForExport(type.value, out, modelsWithOutput);
break;
case "union":
type.members.forEach((m) => collectOutputTypeRefsForExport(m, out, modelsWithOutput));
break;
case "discriminatedUnion":
type.members.forEach((m) => collectOutputTypeRefsForExport(m, out, modelsWithOutput));
break;
case "intersection":
type.members.forEach((m) => collectOutputTypeRefsForExport(m, out, modelsWithOutput));
break;
case "lazy":
collectOutputTypeRefsForExport(type.inner, out, modelsWithOutput);
break;
case "inlineObject":
type.fields.forEach((f) => collectOutputTypeRefsForExport(f.type, out, modelsWithOutput));
break;
}
}
__name(collectOutputTypeRefsForExport, "collectOutputTypeRefsForExport");
function collectExternalInputRefs(root, modelsWithInput) {
const localNames = new Set(root.models.map((m) => m.name));
const refs = /* @__PURE__ */ new Set();
for (const model of root.models) {
if (!modelsWithInput.has(model.name)) continue;
if (model.type) {
collectInputTypeRefsForExport(model.type, refs, modelsWithInput);
continue;
}
if (model.bases) {
for (const base of model.bases) {
if (modelsWithInput.has(base) && !localNames.has(base)) refs.add(`${base}Input`);
}
}
const writeFields = model.fields.filter((f) => f.visibility !== "readonly");
for (const field of writeFields) {
collectInputTypeRefsForExport(field.type, refs, modelsWithInput);
}
}
for (const name of localNames) {
refs.delete(`${name}Input`);
}
return [
...refs
].sort();
}
__name(collectExternalInputRefs, "collectExternalInputRefs");
function collectInputTypeRefsForExport(type, out, modelsWithInput) {
switch (type.kind) {
case "ref":
if (modelsWithInput.has(type.name)) out.add(`${type.name}Input`);
break;
case "array":
collectInputTypeRefsForExport(type.item, out, modelsWithInput);
break;
case "tuple":
type.items.forEach((i) => collectInputTypeRefsForExport(i, out, modelsWithInput));
break;
case "record":
collectInputTypeRefsForExport(type.key, out, modelsWithInput);
collectInputTypeRefsForExport(type.value, out, modelsWithInput);
break;
case "union":
type.members.forEach((m) => collectInputTypeRefsForExport(m, out, modelsWithInput));
break;
case "discriminatedUnion":
type.members.forEach((m) => collectInputTypeRefsForExport(m, out, modelsWithInput));
break;
case "intersection":
type.members.forEach((m) => collectInputTypeRefsForExport(m, out, modelsWithInput));
break;
case "lazy":
collectInputTypeRefsForExport(type.inner, out, modelsWithInput);
break;
case "inlineObject":
type.fields.forEach((f) => collectInputTypeRefsForExport(f.type, out, modelsWithInput));
break;
}
}
__name(collectInputTypeRefsForExport, "collectInputTypeRefsForExport");
function topoSortModels(models) {
const localNames = new Set(models.map((m) => m.name));
const modelMap = new Map(models.map((m) => [
m.name,
m
]));
const deps = /* @__PURE__ */ new Map();
for (const model of models) {
const refs = /* @__PURE__ */ new Set();
if (model.bases) {
for (const b of model.bases) if (localNames.has(b)) refs.add(b);
}
if (model.type) collectTypeRefs(model.type, refs);
for (const field of model.fields) {
collectTypeRefs(field.type, refs);
}
const localDeps = /* @__PURE__ */ new Set();
for (const r of refs) {
if (localNames.has(r) && r !== model.name) localDeps.add(r);
}
deps.set(model.name, localDeps);
}
const remaining = /* @__PURE__ */ new Map();
for (const [name, d] of deps) {
remaining.set(name, new Set(d));
}
const queue = [];
for (const name of localNames) {
if (remaining.get(name).size === 0) queue.push(name);
}
const sorted = [];
while (queue.length > 0) {
const name = queue.shift();
sorted.push(modelMap.get(name));
for (const [other, rem] of remaining) {
if (rem.delete(name) && rem.size === 0) {
queue.push(other);
}
}
}
for (const model of models) {
if (!sorted.includes(model)) sorted.push(model);
}
return sorted;
}
__name(topoSortModels, "topoSortModels");
function pascalToDotCase(name) {
return name.replace(/([a-z0-9])([A-Z])/g, "$1.$2").toLowerCase();
}
__name(pascalToDotCase, "pascalToDotCase");
export {
__name,
SCALAR_NAMES,
SECURITY_NONE,
resolveModifiers,
resolveSecurity,
resolveEffectiveFields,
buildModelIndex,
collectPublicTypeNames,
collectTransitiveModelRefs,
collectTypeRefs,
computeModelsWithInput,
computeModelsWithOutput,
collectExternalRefs,
collectExternalOutputRefs,
collectExternalInputRefs,
topoSortModels,
pascalToDotCase
};
//# sourceMappingURL=chunk-NK5CXYRY.js.map
{"version":3,"sources":["../src/ast.ts","../src/type-utils.ts"],"sourcesContent":["// ─── Shared ────────────────────────────────────────────────────────────────\n\nexport interface SourceLocation {\n file: string;\n line: number;\n}\n\nexport const SCALAR_NAMES: ReadonlySet<string> = new Set<ScalarTypeNode['name']>([\n 'string',\n 'number',\n 'int',\n 'bigint',\n 'boolean',\n 'date',\n 'time',\n 'datetime',\n 'duration',\n 'interval',\n 'email',\n 'url',\n 'uuid',\n 'unknown',\n 'null',\n 'object',\n 'binary',\n 'json',\n]);\n\n// ─── Contracts AST (.ck) ──────────────────────────────────────────────────\n\nexport type ContractTypeNode =\n | ScalarTypeNode\n | ArrayTypeNode\n | TupleTypeNode\n | RecordTypeNode\n | EnumTypeNode\n | LiteralTypeNode\n | UnionTypeNode\n | DiscriminatedUnionTypeNode\n | IntersectionTypeNode\n | ModelRefTypeNode\n | InlineObjectTypeNode\n | LazyTypeNode;\n\nexport interface ScalarTypeNode {\n kind: 'scalar';\n name:\n | 'string'\n | 'number'\n | 'int'\n | 'bigint'\n | 'boolean'\n | 'date'\n | 'time'\n | 'datetime'\n | 'duration'\n | 'interval'\n | 'email'\n | 'url'\n | 'uuid'\n | 'unknown'\n | 'null'\n | 'object'\n | 'binary'\n | 'json';\n min?: number | bigint | string;\n max?: number | bigint | string;\n len?: number;\n regex?: string;\n format?: string;\n}\n\nexport interface ArrayTypeNode {\n kind: 'array';\n item: ContractTypeNode;\n min?: number;\n max?: number;\n}\n\nexport interface TupleTypeNode {\n kind: 'tuple';\n items: ContractTypeNode[];\n}\n\nexport interface RecordTypeNode {\n kind: 'record';\n key: ContractTypeNode;\n value: ContractTypeNode;\n}\n\nexport interface EnumTypeNode {\n kind: 'enum';\n values: string[];\n}\n\nexport interface LiteralTypeNode {\n kind: 'literal';\n value: string | number | boolean;\n}\n\nexport interface UnionTypeNode {\n kind: 'union';\n members: ContractTypeNode[];\n}\n\nexport interface DiscriminatedUnionTypeNode {\n kind: 'discriminatedUnion';\n discriminator: string;\n members: ContractTypeNode[];\n}\n\nexport interface ModelRefTypeNode {\n kind: 'ref';\n name: string;\n lazy?: boolean;\n}\n\nexport interface InlineObjectTypeNode {\n kind: 'inlineObject';\n fields: FieldNode[];\n mode?: ObjectMode;\n}\n\nexport interface IntersectionTypeNode {\n kind: 'intersection';\n members: ContractTypeNode[];\n}\n\nexport interface LazyTypeNode {\n kind: 'lazy';\n inner: ContractTypeNode;\n}\n\nexport interface FieldNode {\n name: string;\n optional: boolean;\n nullable: boolean;\n visibility: 'readonly' | 'writeonly' | 'normal';\n type: ContractTypeNode;\n default?: string | number | boolean;\n deprecated?: boolean;\n /** Set when the field is declared with the `override` modifier — used by inheritance validation\n * to confirm the field is intentionally redeclaring a conflicting base field. */\n override?: boolean;\n description?: string;\n loc: SourceLocation;\n}\n\nexport interface ModelNode {\n kind: 'model';\n name: string;\n /** Names of base contracts this model extends, in left-to-right declaration order.\n * `contract C: A & B & { ... }` produces `bases: ['A', 'B']`. Empty/undefined for non-inherited models. */\n bases?: string[];\n fields: FieldNode[];\n type?: ContractTypeNode; // type alias: Name: typeExpression (fields will be empty)\n mode?: ObjectMode; // object validation mode — defaults to 'strict'\n inputCase?: 'camel' | 'snake' | 'pascal'; // format(input=) — key casing of incoming data\n outputCase?: 'camel' | 'snake' | 'pascal'; // format(output=) — key casing of emitted data\n deprecated?: boolean;\n description?: string;\n loc: SourceLocation;\n}\n\nexport interface ContractRootNode {\n kind: 'contractRoot';\n meta: Record<string, string>;\n /** Service name → module path mappings from `options { services { ... } }`. */\n services?: Record<string, string>;\n models: ModelNode[];\n file: string;\n /** Comment lines not attached to any node, sorted by line number. */\n orphanComments?: Array<{ line: number; text: string }>;\n}\n\n// ─── Operations AST (.op) ──────────────────────────────────────────────────\n\n/** Constrained security declaration. */\nexport interface SecurityFields {\n /** Named policy required for this endpoint, or `false` to explicitly bypass policy enforcement. */\n policy?: string | false;\n /** Inline comment attached to the `policy:` line. */\n policyDescription?: string;\n loc: SourceLocation;\n}\n\n/** Sentinel value for explicitly public endpoints (`security: none`). */\nexport const SECURITY_NONE = 'none' as const;\nexport type SecurityNone = typeof SECURITY_NONE;\n\n/** Security declaration: explicit public (`none`), or constrained auth fields. */\nexport type SecurityNode = SecurityNone | SecurityFields;\n\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';\n\n/** Controls how Zod handles unknown keys on an object schema. */\nexport type ObjectMode = 'strict' | 'strip' | 'loose';\n\n/** Visibility/lifecycle modifiers on routes and operations.\n * `public` is operation-only: overrides inherited route-level modifiers. */\nexport type RouteModifier = 'internal' | 'deprecated' | 'public';\n\n/** JSON-like value tree used for `plugins` entries — strings, numbers, booleans, null, nested objects, and arrays. */\nexport type PluginValue = string | number | boolean | null | PluginValue[] | { [key: string]: PluginValue };\n\nexport interface OpParamNode {\n name: string;\n optional: boolean;\n nullable: boolean;\n type: ContractTypeNode;\n default?: string | number | boolean;\n description?: string;\n loc: SourceLocation;\n}\n\n/** Either inline param declarations, a single type reference name, or a ContractTypeNode. */\nexport type ParamSource = { kind: 'params'; nodes: OpParamNode[] } | { kind: 'ref'; name: string } | { kind: 'type'; node: ContractTypeNode };\n\n/**\n * Recognized request mime types that codegen has dedicated handling for. Other strings are\n * still permitted (any RFC 6838-shaped `type/subtype`) and pass through unchanged; codegen\n * falls back to a JSON-ish default for `+json` suffixes and a generic body for everything else.\n */\nexport type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';\n\nexport interface OpRequestBodyNode {\n contentType: string;\n bodyType: ContractTypeNode;\n}\n\nexport interface OpRequestNode {\n bodies: OpRequestBodyNode[];\n}\n\nexport interface OpResponseHeaderNode {\n /** Header name as written in the .ck source (preserves casing/hyphens, e.g. `preference-applied`, `ETag`). */\n name: string;\n optional: boolean;\n type: ContractTypeNode;\n description?: string;\n}\n\nexport interface OpResponseNode {\n statusCode: number;\n contentType?: string;\n bodyType?: ContractTypeNode;\n /** Declared response headers for this status code. Undefined = none declared. */\n headers?: OpResponseHeaderNode[];\n /** Set when the status code body declares `headers: none` — suppresses options-level response header merge for this code. */\n headersOptOut?: boolean;\n}\n\nexport interface OpOperationNode {\n method: HttpMethod;\n name?: string; // e.g. \"Create an Offer\" — human-readable name for docs/collections\n service?: string; // e.g. \"LedgerService.updateCategoryNesting\"\n sdk?: string; // e.g. \"getUser\" — explicit SDK method name\n /** HMAC signature key name for this endpoint (e.g. `WEBHOOK_SECRET`). */\n signature?: string;\n /** Inline comment attached to the `signature:` line. */\n signatureDescription?: string;\n request?: OpRequestNode;\n responses: OpResponseNode[];\n query?: ParamSource;\n queryMode?: ObjectMode;\n headers?: ParamSource;\n headersMode?: ObjectMode;\n /** Set when the operation declares `headers: none` — suppresses options-level request header merge for this op. */\n requestHeadersOptOut?: boolean;\n security?: SecurityNode; // overrides config default; \"none\" = explicitly public\n /** Explicit modifiers. undefined = inherit from route; [] or array = override. */\n modifiers?: RouteModifier[];\n /** Raw plugin values from the grammar, e.g. `{ bruno: { template: \"file://request-token.yml\" } }`. */\n plugins?: Record<string, PluginValue>;\n /** Resolved plugin extension values keyed by plugin name. Populated by the CLI resolver — same shape as `plugins`, but every `file://` URL string is replaced with the file's contents. Never set by the parser. */\n pluginExtensions?: Record<string, PluginValue>;\n description?: string;\n loc: SourceLocation;\n}\n\nexport interface OpRouteNode {\n path: string;\n params?: ParamSource;\n paramsMode?: ObjectMode;\n operations: OpOperationNode[];\n /** Route-level modifiers — cascade to all operations unless overridden. */\n modifiers?: RouteModifier[];\n /** Route-level security default — cascades to operations that have no explicit security declaration. */\n security?: SecurityNode;\n description?: string;\n loc: SourceLocation;\n}\n\n/**\n * Resolves the effective modifiers for an operation, applying route-level cascade.\n * If the operation specifies any explicit modifiers, those replace (not merge) the route's.\n * `public` on an operation acts as an explicit override that clears inherited modifiers;\n * it is stripped from the returned array (it is not a codegen modifier itself).\n */\nexport function resolveModifiers(route: OpRouteNode, op: OpOperationNode): RouteModifier[] {\n const raw = op.modifiers ?? route.modifiers ?? [];\n return raw.filter(m => m !== 'public');\n}\n\n/**\n * Resolves the effective security for an operation, applying cascade from operation → route → file.\n * Operation-level security always wins; if absent, the route's security is used; if absent, the file's.\n */\nexport function resolveSecurity(route: OpRouteNode, op: OpOperationNode, root?: OpRootNode): SecurityNode | undefined {\n if (op.security !== undefined) return op.security;\n if (route.security !== undefined) return route.security;\n return root?.security;\n}\n\nexport interface OpRootNode {\n kind: 'opRoot';\n meta: Record<string, string>;\n /** Service name → module path mappings from `options { services { ... } }`. */\n services?: Record<string, string>;\n /** File-level security default — cascades to all routes/operations unless overridden. */\n security?: SecurityNode;\n /** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */\n requestHeaders?: OpResponseHeaderNode[];\n /** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */\n responseHeaders?: OpResponseHeaderNode[];\n routes: OpRouteNode[];\n file: string;\n /** Comment lines not attached to any node, sorted by line number. */\n orphanComments?: Array<{ line: number; text: string }>;\n}\n\n// ─── Unified AST (.ck) ───────────────────────────────────────────────────\n\nexport interface CkRootNode {\n kind: 'ckRoot';\n meta: Record<string, string>;\n services: Record<string, string>;\n /** File-level security default — cascades to all routes/operations unless overridden. */\n security?: SecurityNode;\n /** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */\n requestHeaders?: OpResponseHeaderNode[];\n /** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */\n responseHeaders?: OpResponseHeaderNode[];\n models: ModelNode[];\n routes: OpRouteNode[];\n file: string;\n}\n","import type { ContractTypeNode, ContractRootNode, FieldNode, ModelNode, OpRootNode, ParamSource } from './ast.js';\nimport { resolveModifiers } from './ast.js';\n\n// ─── Effective-field resolution ────────────────────────────────────────────\n\n/** Result of `resolveEffectiveFields` — the flattened field set plus any refs that\n * couldn't be resolved against the supplied model index. */\nexport interface EffectiveFields {\n fields: FieldNode[];\n /** Model names that the resolution touched but couldn't find in the index. */\n unresolved: string[];\n}\n\n/**\n * Flattens a type or model name into an effective field list, following all forms of\n * composition recognized by the contractkit language:\n *\n * - `contract Foo: { a: int }` → own fields\n * - `contract Foo: A & B & { c: int }` → bases (`A`, `B`) contribute, own fields appended\n * - `Foo: A & B` (no `{ ... }`) → type alias to intersection; both members contribute\n * - Alias chains (`Foo: SomeOther`), nested intersections, inline objects, and `lazy` wrappers\n * - Multi-base inheritance with diamond dedup (later declarations override earlier)\n * - Cycle protection (`A: B`, `B: A` → resolved once, no infinite loop)\n *\n * Shapes that can't contribute named fields (scalars, unions, enums, arrays, records,\n * tuples) yield an empty field list — they're meant to be rendered as their own thing,\n * not flattened. Unresolved refs are captured for the caller to surface as a diagnostic.\n */\nexport function resolveEffectiveFields(\n target: string | ContractTypeNode,\n modelIndex: ReadonlyMap<string, ModelNode>,\n): EffectiveFields {\n const unresolved: string[] = [];\n const visited = new Set<string>();\n\n const recordUnresolved = (name: string): void => {\n if (!unresolved.includes(name)) unresolved.push(name);\n };\n\n const fromName = (name: string): FieldNode[] => {\n if (visited.has(name)) return [];\n visited.add(name);\n const model = modelIndex.get(name);\n if (!model) {\n recordUnresolved(name);\n return [];\n }\n if (model.type) return fromType(model.type);\n return collectModelFields(model);\n };\n\n const collectModelFields = (model: ModelNode): FieldNode[] => {\n const merged: FieldNode[] = [];\n const index = new Map<string, number>();\n const push = (f: FieldNode): void => {\n const existing = index.get(f.name);\n if (existing !== undefined) merged[existing] = f;\n else {\n index.set(f.name, merged.length);\n merged.push(f);\n }\n };\n if (model.bases) {\n for (const base of model.bases) {\n for (const f of fromName(base)) push(f);\n }\n }\n for (const f of model.fields) push(f);\n return merged;\n };\n\n const fromType = (type: ContractTypeNode): FieldNode[] => {\n switch (type.kind) {\n case 'inlineObject': return type.fields;\n case 'ref': return fromName(type.name);\n case 'intersection': {\n const merged: FieldNode[] = [];\n const index = new Map<string, number>();\n for (const member of type.members) {\n for (const f of fromType(member)) {\n const existing = index.get(f.name);\n if (existing !== undefined) merged[existing] = f;\n else {\n index.set(f.name, merged.length);\n merged.push(f);\n }\n }\n }\n return merged;\n }\n case 'lazy': return fromType(type.inner);\n default: return [];\n }\n };\n\n const fields = typeof target === 'string' ? fromName(target) : fromType(target);\n return { fields, unresolved };\n}\n\n/** Builds a lookup map suitable for {@link resolveEffectiveFields}. */\nexport function buildModelIndex(models: readonly ModelNode[]): Map<string, ModelNode> {\n const out = new Map<string, ModelNode>();\n for (const m of models) out.set(m.name, m);\n return out;\n}\n\n// ─── Type collection ──────────────────────────────────────────────────────\n\n/**\n * Returns the set of type names directly referenced by public (non-internal)\n * operations in the root. Does not include transitive dependencies — callers\n * should expand these through the contract model graph if needed.\n */\nexport function collectPublicTypeNames(root: OpRootNode, modelsWithInput?: Set<string>, modelsWithOutput?: Set<string>): Set<string> {\n return new Set(collectTypes(root, modelsWithInput, modelsWithOutput));\n}\n\nfunction collectTypes(root: OpRootNode, modelsWithInput?: Set<string>, modelsWithOutput?: Set<string>): string[] {\n const types = new Set<string>();\n for (const route of root.routes) {\n const publicOps = route.operations.filter(op => !resolveModifiers(route, op).includes('internal'));\n if (publicOps.length === 0) continue;\n // Only collect path-param types if there are public ops on this route\n collectParamSourceRefs(route.params, types);\n collectParamSourceInputRefs(route.params, types, modelsWithInput);\n for (const op of publicOps) {\n if (op.request) {\n for (const body of op.request.bodies) {\n collectTypeNodeRefs(body.bodyType, types);\n collectInputTypeNodeRefs(body.bodyType, types, modelsWithInput);\n }\n }\n for (const resp of op.responses) {\n if (resp.bodyType) {\n collectTypeNodeRefs(resp.bodyType, types);\n collectOutputTypeNodeRefs(resp.bodyType, types, modelsWithOutput);\n }\n }\n collectParamSourceRefs(op.query, types);\n collectParamSourceInputRefs(op.query, types, modelsWithInput);\n collectParamSourceRefs(op.headers, types);\n collectParamSourceInputRefs(op.headers, types, modelsWithInput);\n }\n }\n return [...types].sort();\n}\n\n/** Collect Output variant refs for response-side ContractTypeNode types. */\nfunction collectOutputTypeNodeRefs(type: ContractTypeNode, out: Set<string>, modelsWithOutput?: Set<string>): void {\n if (!modelsWithOutput) return;\n switch (type.kind) {\n case 'ref':\n if (modelsWithOutput.has(type.name)) out.add(`${type.name}Output`);\n break;\n case 'array':\n collectOutputTypeNodeRefs(type.item, out, modelsWithOutput);\n break;\n case 'intersection':\n case 'union':\n case 'discriminatedUnion':\n type.members.forEach(m => collectOutputTypeNodeRefs(m, out, modelsWithOutput));\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectOutputTypeNodeRefs(f.type, out, modelsWithOutput));\n break;\n case 'lazy':\n collectOutputTypeNodeRefs(type.inner, out, modelsWithOutput);\n break;\n }\n}\n\n/** Collect Input variant refs for request-side ParamSource types. */\nfunction collectParamSourceInputRefs(source: ParamSource | undefined, out: Set<string>, modelsWithInput?: Set<string>): void {\n if (!source || !modelsWithInput) return;\n if (source.kind === 'ref') {\n if (modelsWithInput.has(source.name)) out.add(`${source.name}Input`);\n } else if (source.kind === 'params') {\n for (const param of source.nodes) {\n collectInputTypeNodeRefs(param.type, out, modelsWithInput);\n }\n } else {\n collectInputTypeNodeRefs(source.node, out, modelsWithInput);\n }\n}\n\n/** Collect Input variant refs for request-side ContractTypeNode types. */\nfunction collectInputTypeNodeRefs(type: ContractTypeNode, out: Set<string>, modelsWithInput?: Set<string>): void {\n if (!modelsWithInput) return;\n switch (type.kind) {\n case 'ref':\n if (modelsWithInput.has(type.name)) out.add(`${type.name}Input`);\n break;\n case 'array':\n collectInputTypeNodeRefs(type.item, out, modelsWithInput);\n break;\n case 'intersection':\n case 'union':\n case 'discriminatedUnion':\n type.members.forEach(m => collectInputTypeNodeRefs(m, out, modelsWithInput));\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectInputTypeNodeRefs(f.type, out, modelsWithInput));\n break;\n case 'lazy':\n collectInputTypeNodeRefs(type.inner, out, modelsWithInput);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n if (/^[A-Z]/.test(source.name)) out.add(source.name);\n } else if (source.kind === 'params') {\n for (const param of source.nodes) {\n collectTypeNodeRefs(param.type, out);\n }\n } else {\n collectTypeNodeRefs(source.node, out);\n }\n}\n\nfunction collectTypeNodeRefs(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n if (/^[A-Z]/.test(type.name)) out.add(type.name);\n break;\n case 'array':\n collectTypeNodeRefs(type.item, out);\n break;\n case 'tuple':\n type.items.forEach(t => collectTypeNodeRefs(t, out));\n break;\n case 'record':\n collectTypeNodeRefs(type.key, out);\n collectTypeNodeRefs(type.value, out);\n break;\n case 'union':\n type.members.forEach(t => collectTypeNodeRefs(t, out));\n break;\n case 'discriminatedUnion':\n type.members.forEach(t => collectTypeNodeRefs(t, out));\n break;\n case 'intersection':\n type.members.forEach(t => collectTypeNodeRefs(t, out));\n break;\n case 'lazy':\n collectTypeNodeRefs(type.inner, out);\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectTypeNodeRefs(f.type, out));\n break;\n }\n}\n\n/**\n * Walk the model graph from a set of seed types and return every transitively\n * referenced model name. Bases are followed; aliased model `type` and field types\n * are queued. Useful for cache fingerprinting — gives the slice of the model\n * universe a particular op or contract root depends on.\n *\n * Models referenced by name but not present in `modelMap` are still included in\n * the result (so a fingerprint that mentions them will detect when they appear\n * later), but no further traversal happens through them.\n */\nexport function collectTransitiveModelRefs(seedTypes: ContractTypeNode[], modelMap: Map<string, ModelNode>): Set<string> {\n const found = new Set<string>();\n const queue: ContractTypeNode[] = [...seedTypes];\n while (queue.length > 0) {\n const t = queue.pop()!;\n const refs = new Set<string>();\n collectTypeRefs(t, refs);\n for (const ref of refs) {\n if (found.has(ref)) continue;\n found.add(ref);\n const m = modelMap.get(ref);\n if (!m) continue;\n if (m.type) queue.push(m.type);\n for (const f of m.fields) queue.push(f.type);\n if (m.bases) {\n for (const base of m.bases) {\n if (found.has(base)) continue;\n found.add(base);\n const bm = modelMap.get(base);\n if (!bm) continue;\n if (bm.type) queue.push(bm.type);\n for (const f of bm.fields) queue.push(f.type);\n }\n }\n }\n }\n return found;\n}\n\nexport function collectTypeRefs(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectTypeRefs(type.item, out);\n break;\n case 'tuple':\n type.items.forEach(t => collectTypeRefs(t, out));\n break;\n case 'record':\n collectTypeRefs(type.key, out);\n collectTypeRefs(type.value, out);\n break;\n case 'union':\n type.members.forEach(t => collectTypeRefs(t, out));\n break;\n case 'discriminatedUnion':\n type.members.forEach(t => collectTypeRefs(t, out));\n break;\n case 'intersection':\n type.members.forEach(t => collectTypeRefs(t, out));\n break;\n case 'lazy':\n collectTypeRefs(type.inner, out);\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectTypeRefs(f.type, out));\n break;\n }\n}\n\n// ─── Contract model utilities ─────────────────────────────────────────────\n\n/**\n * Compute which models need Input variants, including transitive dependencies.\n * A model needs an Input variant if it has visibility-modified fields, OR if\n * any of its field types (recursively) reference a model that has an Input variant.\n */\nexport function computeModelsWithInput(models: ModelNode[], externalModelsWithInput: Set<string> = new Set()): Set<string> {\n const result = new Set<string>();\n\n // Initial pass: direct visibility modifiers\n for (const model of models) {\n if (model.fields.some(f => f.visibility !== 'normal')) {\n result.add(model.name);\n }\n }\n\n // Transitive closure\n let changed = true;\n while (changed) {\n changed = false;\n for (const model of models) {\n if (result.has(model.name)) continue;\n const refs = new Set<string>();\n for (const field of model.fields) {\n collectTypeRefs(field.type, refs);\n }\n if (model.bases) for (const b of model.bases) refs.add(b);\n if (model.type) collectTypeRefs(model.type, refs);\n for (const ref of refs) {\n if (result.has(ref) || externalModelsWithInput.has(ref)) {\n result.add(model.name);\n changed = true;\n break;\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Compute which models need Output variants (post-transform wire shape),\n * including transitive dependencies. A model needs an Output variant if it\n * has `format(output=...)` set to a non-camel case, OR if any of its field\n * types (recursively) reference a model that has an Output variant.\n */\nexport function computeModelsWithOutput(models: ModelNode[], externalModelsWithOutput: Set<string> = new Set()): Set<string> {\n const result = new Set<string>();\n\n // Initial pass: direct outputCase transforms\n for (const model of models) {\n if (model.outputCase && model.outputCase !== 'camel') {\n result.add(model.name);\n }\n }\n\n // Transitive closure\n let changed = true;\n while (changed) {\n changed = false;\n for (const model of models) {\n if (result.has(model.name)) continue;\n const refs = new Set<string>();\n for (const field of model.fields) {\n collectTypeRefs(field.type, refs);\n }\n if (model.bases) for (const b of model.bases) refs.add(b);\n if (model.type) collectTypeRefs(model.type, refs);\n for (const ref of refs) {\n if (result.has(ref) || externalModelsWithOutput.has(ref)) {\n result.add(model.name);\n changed = true;\n break;\n }\n }\n }\n }\n\n return result;\n}\n\nexport function collectExternalRefs(root: ContractRootNode): string[] {\n const localNames = new Set(root.models.map(m => m.name));\n const refs = new Set<string>();\n\n for (const model of root.models) {\n if (model.bases) for (const b of model.bases) if (!localNames.has(b)) refs.add(b);\n if (model.type) collectTypeRefs(model.type, refs);\n for (const field of model.fields) {\n collectTypeRefs(field.type, refs);\n }\n }\n\n for (const name of localNames) refs.delete(name);\n return [...refs].sort();\n}\n\n/** Collect external Output variant refs needed for Output schema fields. */\nexport function collectExternalOutputRefs(root: ContractRootNode, modelsWithOutput: Set<string>): string[] {\n const localNames = new Set(root.models.map(m => m.name));\n const refs = new Set<string>();\n\n for (const model of root.models) {\n if (!modelsWithOutput.has(model.name)) continue;\n if (model.type) {\n collectOutputTypeRefsForExport(model.type, refs, modelsWithOutput);\n continue;\n }\n if (model.bases) {\n for (const base of model.bases) {\n if (modelsWithOutput.has(base) && !localNames.has(base)) refs.add(`${base}Output`);\n }\n }\n for (const field of model.fields) {\n collectOutputTypeRefsForExport(field.type, refs, modelsWithOutput);\n }\n }\n\n for (const name of localNames) {\n refs.delete(`${name}Output`);\n }\n\n return [...refs].sort();\n}\n\nfunction collectOutputTypeRefsForExport(type: ContractTypeNode, out: Set<string>, modelsWithOutput: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n if (modelsWithOutput.has(type.name)) out.add(`${type.name}Output`);\n break;\n case 'array':\n collectOutputTypeRefsForExport(type.item, out, modelsWithOutput);\n break;\n case 'tuple':\n type.items.forEach(i => collectOutputTypeRefsForExport(i, out, modelsWithOutput));\n break;\n case 'record':\n collectOutputTypeRefsForExport(type.key, out, modelsWithOutput);\n collectOutputTypeRefsForExport(type.value, out, modelsWithOutput);\n break;\n case 'union':\n type.members.forEach(m => collectOutputTypeRefsForExport(m, out, modelsWithOutput));\n break;\n case 'discriminatedUnion':\n type.members.forEach(m => collectOutputTypeRefsForExport(m, out, modelsWithOutput));\n break;\n case 'intersection':\n type.members.forEach(m => collectOutputTypeRefsForExport(m, out, modelsWithOutput));\n break;\n case 'lazy':\n collectOutputTypeRefsForExport(type.inner, out, modelsWithOutput);\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectOutputTypeRefsForExport(f.type, out, modelsWithOutput));\n break;\n }\n}\n\n/** Collect external Input variant refs needed for Input schema fields. */\nexport function collectExternalInputRefs(root: ContractRootNode, modelsWithInput: Set<string>): string[] {\n const localNames = new Set(root.models.map(m => m.name));\n const refs = new Set<string>();\n\n for (const model of root.models) {\n if (!modelsWithInput.has(model.name)) continue;\n if (model.type) {\n collectInputTypeRefsForExport(model.type, refs, modelsWithInput);\n continue;\n }\n if (model.bases) {\n for (const base of model.bases) {\n if (modelsWithInput.has(base) && !localNames.has(base)) refs.add(`${base}Input`);\n }\n }\n const writeFields = model.fields.filter(f => f.visibility !== 'readonly');\n for (const field of writeFields) {\n collectInputTypeRefsForExport(field.type, refs, modelsWithInput);\n }\n }\n\n for (const name of localNames) {\n refs.delete(`${name}Input`);\n }\n\n return [...refs].sort();\n}\n\nfunction collectInputTypeRefsForExport(type: ContractTypeNode, out: Set<string>, modelsWithInput: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n if (modelsWithInput.has(type.name)) out.add(`${type.name}Input`);\n break;\n case 'array':\n collectInputTypeRefsForExport(type.item, out, modelsWithInput);\n break;\n case 'tuple':\n type.items.forEach(i => collectInputTypeRefsForExport(i, out, modelsWithInput));\n break;\n case 'record':\n collectInputTypeRefsForExport(type.key, out, modelsWithInput);\n collectInputTypeRefsForExport(type.value, out, modelsWithInput);\n break;\n case 'union':\n type.members.forEach(m => collectInputTypeRefsForExport(m, out, modelsWithInput));\n break;\n case 'discriminatedUnion':\n type.members.forEach(m => collectInputTypeRefsForExport(m, out, modelsWithInput));\n break;\n case 'intersection':\n type.members.forEach(m => collectInputTypeRefsForExport(m, out, modelsWithInput));\n break;\n case 'lazy':\n collectInputTypeRefsForExport(type.inner, out, modelsWithInput);\n break;\n case 'inlineObject':\n type.fields.forEach(f => collectInputTypeRefsForExport(f.type, out, modelsWithInput));\n break;\n }\n}\n\n/**\n * Topologically sort models so dependencies are emitted before dependents.\n * Falls back to source order for cycles.\n */\nexport function topoSortModels(models: ModelNode[]): ModelNode[] {\n const localNames = new Set(models.map(m => m.name));\n const modelMap = new Map(models.map(m => [m.name, m]));\n\n const deps = new Map<string, Set<string>>();\n for (const model of models) {\n const refs = new Set<string>();\n if (model.bases) for (const b of model.bases) if (localNames.has(b)) refs.add(b);\n if (model.type) collectTypeRefs(model.type, refs);\n for (const field of model.fields) {\n collectTypeRefs(field.type, refs);\n }\n const localDeps = new Set<string>();\n for (const r of refs) {\n if (localNames.has(r) && r !== model.name) localDeps.add(r);\n }\n deps.set(model.name, localDeps);\n }\n\n const remaining = new Map<string, Set<string>>();\n for (const [name, d] of deps) {\n remaining.set(name, new Set(d));\n }\n\n const queue: string[] = [];\n for (const name of localNames) {\n if (remaining.get(name)!.size === 0) queue.push(name);\n }\n\n const sorted: ModelNode[] = [];\n while (queue.length > 0) {\n const name = queue.shift()!;\n sorted.push(modelMap.get(name)!);\n for (const [other, rem] of remaining) {\n if (rem.delete(name) && rem.size === 0) {\n queue.push(other);\n }\n }\n }\n\n for (const model of models) {\n if (!sorted.includes(model)) sorted.push(model);\n }\n\n return sorted;\n}\n\n/** Convert PascalCase to dot-separated lowercase: CounterpartyAccount → counterparty.account */\nexport function pascalToDotCase(name: string): string {\n return name.replace(/([a-z0-9])([A-Z])/g, '$1.$2').toLowerCase();\n}\n"],"mappings":";;;;AAOO,IAAMA,eAAoC,oBAAIC,IAA4B;EAC7E;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACH;AAiKM,IAAMC,gBAAgB;AAgHtB,SAASC,iBAAiBC,OAAoBC,IAAmB;AACpE,QAAMC,MAAMD,GAAGE,aAAaH,MAAMG,aAAa,CAAA;AAC/C,SAAOD,IAAIE,OAAOC,CAAAA,MAAKA,MAAM,QAAA;AACjC;AAHgBN;AAST,SAASO,gBAAgBN,OAAoBC,IAAqBM,MAAiB;AACtF,MAAIN,GAAGO,aAAaC,OAAW,QAAOR,GAAGO;AACzC,MAAIR,MAAMQ,aAAaC,OAAW,QAAOT,MAAMQ;AAC/C,SAAOD,MAAMC;AACjB;AAJgBF;;;ACxRT,SAASI,uBACZC,QACAC,YAA0C;AAE1C,QAAMC,aAAuB,CAAA;AAC7B,QAAMC,UAAU,oBAAIC,IAAAA;AAEpB,QAAMC,mBAAmB,wBAACC,SAAAA;AACtB,QAAI,CAACJ,WAAWK,SAASD,IAAAA,EAAOJ,YAAWM,KAAKF,IAAAA;EACpD,GAFyB;AAIzB,QAAMG,WAAW,wBAACH,SAAAA;AACd,QAAIH,QAAQO,IAAIJ,IAAAA,EAAO,QAAO,CAAA;AAC9BH,YAAQQ,IAAIL,IAAAA;AACZ,UAAMM,QAAQX,WAAWY,IAAIP,IAAAA;AAC7B,QAAI,CAACM,OAAO;AACRP,uBAAiBC,IAAAA;AACjB,aAAO,CAAA;IACX;AACA,QAAIM,MAAME,KAAM,QAAOC,SAASH,MAAME,IAAI;AAC1C,WAAOE,mBAAmBJ,KAAAA;EAC9B,GAViB;AAYjB,QAAMI,qBAAqB,wBAACJ,UAAAA;AACxB,UAAMK,SAAsB,CAAA;AAC5B,UAAMC,QAAQ,oBAAIC,IAAAA;AAClB,UAAMX,OAAO,wBAACY,MAAAA;AACV,YAAMC,WAAWH,MAAML,IAAIO,EAAEd,IAAI;AACjC,UAAIe,aAAaC,OAAWL,QAAOI,QAAAA,IAAYD;WAC1C;AACDF,cAAMK,IAAIH,EAAEd,MAAMW,OAAOO,MAAM;AAC/BP,eAAOT,KAAKY,CAAAA;MAChB;IACJ,GAPa;AAQb,QAAIR,MAAMa,OAAO;AACb,iBAAWC,QAAQd,MAAMa,OAAO;AAC5B,mBAAWL,KAAKX,SAASiB,IAAAA,EAAOlB,MAAKY,CAAAA;MACzC;IACJ;AACA,eAAWA,KAAKR,MAAMe,OAAQnB,MAAKY,CAAAA;AACnC,WAAOH;EACX,GAlB2B;AAoB3B,QAAMF,WAAW,wBAACD,SAAAA;AACd,YAAQA,KAAKc,MAAI;MACb,KAAK;AAAgB,eAAOd,KAAKa;MACjC,KAAK;AAAO,eAAOlB,SAASK,KAAKR,IAAI;MACrC,KAAK,gBAAgB;AACjB,cAAMW,SAAsB,CAAA;AAC5B,cAAMC,QAAQ,oBAAIC,IAAAA;AAClB,mBAAWU,UAAUf,KAAKgB,SAAS;AAC/B,qBAAWV,KAAKL,SAASc,MAAAA,GAAS;AAC9B,kBAAMR,WAAWH,MAAML,IAAIO,EAAEd,IAAI;AACjC,gBAAIe,aAAaC,OAAWL,QAAOI,QAAAA,IAAYD;iBAC1C;AACDF,oBAAMK,IAAIH,EAAEd,MAAMW,OAAOO,MAAM;AAC/BP,qBAAOT,KAAKY,CAAAA;YAChB;UACJ;QACJ;AACA,eAAOH;MACX;MACA,KAAK;AAAQ,eAAOF,SAASD,KAAKiB,KAAK;MACvC;AAAS,eAAO,CAAA;IACpB;EACJ,GAtBiB;AAwBjB,QAAMJ,SAAS,OAAO3B,WAAW,WAAWS,SAAST,MAAAA,IAAUe,SAASf,MAAAA;AACxE,SAAO;IAAE2B;IAAQzB;EAAW;AAChC;AArEgBH;AAwET,SAASiC,gBAAgBC,QAA4B;AACxD,QAAMC,MAAM,oBAAIf,IAAAA;AAChB,aAAWgB,KAAKF,OAAQC,KAAIX,IAAIY,EAAE7B,MAAM6B,CAAAA;AACxC,SAAOD;AACX;AAJgBF;AAaT,SAASI,uBAAuBC,MAAkBC,iBAA+BC,kBAA8B;AAClH,SAAO,IAAInC,IAAIoC,aAAaH,MAAMC,iBAAiBC,gBAAAA,CAAAA;AACvD;AAFgBH;AAIhB,SAASI,aAAaH,MAAkBC,iBAA+BC,kBAA8B;AACjG,QAAME,QAAQ,oBAAIrC,IAAAA;AAClB,aAAWsC,SAASL,KAAKM,QAAQ;AAC7B,UAAMC,YAAYF,MAAMG,WAAWC,OAAOC,CAAAA,OAAM,CAACC,iBAAiBN,OAAOK,EAAAA,EAAIxC,SAAS,UAAA,CAAA;AACtF,QAAIqC,UAAUpB,WAAW,EAAG;AAE5ByB,2BAAuBP,MAAMQ,QAAQT,KAAAA;AACrCU,gCAA4BT,MAAMQ,QAAQT,OAAOH,eAAAA;AACjD,eAAWS,MAAMH,WAAW;AACxB,UAAIG,GAAGK,SAAS;AACZ,mBAAWC,QAAQN,GAAGK,QAAQE,QAAQ;AAClCC,8BAAoBF,KAAKG,UAAUf,KAAAA;AACnCgB,mCAAyBJ,KAAKG,UAAUf,OAAOH,eAAAA;QACnD;MACJ;AACA,iBAAWoB,QAAQX,GAAGY,WAAW;AAC7B,YAAID,KAAKF,UAAU;AACfD,8BAAoBG,KAAKF,UAAUf,KAAAA;AACnCmB,oCAA0BF,KAAKF,UAAUf,OAAOF,gBAAAA;QACpD;MACJ;AACAU,6BAAuBF,GAAGc,OAAOpB,KAAAA;AACjCU,kCAA4BJ,GAAGc,OAAOpB,OAAOH,eAAAA;AAC7CW,6BAAuBF,GAAGe,SAASrB,KAAAA;AACnCU,kCAA4BJ,GAAGe,SAASrB,OAAOH,eAAAA;IACnD;EACJ;AACA,SAAO;OAAIG;IAAOsB,KAAI;AAC1B;AA5BSvB;AA+BT,SAASoB,0BAA0B9C,MAAwBoB,KAAkBK,kBAA8B;AACvG,MAAI,CAACA,iBAAkB;AACvB,UAAQzB,KAAKc,MAAI;IACb,KAAK;AACD,UAAIW,iBAAiB7B,IAAII,KAAKR,IAAI,EAAG4B,KAAIvB,IAAI,GAAGG,KAAKR,IAAI,QAAQ;AACjE;IACJ,KAAK;AACDsD,gCAA0B9C,KAAKkD,MAAM9B,KAAKK,gBAAAA;AAC1C;IACJ,KAAK;IACL,KAAK;IACL,KAAK;AACDzB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKyB,0BAA0BzB,GAAGD,KAAKK,gBAAAA,CAAAA;AAC5D;IACJ,KAAK;AACDzB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAKwC,0BAA0BxC,EAAEN,MAAMoB,KAAKK,gBAAAA,CAAAA;AAChE;IACJ,KAAK;AACDqB,gCAA0B9C,KAAKiB,OAAOG,KAAKK,gBAAAA;AAC3C;EACR;AACJ;AArBSqB;AAwBT,SAAST,4BAA4Be,QAAiChC,KAAkBI,iBAA6B;AACjH,MAAI,CAAC4B,UAAU,CAAC5B,gBAAiB;AACjC,MAAI4B,OAAOtC,SAAS,OAAO;AACvB,QAAIU,gBAAgB5B,IAAIwD,OAAO5D,IAAI,EAAG4B,KAAIvB,IAAI,GAAGuD,OAAO5D,IAAI,OAAO;EACvE,WAAW4D,OAAOtC,SAAS,UAAU;AACjC,eAAWuC,SAASD,OAAOE,OAAO;AAC9BX,+BAAyBU,MAAMrD,MAAMoB,KAAKI,eAAAA;IAC9C;EACJ,OAAO;AACHmB,6BAAyBS,OAAOG,MAAMnC,KAAKI,eAAAA;EAC/C;AACJ;AAXSa;AAcT,SAASM,yBAAyB3C,MAAwBoB,KAAkBI,iBAA6B;AACrG,MAAI,CAACA,gBAAiB;AACtB,UAAQxB,KAAKc,MAAI;IACb,KAAK;AACD,UAAIU,gBAAgB5B,IAAII,KAAKR,IAAI,EAAG4B,KAAIvB,IAAI,GAAGG,KAAKR,IAAI,OAAO;AAC/D;IACJ,KAAK;AACDmD,+BAAyB3C,KAAKkD,MAAM9B,KAAKI,eAAAA;AACzC;IACJ,KAAK;IACL,KAAK;IACL,KAAK;AACDxB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKsB,yBAAyBtB,GAAGD,KAAKI,eAAAA,CAAAA;AAC3D;IACJ,KAAK;AACDxB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAKqC,yBAAyBrC,EAAEN,MAAMoB,KAAKI,eAAAA,CAAAA;AAC/D;IACJ,KAAK;AACDmB,+BAAyB3C,KAAKiB,OAAOG,KAAKI,eAAAA;AAC1C;EACR;AACJ;AArBSmB;AAuBT,SAASR,uBAAuBiB,QAAiChC,KAAgB;AAC7E,MAAI,CAACgC,OAAQ;AACb,MAAIA,OAAOtC,SAAS,OAAO;AACvB,QAAI,SAAS0C,KAAKJ,OAAO5D,IAAI,EAAG4B,KAAIvB,IAAIuD,OAAO5D,IAAI;EACvD,WAAW4D,OAAOtC,SAAS,UAAU;AACjC,eAAWuC,SAASD,OAAOE,OAAO;AAC9Bb,0BAAoBY,MAAMrD,MAAMoB,GAAAA;IACpC;EACJ,OAAO;AACHqB,wBAAoBW,OAAOG,MAAMnC,GAAAA;EACrC;AACJ;AAXSe;AAaT,SAASM,oBAAoBzC,MAAwBoB,KAAgB;AACjE,UAAQpB,KAAKc,MAAI;IACb,KAAK;AACD,UAAI,SAAS0C,KAAKxD,KAAKR,IAAI,EAAG4B,KAAIvB,IAAIG,KAAKR,IAAI;AAC/C;IACJ,KAAK;AACDiD,0BAAoBzC,KAAKkD,MAAM9B,GAAAA;AAC/B;IACJ,KAAK;AACDpB,WAAKyD,MAAMN,QAAQO,CAAAA,MAAKjB,oBAAoBiB,GAAGtC,GAAAA,CAAAA;AAC/C;IACJ,KAAK;AACDqB,0BAAoBzC,KAAK2D,KAAKvC,GAAAA;AAC9BqB,0BAAoBzC,KAAK4D,OAAOxC,GAAAA;AAChC;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKjB,oBAAoBiB,GAAGtC,GAAAA,CAAAA;AACjD;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKjB,oBAAoBiB,GAAGtC,GAAAA,CAAAA;AACjD;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKjB,oBAAoBiB,GAAGtC,GAAAA,CAAAA;AACjD;IACJ,KAAK;AACDqB,0BAAoBzC,KAAKiB,OAAOG,GAAAA;AAChC;IACJ,KAAK;AACDpB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAKmC,oBAAoBnC,EAAEN,MAAMoB,GAAAA,CAAAA;AACrD;EACR;AACJ;AA/BSqB;AA2CF,SAASoB,2BAA2BC,WAA+BC,UAAgC;AACtG,QAAMC,QAAQ,oBAAI1E,IAAAA;AAClB,QAAM2E,QAA4B;OAAIH;;AACtC,SAAOG,MAAMvD,SAAS,GAAG;AACrB,UAAMgD,IAAIO,MAAMC,IAAG;AACnB,UAAMC,OAAO,oBAAI7E,IAAAA;AACjB8E,oBAAgBV,GAAGS,IAAAA;AACnB,eAAWE,OAAOF,MAAM;AACpB,UAAIH,MAAMpE,IAAIyE,GAAAA,EAAM;AACpBL,YAAMnE,IAAIwE,GAAAA;AACV,YAAMhD,IAAI0C,SAAShE,IAAIsE,GAAAA;AACvB,UAAI,CAAChD,EAAG;AACR,UAAIA,EAAErB,KAAMiE,OAAMvE,KAAK2B,EAAErB,IAAI;AAC7B,iBAAWM,KAAKe,EAAER,OAAQoD,OAAMvE,KAAKY,EAAEN,IAAI;AAC3C,UAAIqB,EAAEV,OAAO;AACT,mBAAWC,QAAQS,EAAEV,OAAO;AACxB,cAAIqD,MAAMpE,IAAIgB,IAAAA,EAAO;AACrBoD,gBAAMnE,IAAIe,IAAAA;AACV,gBAAM0D,KAAKP,SAAShE,IAAIa,IAAAA;AACxB,cAAI,CAAC0D,GAAI;AACT,cAAIA,GAAGtE,KAAMiE,OAAMvE,KAAK4E,GAAGtE,IAAI;AAC/B,qBAAWM,KAAKgE,GAAGzD,OAAQoD,OAAMvE,KAAKY,EAAEN,IAAI;QAChD;MACJ;IACJ;EACJ;AACA,SAAOgE;AACX;AA3BgBH;AA6BT,SAASO,gBAAgBpE,MAAwBoB,KAAgB;AACpE,UAAQpB,KAAKc,MAAI;IACb,KAAK;AACDM,UAAIvB,IAAIG,KAAKR,IAAI;AACjB;IACJ,KAAK;AACD4E,sBAAgBpE,KAAKkD,MAAM9B,GAAAA;AAC3B;IACJ,KAAK;AACDpB,WAAKyD,MAAMN,QAAQO,CAAAA,MAAKU,gBAAgBV,GAAGtC,GAAAA,CAAAA;AAC3C;IACJ,KAAK;AACDgD,sBAAgBpE,KAAK2D,KAAKvC,GAAAA;AAC1BgD,sBAAgBpE,KAAK4D,OAAOxC,GAAAA;AAC5B;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKU,gBAAgBV,GAAGtC,GAAAA,CAAAA;AAC7C;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKU,gBAAgBV,GAAGtC,GAAAA,CAAAA;AAC7C;IACJ,KAAK;AACDpB,WAAKgB,QAAQmC,QAAQO,CAAAA,MAAKU,gBAAgBV,GAAGtC,GAAAA,CAAAA;AAC7C;IACJ,KAAK;AACDgD,sBAAgBpE,KAAKiB,OAAOG,GAAAA;AAC5B;IACJ,KAAK;AACDpB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAK8D,gBAAgB9D,EAAEN,MAAMoB,GAAAA,CAAAA;AACjD;EACR;AACJ;AA/BgBgD;AAwCT,SAASG,uBAAuBpD,QAAqBqD,0BAAuC,oBAAIlF,IAAAA,GAAK;AACxG,QAAMmF,SAAS,oBAAInF,IAAAA;AAGnB,aAAWQ,SAASqB,QAAQ;AACxB,QAAIrB,MAAMe,OAAO6D,KAAKpE,CAAAA,MAAKA,EAAEqE,eAAe,QAAA,GAAW;AACnDF,aAAO5E,IAAIC,MAAMN,IAAI;IACzB;EACJ;AAGA,MAAIoF,UAAU;AACd,SAAOA,SAAS;AACZA,cAAU;AACV,eAAW9E,SAASqB,QAAQ;AACxB,UAAIsD,OAAO7E,IAAIE,MAAMN,IAAI,EAAG;AAC5B,YAAM2E,OAAO,oBAAI7E,IAAAA;AACjB,iBAAWuF,SAAS/E,MAAMe,QAAQ;AAC9BuD,wBAAgBS,MAAM7E,MAAMmE,IAAAA;MAChC;AACA,UAAIrE,MAAMa,MAAO,YAAWmE,KAAKhF,MAAMa,MAAOwD,MAAKtE,IAAIiF,CAAAA;AACvD,UAAIhF,MAAME,KAAMoE,iBAAgBtE,MAAME,MAAMmE,IAAAA;AAC5C,iBAAWE,OAAOF,MAAM;AACpB,YAAIM,OAAO7E,IAAIyE,GAAAA,KAAQG,wBAAwB5E,IAAIyE,GAAAA,GAAM;AACrDI,iBAAO5E,IAAIC,MAAMN,IAAI;AACrBoF,oBAAU;AACV;QACJ;MACJ;IACJ;EACJ;AAEA,SAAOH;AACX;AAjCgBF;AAyCT,SAASQ,wBAAwB5D,QAAqB6D,2BAAwC,oBAAI1F,IAAAA,GAAK;AAC1G,QAAMmF,SAAS,oBAAInF,IAAAA;AAGnB,aAAWQ,SAASqB,QAAQ;AACxB,QAAIrB,MAAMmF,cAAcnF,MAAMmF,eAAe,SAAS;AAClDR,aAAO5E,IAAIC,MAAMN,IAAI;IACzB;EACJ;AAGA,MAAIoF,UAAU;AACd,SAAOA,SAAS;AACZA,cAAU;AACV,eAAW9E,SAASqB,QAAQ;AACxB,UAAIsD,OAAO7E,IAAIE,MAAMN,IAAI,EAAG;AAC5B,YAAM2E,OAAO,oBAAI7E,IAAAA;AACjB,iBAAWuF,SAAS/E,MAAMe,QAAQ;AAC9BuD,wBAAgBS,MAAM7E,MAAMmE,IAAAA;MAChC;AACA,UAAIrE,MAAMa,MAAO,YAAWmE,KAAKhF,MAAMa,MAAOwD,MAAKtE,IAAIiF,CAAAA;AACvD,UAAIhF,MAAME,KAAMoE,iBAAgBtE,MAAME,MAAMmE,IAAAA;AAC5C,iBAAWE,OAAOF,MAAM;AACpB,YAAIM,OAAO7E,IAAIyE,GAAAA,KAAQW,yBAAyBpF,IAAIyE,GAAAA,GAAM;AACtDI,iBAAO5E,IAAIC,MAAMN,IAAI;AACrBoF,oBAAU;AACV;QACJ;MACJ;IACJ;EACJ;AAEA,SAAOH;AACX;AAjCgBM;AAmCT,SAASG,oBAAoB3D,MAAsB;AACtD,QAAM4D,aAAa,IAAI7F,IAAIiC,KAAKJ,OAAOiE,IAAI/D,CAAAA,MAAKA,EAAE7B,IAAI,CAAA;AACtD,QAAM2E,OAAO,oBAAI7E,IAAAA;AAEjB,aAAWQ,SAASyB,KAAKJ,QAAQ;AAC7B,QAAIrB,MAAMa,OAAO;AAAA,iBAAWmE,KAAKhF,MAAMa,MAAO,KAAI,CAACwE,WAAWvF,IAAIkF,CAAAA,EAAIX,MAAKtE,IAAIiF,CAAAA;IAAE;AACjF,QAAIhF,MAAME,KAAMoE,iBAAgBtE,MAAME,MAAMmE,IAAAA;AAC5C,eAAWU,SAAS/E,MAAMe,QAAQ;AAC9BuD,sBAAgBS,MAAM7E,MAAMmE,IAAAA;IAChC;EACJ;AAEA,aAAW3E,QAAQ2F,WAAYhB,MAAKkB,OAAO7F,IAAAA;AAC3C,SAAO;OAAI2E;IAAMlB,KAAI;AACzB;AAdgBiC;AAiBT,SAASI,0BAA0B/D,MAAwBE,kBAA6B;AAC3F,QAAM0D,aAAa,IAAI7F,IAAIiC,KAAKJ,OAAOiE,IAAI/D,CAAAA,MAAKA,EAAE7B,IAAI,CAAA;AACtD,QAAM2E,OAAO,oBAAI7E,IAAAA;AAEjB,aAAWQ,SAASyB,KAAKJ,QAAQ;AAC7B,QAAI,CAACM,iBAAiB7B,IAAIE,MAAMN,IAAI,EAAG;AACvC,QAAIM,MAAME,MAAM;AACZuF,qCAA+BzF,MAAME,MAAMmE,MAAM1C,gBAAAA;AACjD;IACJ;AACA,QAAI3B,MAAMa,OAAO;AACb,iBAAWC,QAAQd,MAAMa,OAAO;AAC5B,YAAIc,iBAAiB7B,IAAIgB,IAAAA,KAAS,CAACuE,WAAWvF,IAAIgB,IAAAA,EAAOuD,MAAKtE,IAAI,GAAGe,IAAAA,QAAY;MACrF;IACJ;AACA,eAAWiE,SAAS/E,MAAMe,QAAQ;AAC9B0E,qCAA+BV,MAAM7E,MAAMmE,MAAM1C,gBAAAA;IACrD;EACJ;AAEA,aAAWjC,QAAQ2F,YAAY;AAC3BhB,SAAKkB,OAAO,GAAG7F,IAAAA,QAAY;EAC/B;AAEA,SAAO;OAAI2E;IAAMlB,KAAI;AACzB;AAzBgBqC;AA2BhB,SAASC,+BAA+BvF,MAAwBoB,KAAkBK,kBAA6B;AAC3G,UAAQzB,KAAKc,MAAI;IACb,KAAK;AACD,UAAIW,iBAAiB7B,IAAII,KAAKR,IAAI,EAAG4B,KAAIvB,IAAI,GAAGG,KAAKR,IAAI,QAAQ;AACjE;IACJ,KAAK;AACD+F,qCAA+BvF,KAAKkD,MAAM9B,KAAKK,gBAAAA;AAC/C;IACJ,KAAK;AACDzB,WAAKyD,MAAMN,QAAQqC,CAAAA,MAAKD,+BAA+BC,GAAGpE,KAAKK,gBAAAA,CAAAA;AAC/D;IACJ,KAAK;AACD8D,qCAA+BvF,KAAK2D,KAAKvC,KAAKK,gBAAAA;AAC9C8D,qCAA+BvF,KAAK4D,OAAOxC,KAAKK,gBAAAA;AAChD;IACJ,KAAK;AACDzB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKkE,+BAA+BlE,GAAGD,KAAKK,gBAAAA,CAAAA;AACjE;IACJ,KAAK;AACDzB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKkE,+BAA+BlE,GAAGD,KAAKK,gBAAAA,CAAAA;AACjE;IACJ,KAAK;AACDzB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKkE,+BAA+BlE,GAAGD,KAAKK,gBAAAA,CAAAA;AACjE;IACJ,KAAK;AACD8D,qCAA+BvF,KAAKiB,OAAOG,KAAKK,gBAAAA;AAChD;IACJ,KAAK;AACDzB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAKiF,+BAA+BjF,EAAEN,MAAMoB,KAAKK,gBAAAA,CAAAA;AACrE;EACR;AACJ;AA/BS8D;AAkCF,SAASE,yBAAyBlE,MAAwBC,iBAA4B;AACzF,QAAM2D,aAAa,IAAI7F,IAAIiC,KAAKJ,OAAOiE,IAAI/D,CAAAA,MAAKA,EAAE7B,IAAI,CAAA;AACtD,QAAM2E,OAAO,oBAAI7E,IAAAA;AAEjB,aAAWQ,SAASyB,KAAKJ,QAAQ;AAC7B,QAAI,CAACK,gBAAgB5B,IAAIE,MAAMN,IAAI,EAAG;AACtC,QAAIM,MAAME,MAAM;AACZ0F,oCAA8B5F,MAAME,MAAMmE,MAAM3C,eAAAA;AAChD;IACJ;AACA,QAAI1B,MAAMa,OAAO;AACb,iBAAWC,QAAQd,MAAMa,OAAO;AAC5B,YAAIa,gBAAgB5B,IAAIgB,IAAAA,KAAS,CAACuE,WAAWvF,IAAIgB,IAAAA,EAAOuD,MAAKtE,IAAI,GAAGe,IAAAA,OAAW;MACnF;IACJ;AACA,UAAM+E,cAAc7F,MAAMe,OAAOmB,OAAO1B,CAAAA,MAAKA,EAAEqE,eAAe,UAAA;AAC9D,eAAWE,SAASc,aAAa;AAC7BD,oCAA8Bb,MAAM7E,MAAMmE,MAAM3C,eAAAA;IACpD;EACJ;AAEA,aAAWhC,QAAQ2F,YAAY;AAC3BhB,SAAKkB,OAAO,GAAG7F,IAAAA,OAAW;EAC9B;AAEA,SAAO;OAAI2E;IAAMlB,KAAI;AACzB;AA1BgBwC;AA4BhB,SAASC,8BAA8B1F,MAAwBoB,KAAkBI,iBAA4B;AACzG,UAAQxB,KAAKc,MAAI;IACb,KAAK;AACD,UAAIU,gBAAgB5B,IAAII,KAAKR,IAAI,EAAG4B,KAAIvB,IAAI,GAAGG,KAAKR,IAAI,OAAO;AAC/D;IACJ,KAAK;AACDkG,oCAA8B1F,KAAKkD,MAAM9B,KAAKI,eAAAA;AAC9C;IACJ,KAAK;AACDxB,WAAKyD,MAAMN,QAAQqC,CAAAA,MAAKE,8BAA8BF,GAAGpE,KAAKI,eAAAA,CAAAA;AAC9D;IACJ,KAAK;AACDkE,oCAA8B1F,KAAK2D,KAAKvC,KAAKI,eAAAA;AAC7CkE,oCAA8B1F,KAAK4D,OAAOxC,KAAKI,eAAAA;AAC/C;IACJ,KAAK;AACDxB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKqE,8BAA8BrE,GAAGD,KAAKI,eAAAA,CAAAA;AAChE;IACJ,KAAK;AACDxB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKqE,8BAA8BrE,GAAGD,KAAKI,eAAAA,CAAAA;AAChE;IACJ,KAAK;AACDxB,WAAKgB,QAAQmC,QAAQ9B,CAAAA,MAAKqE,8BAA8BrE,GAAGD,KAAKI,eAAAA,CAAAA;AAChE;IACJ,KAAK;AACDkE,oCAA8B1F,KAAKiB,OAAOG,KAAKI,eAAAA;AAC/C;IACJ,KAAK;AACDxB,WAAKa,OAAOsC,QAAQ7C,CAAAA,MAAKoF,8BAA8BpF,EAAEN,MAAMoB,KAAKI,eAAAA,CAAAA;AACpE;EACR;AACJ;AA/BSkE;AAqCF,SAASE,eAAezE,QAAmB;AAC9C,QAAMgE,aAAa,IAAI7F,IAAI6B,OAAOiE,IAAI/D,CAAAA,MAAKA,EAAE7B,IAAI,CAAA;AACjD,QAAMuE,WAAW,IAAI1D,IAAIc,OAAOiE,IAAI/D,CAAAA,MAAK;IAACA,EAAE7B;IAAM6B;GAAE,CAAA;AAEpD,QAAMwE,OAAO,oBAAIxF,IAAAA;AACjB,aAAWP,SAASqB,QAAQ;AACxB,UAAMgD,OAAO,oBAAI7E,IAAAA;AACjB,QAAIQ,MAAMa,OAAO;AAAA,iBAAWmE,KAAKhF,MAAMa,MAAO,KAAIwE,WAAWvF,IAAIkF,CAAAA,EAAIX,MAAKtE,IAAIiF,CAAAA;IAAE;AAChF,QAAIhF,MAAME,KAAMoE,iBAAgBtE,MAAME,MAAMmE,IAAAA;AAC5C,eAAWU,SAAS/E,MAAMe,QAAQ;AAC9BuD,sBAAgBS,MAAM7E,MAAMmE,IAAAA;IAChC;AACA,UAAM2B,YAAY,oBAAIxG,IAAAA;AACtB,eAAWyG,KAAK5B,MAAM;AAClB,UAAIgB,WAAWvF,IAAImG,CAAAA,KAAMA,MAAMjG,MAAMN,KAAMsG,WAAUjG,IAAIkG,CAAAA;IAC7D;AACAF,SAAKpF,IAAIX,MAAMN,MAAMsG,SAAAA;EACzB;AAEA,QAAME,YAAY,oBAAI3F,IAAAA;AACtB,aAAW,CAACb,MAAMyG,CAAAA,KAAMJ,MAAM;AAC1BG,cAAUvF,IAAIjB,MAAM,IAAIF,IAAI2G,CAAAA,CAAAA;EAChC;AAEA,QAAMhC,QAAkB,CAAA;AACxB,aAAWzE,QAAQ2F,YAAY;AAC3B,QAAIa,UAAUjG,IAAIP,IAAAA,EAAO0G,SAAS,EAAGjC,OAAMvE,KAAKF,IAAAA;EACpD;AAEA,QAAM2G,SAAsB,CAAA;AAC5B,SAAOlC,MAAMvD,SAAS,GAAG;AACrB,UAAMlB,OAAOyE,MAAMmC,MAAK;AACxBD,WAAOzG,KAAKqE,SAAShE,IAAIP,IAAAA,CAAAA;AACzB,eAAW,CAAC6G,OAAOC,GAAAA,KAAQN,WAAW;AAClC,UAAIM,IAAIjB,OAAO7F,IAAAA,KAAS8G,IAAIJ,SAAS,GAAG;AACpCjC,cAAMvE,KAAK2G,KAAAA;MACf;IACJ;EACJ;AAEA,aAAWvG,SAASqB,QAAQ;AACxB,QAAI,CAACgF,OAAO1G,SAASK,KAAAA,EAAQqG,QAAOzG,KAAKI,KAAAA;EAC7C;AAEA,SAAOqG;AACX;AA7CgBP;AAgDT,SAASW,gBAAgB/G,MAAY;AACxC,SAAOA,KAAKgH,QAAQ,sBAAsB,OAAA,EAASC,YAAW;AAClE;AAFgBF;","names":["SCALAR_NAMES","Set","SECURITY_NONE","resolveModifiers","route","op","raw","modifiers","filter","m","resolveSecurity","root","security","undefined","resolveEffectiveFields","target","modelIndex","unresolved","visited","Set","recordUnresolved","name","includes","push","fromName","has","add","model","get","type","fromType","collectModelFields","merged","index","Map","f","existing","undefined","set","length","bases","base","fields","kind","member","members","inner","buildModelIndex","models","out","m","collectPublicTypeNames","root","modelsWithInput","modelsWithOutput","collectTypes","types","route","routes","publicOps","operations","filter","op","resolveModifiers","collectParamSourceRefs","params","collectParamSourceInputRefs","request","body","bodies","collectTypeNodeRefs","bodyType","collectInputTypeNodeRefs","resp","responses","collectOutputTypeNodeRefs","query","headers","sort","item","forEach","source","param","nodes","node","test","items","t","key","value","collectTransitiveModelRefs","seedTypes","modelMap","found","queue","pop","refs","collectTypeRefs","ref","bm","computeModelsWithInput","externalModelsWithInput","result","some","visibility","changed","field","b","computeModelsWithOutput","externalModelsWithOutput","outputCase","collectExternalRefs","localNames","map","delete","collectExternalOutputRefs","collectOutputTypeRefsForExport","i","collectExternalInputRefs","collectInputTypeRefsForExport","writeFields","topoSortModels","deps","localDeps","r","remaining","d","size","sorted","shift","other","rem","pascalToDotCase","replace","toLowerCase"]}
interface SourceLocation {
file: string;
line: number;
}
declare const SCALAR_NAMES: ReadonlySet<string>;
type ContractTypeNode = ScalarTypeNode | ArrayTypeNode | TupleTypeNode | RecordTypeNode | EnumTypeNode | LiteralTypeNode | UnionTypeNode | DiscriminatedUnionTypeNode | IntersectionTypeNode | ModelRefTypeNode | InlineObjectTypeNode | LazyTypeNode;
interface ScalarTypeNode {
kind: 'scalar';
name: 'string' | 'number' | 'int' | 'bigint' | 'boolean' | 'date' | 'time' | 'datetime' | 'duration' | 'interval' | 'email' | 'url' | 'uuid' | 'unknown' | 'null' | 'object' | 'binary' | 'json';
min?: number | bigint | string;
max?: number | bigint | string;
len?: number;
regex?: string;
format?: string;
}
interface ArrayTypeNode {
kind: 'array';
item: ContractTypeNode;
min?: number;
max?: number;
}
interface TupleTypeNode {
kind: 'tuple';
items: ContractTypeNode[];
}
interface RecordTypeNode {
kind: 'record';
key: ContractTypeNode;
value: ContractTypeNode;
}
interface EnumTypeNode {
kind: 'enum';
values: string[];
}
interface LiteralTypeNode {
kind: 'literal';
value: string | number | boolean;
}
interface UnionTypeNode {
kind: 'union';
members: ContractTypeNode[];
}
interface DiscriminatedUnionTypeNode {
kind: 'discriminatedUnion';
discriminator: string;
members: ContractTypeNode[];
}
interface ModelRefTypeNode {
kind: 'ref';
name: string;
lazy?: boolean;
}
interface InlineObjectTypeNode {
kind: 'inlineObject';
fields: FieldNode[];
mode?: ObjectMode;
}
interface IntersectionTypeNode {
kind: 'intersection';
members: ContractTypeNode[];
}
interface LazyTypeNode {
kind: 'lazy';
inner: ContractTypeNode;
}
interface FieldNode {
name: string;
optional: boolean;
nullable: boolean;
visibility: 'readonly' | 'writeonly' | 'normal';
type: ContractTypeNode;
default?: string | number | boolean;
deprecated?: boolean;
/** Set when the field is declared with the `override` modifier — used by inheritance validation
* to confirm the field is intentionally redeclaring a conflicting base field. */
override?: boolean;
description?: string;
loc: SourceLocation;
}
interface ModelNode {
kind: 'model';
name: string;
/** Names of base contracts this model extends, in left-to-right declaration order.
* `contract C: A & B & { ... }` produces `bases: ['A', 'B']`. Empty/undefined for non-inherited models. */
bases?: string[];
fields: FieldNode[];
type?: ContractTypeNode;
mode?: ObjectMode;
inputCase?: 'camel' | 'snake' | 'pascal';
outputCase?: 'camel' | 'snake' | 'pascal';
deprecated?: boolean;
description?: string;
loc: SourceLocation;
}
interface ContractRootNode {
kind: 'contractRoot';
meta: Record<string, string>;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record<string, string>;
models: ModelNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array<{
line: number;
text: string;
}>;
}
/** Constrained security declaration. */
interface SecurityFields {
/** Named policy required for this endpoint, or `false` to explicitly bypass policy enforcement. */
policy?: string | false;
/** Inline comment attached to the `policy:` line. */
policyDescription?: string;
loc: SourceLocation;
}
/** Sentinel value for explicitly public endpoints (`security: none`). */
declare const SECURITY_NONE: "none";
type SecurityNone = typeof SECURITY_NONE;
/** Security declaration: explicit public (`none`), or constrained auth fields. */
type SecurityNode = SecurityNone | SecurityFields;
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
/** Controls how Zod handles unknown keys on an object schema. */
type ObjectMode = 'strict' | 'strip' | 'loose';
/** Visibility/lifecycle modifiers on routes and operations.
* `public` is operation-only: overrides inherited route-level modifiers. */
type RouteModifier = 'internal' | 'deprecated' | 'public';
/** JSON-like value tree used for `plugins` entries — strings, numbers, booleans, null, nested objects, and arrays. */
type PluginValue = string | number | boolean | null | PluginValue[] | {
[key: string]: PluginValue;
};
interface OpParamNode {
name: string;
optional: boolean;
nullable: boolean;
type: ContractTypeNode;
default?: string | number | boolean;
description?: string;
loc: SourceLocation;
}
/** Either inline param declarations, a single type reference name, or a ContractTypeNode. */
type ParamSource = {
kind: 'params';
nodes: OpParamNode[];
} | {
kind: 'ref';
name: string;
} | {
kind: 'type';
node: ContractTypeNode;
};
/**
* Recognized request mime types that codegen has dedicated handling for. Other strings are
* still permitted (any RFC 6838-shaped `type/subtype`) and pass through unchanged; codegen
* falls back to a JSON-ish default for `+json` suffixes and a generic body for everything else.
*/
type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
interface OpRequestBodyNode {
contentType: string;
bodyType: ContractTypeNode;
}
interface OpRequestNode {
bodies: OpRequestBodyNode[];
}
interface OpResponseHeaderNode {
/** Header name as written in the .ck source (preserves casing/hyphens, e.g. `preference-applied`, `ETag`). */
name: string;
optional: boolean;
type: ContractTypeNode;
description?: string;
}
interface OpResponseNode {
statusCode: number;
contentType?: string;
bodyType?: ContractTypeNode;
/** Declared response headers for this status code. Undefined = none declared. */
headers?: OpResponseHeaderNode[];
/** Set when the status code body declares `headers: none` — suppresses options-level response header merge for this code. */
headersOptOut?: boolean;
}
interface OpOperationNode {
method: HttpMethod;
name?: string;
service?: string;
sdk?: string;
/** HMAC signature key name for this endpoint (e.g. `WEBHOOK_SECRET`). */
signature?: string;
/** Inline comment attached to the `signature:` line. */
signatureDescription?: string;
request?: OpRequestNode;
responses: OpResponseNode[];
query?: ParamSource;
queryMode?: ObjectMode;
headers?: ParamSource;
headersMode?: ObjectMode;
/** Set when the operation declares `headers: none` — suppresses options-level request header merge for this op. */
requestHeadersOptOut?: boolean;
security?: SecurityNode;
/** Explicit modifiers. undefined = inherit from route; [] or array = override. */
modifiers?: RouteModifier[];
/** Raw plugin values from the grammar, e.g. `{ bruno: { template: "file://request-token.yml" } }`. */
plugins?: Record<string, PluginValue>;
/** Resolved plugin extension values keyed by plugin name. Populated by the CLI resolver — same shape as `plugins`, but every `file://` URL string is replaced with the file's contents. Never set by the parser. */
pluginExtensions?: Record<string, PluginValue>;
description?: string;
loc: SourceLocation;
}
interface OpRouteNode {
path: string;
params?: ParamSource;
paramsMode?: ObjectMode;
operations: OpOperationNode[];
/** Route-level modifiers — cascade to all operations unless overridden. */
modifiers?: RouteModifier[];
/** Route-level security default — cascades to operations that have no explicit security declaration. */
security?: SecurityNode;
description?: string;
loc: SourceLocation;
}
/**
* Resolves the effective modifiers for an operation, applying route-level cascade.
* If the operation specifies any explicit modifiers, those replace (not merge) the route's.
* `public` on an operation acts as an explicit override that clears inherited modifiers;
* it is stripped from the returned array (it is not a codegen modifier itself).
*/
declare function resolveModifiers(route: OpRouteNode, op: OpOperationNode): RouteModifier[];
/**
* Resolves the effective security for an operation, applying cascade from operation → route → file.
* Operation-level security always wins; if absent, the route's security is used; if absent, the file's.
*/
declare function resolveSecurity(route: OpRouteNode, op: OpOperationNode, root?: OpRootNode): SecurityNode | undefined;
interface OpRootNode {
kind: 'opRoot';
meta: Record<string, string>;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record<string, string>;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
routes: OpRouteNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array<{
line: number;
text: string;
}>;
}
interface CkRootNode {
kind: 'ckRoot';
meta: Record<string, string>;
services: Record<string, string>;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
models: ModelNode[];
routes: OpRouteNode[];
file: string;
}
/** Result of `resolveEffectiveFields` — the flattened field set plus any refs that
* couldn't be resolved against the supplied model index. */
interface EffectiveFields {
fields: FieldNode[];
/** Model names that the resolution touched but couldn't find in the index. */
unresolved: string[];
}
/**
* Flattens a type or model name into an effective field list, following all forms of
* composition recognized by the contractkit language:
*
* - `contract Foo: { a: int }` → own fields
* - `contract Foo: A & B & { c: int }` → bases (`A`, `B`) contribute, own fields appended
* - `Foo: A & B` (no `{ ... }`) → type alias to intersection; both members contribute
* - Alias chains (`Foo: SomeOther`), nested intersections, inline objects, and `lazy` wrappers
* - Multi-base inheritance with diamond dedup (later declarations override earlier)
* - Cycle protection (`A: B`, `B: A` → resolved once, no infinite loop)
*
* Shapes that can't contribute named fields (scalars, unions, enums, arrays, records,
* tuples) yield an empty field list — they're meant to be rendered as their own thing,
* not flattened. Unresolved refs are captured for the caller to surface as a diagnostic.
*/
declare function resolveEffectiveFields(target: string | ContractTypeNode, modelIndex: ReadonlyMap<string, ModelNode>): EffectiveFields;
/** Builds a lookup map suitable for {@link resolveEffectiveFields}. */
declare function buildModelIndex(models: readonly ModelNode[]): Map<string, ModelNode>;
/**
* Returns the set of type names directly referenced by public (non-internal)
* operations in the root. Does not include transitive dependencies — callers
* should expand these through the contract model graph if needed.
*/
declare function collectPublicTypeNames(root: OpRootNode, modelsWithInput?: Set<string>, modelsWithOutput?: Set<string>): Set<string>;
/**
* Walk the model graph from a set of seed types and return every transitively
* referenced model name. Bases are followed; aliased model `type` and field types
* are queued. Useful for cache fingerprinting — gives the slice of the model
* universe a particular op or contract root depends on.
*
* Models referenced by name but not present in `modelMap` are still included in
* the result (so a fingerprint that mentions them will detect when they appear
* later), but no further traversal happens through them.
*/
declare function collectTransitiveModelRefs(seedTypes: ContractTypeNode[], modelMap: Map<string, ModelNode>): Set<string>;
declare function collectTypeRefs(type: ContractTypeNode, out: Set<string>): void;
/**
* Compute which models need Input variants, including transitive dependencies.
* A model needs an Input variant if it has visibility-modified fields, OR if
* any of its field types (recursively) reference a model that has an Input variant.
*/
declare function computeModelsWithInput(models: ModelNode[], externalModelsWithInput?: Set<string>): Set<string>;
/**
* Compute which models need Output variants (post-transform wire shape),
* including transitive dependencies. A model needs an Output variant if it
* has `format(output=...)` set to a non-camel case, OR if any of its field
* types (recursively) reference a model that has an Output variant.
*/
declare function computeModelsWithOutput(models: ModelNode[], externalModelsWithOutput?: Set<string>): Set<string>;
declare function collectExternalRefs(root: ContractRootNode): string[];
/** Collect external Output variant refs needed for Output schema fields. */
declare function collectExternalOutputRefs(root: ContractRootNode, modelsWithOutput: Set<string>): string[];
/** Collect external Input variant refs needed for Input schema fields. */
declare function collectExternalInputRefs(root: ContractRootNode, modelsWithInput: Set<string>): string[];
/**
* Topologically sort models so dependencies are emitted before dependents.
* Falls back to source order for cycles.
*/
declare function topoSortModels(models: ModelNode[]): ModelNode[];
/** Convert PascalCase to dot-separated lowercase: CounterpartyAccount → counterparty.account */
declare function pascalToDotCase(name: string): string;
export { type ArrayTypeNode as A, collectPublicTypeNames as B, type ContractTypeNode as C, type DiscriminatedUnionTypeNode as D, type EffectiveFields as E, type FieldNode as F, collectTransitiveModelRefs as G, type HttpMethod as H, type InlineObjectTypeNode as I, collectTypeRefs as J, type KnownRequestContentType as K, type LazyTypeNode as L, type ModelNode as M, computeModelsWithInput as N, type OpRootNode as O, type ParamSource as P, computeModelsWithOutput as Q, type RecordTypeNode as R, SCALAR_NAMES as S, type TupleTypeNode as T, type UnionTypeNode as U, pascalToDotCase as V, resolveEffectiveFields as W, resolveModifiers as X, resolveSecurity as Y, topoSortModels as Z, type CkRootNode as a, type ContractRootNode as b, type PluginValue as c, type EnumTypeNode as d, type IntersectionTypeNode as e, type LiteralTypeNode as f, type ModelRefTypeNode as g, type ObjectMode as h, type OpOperationNode as i, type OpParamNode as j, type OpRequestBodyNode as k, type OpRequestNode as l, type OpResponseHeaderNode as m, type OpResponseNode as n, type OpRouteNode as o, type RouteModifier as p, SECURITY_NONE as q, type ScalarTypeNode as r, type SecurityFields as s, type SecurityNode as t, type SecurityNone as u, type SourceLocation as v, buildModelIndex as w, collectExternalInputRefs as x, collectExternalOutputRefs as y, collectExternalRefs as z };
import {
buildModelIndex,
collectExternalInputRefs,
collectExternalOutputRefs,
collectExternalRefs,
collectPublicTypeNames,
collectTransitiveModelRefs,
collectTypeRefs,
computeModelsWithInput,
computeModelsWithOutput,
pascalToDotCase,
resolveEffectiveFields,
topoSortModels
} from "./chunk-NK5CXYRY.js";
export {
buildModelIndex,
collectExternalInputRefs,
collectExternalOutputRefs,
collectExternalRefs,
collectPublicTypeNames,
collectTransitiveModelRefs,
collectTypeRefs,
computeModelsWithInput,
computeModelsWithOutput,
pascalToDotCase,
resolveEffectiveFields,
topoSortModels
};
//# sourceMappingURL=type-utils.js.map
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
import { describe, expect, it } from 'vitest';
import { buildModelIndex, resolveEffectiveFields } from '../src/index.js';
import { field, inlineObjectType, model, refType, scalarType } from './helpers.js';
describe('resolveEffectiveFields', () => {
it('returns own fields for a simple contract', () => {
const idx = buildModelIndex([
model('Todo', [
field('id', scalarType('int')),
field('name', scalarType('string')),
]),
]);
const result = resolveEffectiveFields('Todo', idx);
expect(result.fields.map(f => f.name)).toEqual(['id', 'name']);
expect(result.unresolved).toEqual([]);
});
it('flattens multi-base inheritance with bases first then own fields', () => {
const idx = buildModelIndex([
model('A', [field('a', scalarType('boolean'))]),
model('B', [field('b', scalarType('int'))]),
model('C',
[field('c', scalarType('string'))],
{ bases: ['A', 'B'] },
),
]);
const result = resolveEffectiveFields('C', idx);
expect(result.fields.map(f => f.name)).toEqual(['a', 'b', 'c']);
expect(result.unresolved).toEqual([]);
});
it('follows type-alias bases through inline objects and intersections', () => {
const idx = buildModelIndex([
// Pagination is an alias to an intersection of two refs
model('BasePagination', [
field('page', scalarType('int')),
field('pageSize', scalarType('int')),
]),
model('SortOptions', [field('sort', scalarType('string'))]),
model('Pagination', [], {
type: { kind: 'intersection', members: [refType('BasePagination'), refType('SortOptions')] },
}),
// BusinessPagination extends the alias
model('BusinessPagination',
[field('extra', scalarType('int'))],
{ bases: ['Pagination'] },
),
]);
const result = resolveEffectiveFields('BusinessPagination', idx);
expect(result.fields.map(f => f.name)).toEqual(['page', 'pageSize', 'sort', 'extra']);
});
it('dedupes diamond inheritance with last-wins semantics', () => {
// Diamond: D extends B & C, both B and C extend A. `a` field comes from A but should
// only appear once.
const aFieldFromA = field('a', scalarType('boolean'));
const aOverrideFromC = field('a', scalarType('boolean'), { description: 'overridden in C' });
const idx = buildModelIndex([
model('A', [aFieldFromA]),
model('B', [field('b', scalarType('int'))], { bases: ['A'] }),
model('C', [aOverrideFromC], { bases: ['A'] }),
model('D', [], { bases: ['B', 'C'] }),
]);
const result = resolveEffectiveFields('D', idx);
// `a`, `b` — `a` once, with C's override winning (last seen)
expect(result.fields.map(f => f.name)).toEqual(['a', 'b']);
const a = result.fields.find(f => f.name === 'a')!;
expect(a.description).toBe('overridden in C');
});
it('captures unresolved refs from missing bases', () => {
const idx = buildModelIndex([
model('BusinessPagination',
[field('extra', scalarType('int'))],
{ bases: ['Pagination'] },
),
]);
const result = resolveEffectiveFields('BusinessPagination', idx);
expect(result.fields.map(f => f.name)).toEqual(['extra']);
expect(result.unresolved).toEqual(['Pagination']);
});
it('flattens an inline intersection passed as a type argument', () => {
const idx = buildModelIndex([
model('A', [field('a', scalarType('boolean'))]),
model('B', [field('b', scalarType('int'))]),
]);
const result = resolveEffectiveFields(
{ kind: 'intersection', members: [refType('A'), refType('B'), inlineObjectType([field('c', scalarType('string'))])] },
idx,
);
expect(result.fields.map(f => f.name)).toEqual(['a', 'b', 'c']);
});
it('survives cycles without infinite recursion', () => {
const idx = buildModelIndex([
model('A', [field('a', scalarType('boolean'))], { bases: ['B'] }),
model('B', [field('b', scalarType('int'))], { bases: ['A'] }),
]);
const result = resolveEffectiveFields('A', idx);
// Both fields appear once; resolution doesn't hang.
const names = result.fields.map(f => f.name).sort();
expect(names).toEqual(['a', 'b']);
});
it('returns empty fields (not null) for shapes that can\'t produce fields', () => {
const idx = buildModelIndex([]);
const result = resolveEffectiveFields(scalarType('string'), idx);
expect(result.fields).toEqual([]);
expect(result.unresolved).toEqual([]);
});
it('handles bases that are type aliases to a single ref', () => {
// Sometimes a contract aliases another contract by name.
const idx = buildModelIndex([
model('Real', [field('x', scalarType('int'))]),
model('Alias', [], { type: refType('Real') }),
model('Foo', [field('y', scalarType('string'))], { bases: ['Alias'] }),
]);
const result = resolveEffectiveFields('Foo', idx);
expect(result.fields.map(f => f.name)).toEqual(['x', 'y']);
});
});
+14
-14

@@ -1,10 +0,4 @@

> @contractkit/core@0.18.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> eslint --max-warnings=0 && pnpm run build
> @contractkit/core@0.18.0 build /home/runner/work/ContractKit/ContractKit/packages/contractkit
> tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration && cp src/contractkit.ohm dist/
CLI Building entry: src/index.ts
$ eslint --max-warnings=0 && pnpm run build
$ tsup src/index.ts src/type-utils.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration && cp src/contractkit.ohm dist/
CLI Building entry: src/index.ts, src/type-utils.ts
CLI Using tsconfig: tsconfig.json

@@ -14,7 +8,13 @@ CLI tsup v8.5.1

ESM Build start
ESM dist/index.js 89.09 KB
ESM dist/index.js.map 211.91 KB
ESM ⚡️ Build success in 82ms
ESM dist/index.js 74.01 KB
ESM dist/chunk-NK5CXYRY.js 18.22 KB
ESM dist/type-utils.js 667.00 B
ESM dist/chunk-NK5CXYRY.js.map 49.77 KB
ESM dist/type-utils.js.map 71.00 B
ESM dist/index.js.map 168.24 KB
ESM ⚡️ Build success in 74ms
DTS Build start
DTS ⚡️ Build success in 1157ms
DTS dist/index.d.ts 32.75 KB
DTS ⚡️ Build success in 1438ms
DTS dist/index.d.ts 20.58 KB
DTS dist/type-utils.d.ts 391.00 B
DTS dist/type-utils-D-0zDzVa.d.ts 15.28 KB

@@ -0,21 +1,19 @@

$ vitest run --coverage
> @contractkit/core@0.18.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> vitest run --coverage
 RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/packages/contractkit
Coverage enabled with v8
âś“ tests/incremental.test.ts (18 tests) 17ms
âś“ tests/apply-options-defaults.test.ts (12 tests) 105ms
âś“ tests/validate-inheritance.test.ts (16 tests) 66ms
âś“ tests/parser-ck.test.ts (170 tests) 565ms
âś“ tests/incremental.test.ts (18 tests) 12ms
âś“ tests/apply-options-defaults.test.ts (12 tests) 108ms
âś“ tests/validate-inheritance.test.ts (16 tests) 65ms
âś“ tests/parser-ck.test.ts (170 tests) 537ms
âś“ tests/diagnostics.test.ts (10 tests) 11ms
âś“ tests/apply-variable-substitution.test.ts (10 tests) 40ms
âś“ tests/validate-discriminated.test.ts (5 tests) 26ms
âś“ tests/resolve-effective-fields.test.ts (9 tests) 11ms
âś“ tests/apply-variable-substitution.test.ts (10 tests) 48ms
âś“ tests/validate-discriminated.test.ts (5 tests) 25ms
 Test Files  7 passed (7)
 Tests  241 passed (241)
 Start at  17:08:42
 Duration  2.03s (transform 502ms, setup 0ms, import 2.65s, tests 830ms, environment 1ms)
 Test Files  8 passed (8)
 Tests  250 passed (250)
 Start at  11:53:17
 Duration  2.25s (transform 601ms, setup 0ms, import 3.10s, tests 816ms, environment 1ms)

@@ -26,15 +24,23 @@  % Coverage report from v8

-------------------|---------|----------|---------|---------|-------------------
All files | 84.73 | 73.3 | 89.44 | 87.15 |
...ns-defaults.ts | 98 | 95.34 | 100 | 100 | 67,93
...ubstitution.ts | 97.56 | 94.87 | 100 | 100 | 26,55
ast.ts | 90 | 57.14 | 100 | 85.71 | 312
decompose.ts | 100 | 100 | 100 | 100 |
diagnostics.ts | 100 | 100 | 100 | 100 |
grammar.ts | 75 | 100 | 100 | 75 | 23-24
incremental.ts | 97.1 | 93.47 | 90.9 | 98.21 | 198
parser.ts | 100 | 66.66 | 100 | 100 | 25-26
semantics.ts | 92.68 | 79.37 | 93.51 | 95.67 | ...1201,1221-1225
type-builders.ts | 87.38 | 68.21 | 100 | 87.91 | ...40,144-147,197
...inheritance.ts | 68.45 | 58.51 | 71.42 | 70.68 | ...,88-90,187-234
validate-refs.ts | 40.32 | 37.31 | 50 | 42.59 | ...80,185,199-213
All files | 63.74 | 55 | 65.63 | 67 |
src | 64.92 | 56.55 | 69.54 | 68.26 |
...s-defaults.ts | 98 | 95.34 | 100 | 100 | 67,93
...bstitution.ts | 97.56 | 94.87 | 100 | 100 | 26,55
ast.ts | 90 | 57.14 | 100 | 85.71 | 312
content-type.ts | 0 | 0 | 0 | 0 | 16-38
decompose.ts | 100 | 100 | 100 | 100 |
diagnostics.ts | 100 | 100 | 100 | 100 |
grammar.ts | 75 | 100 | 100 | 75 | 23-24
incremental.ts | 97.1 | 93.47 | 90.9 | 98.21 | 198
index.ts | 0 | 0 | 0 | 0 |
parser.ts | 100 | 66.66 | 100 | 100 | 25-26
plugin.ts | 0 | 0 | 0 | 0 |
semantics.ts | 92.68 | 79.37 | 93.51 | 95.67 | ...1201,1221-1225
type-builders.ts | 87.38 | 68.21 | 100 | 87.91 | ...40,144-147,197
type-utils.ts | 12.96 | 9.23 | 12.28 | 14.19 | 91,115-603
...nheritance.ts | 68.45 | 58.51 | 71.42 | 70.68 | ...,88-90,187-234
...-operation.ts | 0 | 0 | 0 | 0 | 6-84
validate-refs.ts | 40.32 | 37.31 | 50 | 42.59 | ...80,185,199-213
tests | 22.44 | 6.45 | 24 | 25.58 |
helpers.ts | 22.44 | 6.45 | 24 | 25.58 | 39-59,71,97-167
-------------------|---------|----------|---------|---------|-------------------
# @contractkit/core
## 0.19.0
### Minor Changes
- a049895: Add `resolveEffectiveFields` and `buildModelIndex` to `@contractkit/core` for flattening multi-base inheritance into a fully-resolved field list. The explorer UI gains `renderSchemaTree` and `renderCodeSamples` for structured request/response rendering with deterministic curl + JSON examples, a two-column operation layout with a right rail, faker-seeded Try-It pre-fill, and a file-level preview page. The VS Code extension follows the active `.ck` editor with a new live preview panel, gates its tree view on detected ContractKit projects, and supports multiple preview tabs for pinned items.
## 0.18.0

@@ -4,0 +10,0 @@

@@ -26,5 +26,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">84.73% </span>
<span class="strong">63.74% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1055/1245</span>
<span class='fraction'>1118/1754</span>
</div>

@@ -34,5 +34,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">73.3% </span>
<span class="strong">55% </span>
<span class="quiet">Branches</span>
<span class='fraction'>530/723</span>
<span class='fraction'>550/1000</span>
</div>

@@ -42,5 +42,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">89.44% </span>
<span class="strong">65.63% </span>
<span class="quiet">Functions</span>
<span class='fraction'>178/199</span>
<span class='fraction'>191/291</span>
</div>

@@ -50,5 +50,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">87.15% </span>
<span class="strong">67% </span>
<span class="quiet">Lines</span>
<span class='fraction'>923/1059</span>
<span class='fraction'>979/1461</span>
</div>

@@ -68,3 +68,3 @@

</div>
<div class='status-line high'></div>
<div class='status-line medium'></div>
<div class="pad1">

@@ -87,181 +87,31 @@ <table class="coverage-summary">

<tbody><tr>
<td class="file high" data-value="apply-options-defaults.ts"><a href="apply-options-defaults.ts.html">apply-options-defaults.ts</a></td>
<td data-value="98" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div>
<td class="file medium" data-value="src"><a href="src/index.html">src</a></td>
<td data-value="64.92" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 64%"></div><div class="cover-empty" style="width: 36%"></div></div>
</td>
<td data-value="98" class="pct high">98%</td>
<td data-value="50" class="abs high">49/50</td>
<td data-value="95.34" class="pct high">95.34%</td>
<td data-value="43" class="abs high">41/43</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="38" class="abs high">38/38</td>
<td data-value="64.92" class="pct medium">64.92%</td>
<td data-value="1705" class="abs medium">1107/1705</td>
<td data-value="56.55" class="pct medium">56.55%</td>
<td data-value="969" class="abs medium">548/969</td>
<td data-value="69.54" class="pct medium">69.54%</td>
<td data-value="266" class="abs medium">185/266</td>
<td data-value="68.26" class="pct medium">68.26%</td>
<td data-value="1418" class="abs medium">968/1418</td>
</tr>
<tr>
<td class="file high" data-value="apply-variable-substitution.ts"><a href="apply-variable-substitution.ts.html">apply-variable-substitution.ts</a></td>
<td data-value="97.56" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
<td class="file low" data-value="tests"><a href="tests/index.html">tests</a></td>
<td data-value="22.44" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 22%"></div><div class="cover-empty" style="width: 78%"></div></div>
</td>
<td data-value="97.56" class="pct high">97.56%</td>
<td data-value="41" class="abs high">40/41</td>
<td data-value="94.87" class="pct high">94.87%</td>
<td data-value="39" class="abs high">37/39</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="33" class="abs high">33/33</td>
<td data-value="22.44" class="pct low">22.44%</td>
<td data-value="49" class="abs low">11/49</td>
<td data-value="6.45" class="pct low">6.45%</td>
<td data-value="31" class="abs low">2/31</td>
<td data-value="24" class="pct low">24%</td>
<td data-value="25" class="abs low">6/25</td>
<td data-value="25.58" class="pct low">25.58%</td>
<td data-value="43" class="abs low">11/43</td>
</tr>
<tr>
<td class="file high" data-value="ast.ts"><a href="ast.ts.html">ast.ts</a></td>
<td data-value="90" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="10" class="abs high">9/10</td>
<td data-value="57.14" class="pct medium">57.14%</td>
<td data-value="7" class="abs medium">4/7</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
<td data-value="85.71" class="pct high">85.71%</td>
<td data-value="7" class="abs high">6/7</td>
</tr>
<tr>
<td class="file high" data-value="decompose.ts"><a href="decompose.ts.html">decompose.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
</tr>
<tr>
<td class="file high" data-value="diagnostics.ts"><a href="diagnostics.ts.html">diagnostics.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="15" class="abs high">15/15</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="8" class="abs high">8/8</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="12" class="abs high">12/12</td>
</tr>
<tr>
<td class="file medium" data-value="grammar.ts"><a href="grammar.ts.html">grammar.ts</a></td>
<td data-value="75" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 75%"></div><div class="cover-empty" style="width: 25%"></div></div>
</td>
<td data-value="75" class="pct medium">75%</td>
<td data-value="8" class="abs medium">6/8</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="75" class="pct medium">75%</td>
<td data-value="8" class="abs medium">6/8</td>
</tr>
<tr>
<td class="file high" data-value="incremental.ts"><a href="incremental.ts.html">incremental.ts</a></td>
<td data-value="97.1" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.1" class="pct high">97.1%</td>
<td data-value="69" class="abs high">67/69</td>
<td data-value="93.47" class="pct high">93.47%</td>
<td data-value="46" class="abs high">43/46</td>
<td data-value="90.9" class="pct high">90.9%</td>
<td data-value="11" class="abs high">10/11</td>
<td data-value="98.21" class="pct high">98.21%</td>
<td data-value="56" class="abs high">55/56</td>
</tr>
<tr>
<td class="file high" data-value="parser.ts"><a href="parser.ts.html">parser.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
<td data-value="66.66" class="pct medium">66.66%</td>
<td data-value="6" class="abs medium">4/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
</tr>
<tr>
<td class="file high" data-value="semantics.ts"><a href="semantics.ts.html">semantics.ts</a></td>
<td data-value="92.68" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
</td>
<td data-value="92.68" class="pct high">92.68%</td>
<td data-value="656" class="abs high">608/656</td>
<td data-value="79.37" class="pct medium">79.37%</td>
<td data-value="286" class="abs medium">227/286</td>
<td data-value="93.51" class="pct high">93.51%</td>
<td data-value="108" class="abs high">101/108</td>
<td data-value="95.67" class="pct high">95.67%</td>
<td data-value="578" class="abs high">553/578</td>
</tr>
<tr>
<td class="file high" data-value="type-builders.ts"><a href="type-builders.ts.html">type-builders.ts</a></td>
<td data-value="87.38" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
</td>
<td data-value="87.38" class="pct high">87.38%</td>
<td data-value="111" class="abs high">97/111</td>
<td data-value="68.21" class="pct medium">68.21%</td>
<td data-value="129" class="abs medium">88/129</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="20" class="abs high">20/20</td>
<td data-value="87.91" class="pct high">87.91%</td>
<td data-value="91" class="abs high">80/91</td>
</tr>
<tr>
<td class="file medium" data-value="validate-inheritance.ts"><a href="validate-inheritance.ts.html">validate-inheritance.ts</a></td>
<td data-value="68.45" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 68%"></div><div class="cover-empty" style="width: 32%"></div></div>
</td>
<td data-value="68.45" class="pct medium">68.45%</td>
<td data-value="149" class="abs medium">102/149</td>
<td data-value="58.51" class="pct medium">58.51%</td>
<td data-value="94" class="abs medium">55/94</td>
<td data-value="71.42" class="pct medium">71.42%</td>
<td data-value="14" class="abs medium">10/14</td>
<td data-value="70.68" class="pct medium">70.68%</td>
<td data-value="116" class="abs medium">82/116</td>
</tr>
<tr>
<td class="file low" data-value="validate-refs.ts"><a href="validate-refs.ts.html">validate-refs.ts</a></td>
<td data-value="40.32" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 40%"></div><div class="cover-empty" style="width: 60%"></div></div>
</td>
<td data-value="40.32" class="pct low">40.32%</td>
<td data-value="124" class="abs low">50/124</td>
<td data-value="37.31" class="pct low">37.31%</td>
<td data-value="67" class="abs low">25/67</td>
<td data-value="50" class="pct medium">50%</td>
<td data-value="18" class="abs medium">9/18</td>
<td data-value="42.59" class="pct low">42.59%</td>
<td data-value="108" class="abs low">46/108</td>
</tr>
</tbody>

@@ -275,3 +125,3 @@ </table>

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
at 2026-05-15T11:53:20.190Z
</div>

@@ -278,0 +128,0 @@ <script src="prettify.js"></script>

@@ -1,3 +0,28 @@

import type { ContractTypeNode, ContractRootNode, ModelNode, OpRootNode } from './ast.js';
import type { ContractTypeNode, ContractRootNode, FieldNode, ModelNode, OpRootNode } from './ast.js';
/** Result of `resolveEffectiveFields` — the flattened field set plus any refs that
* couldn't be resolved against the supplied model index. */
export interface EffectiveFields {
fields: FieldNode[];
/** Model names that the resolution touched but couldn't find in the index. */
unresolved: string[];
}
/**
* Flattens a type or model name into an effective field list, following all forms of
* composition recognized by the contractkit language:
*
* - `contract Foo: { a: int }` → own fields
* - `contract Foo: A & B & { c: int }` → bases (`A`, `B`) contribute, own fields appended
* - `Foo: A & B` (no `{ ... }`) → type alias to intersection; both members contribute
* - Alias chains (`Foo: SomeOther`), nested intersections, inline objects, and `lazy` wrappers
* - Multi-base inheritance with diamond dedup (later declarations override earlier)
* - Cycle protection (`A: B`, `B: A` → resolved once, no infinite loop)
*
* Shapes that can't contribute named fields (scalars, unions, enums, arrays, records,
* tuples) yield an empty field list — they're meant to be rendered as their own thing,
* not flattened. Unresolved refs are captured for the caller to surface as a diagnostic.
*/
export declare function resolveEffectiveFields(target: string | ContractTypeNode, modelIndex: ReadonlyMap<string, ModelNode>): EffectiveFields;
/** Builds a lookup map suitable for {@link resolveEffectiveFields}. */
export declare function buildModelIndex(models: readonly ModelNode[]): Map<string, ModelNode>;
/**
* Returns the set of type names directly referenced by public (non-internal)

@@ -4,0 +29,0 @@ * operations in the root. Does not include transitive dependencies — callers

@@ -1,1 +0,1 @@

{"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAKvG;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAEnI;AA4ID;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BvH;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA+B9E;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiCzH;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiC3H;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAyBzG;AAmCD,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA0BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6C/D;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
{"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAKlH;4DAC4D;AAC5D,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EACjC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,GAC3C,eAAe,CAkEjB;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAIpF;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAEnI;AA4ID;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BvH;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA+B9E;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiCzH;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiC3H;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAyBzG;AAmCD,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA0BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6C/D;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
{
"name": "@contractkit/core",
"version": "0.18.0",
"version": "0.19.0",
"description": "Core DSL compiler library: grammar-driven parser, codegen, and validation",

@@ -29,3 +29,7 @@ "author": {

"exports": {
".": "./dist/index.js"
".": "./dist/index.js",
"./type-utils": {
"types": "./dist/type-utils.d.ts",
"default": "./dist/type-utils.js"
}
},

@@ -40,3 +44,3 @@ "dependencies": {

"scripts": {
"build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration && cp src/contractkit.ohm dist/",
"build": "tsup src/index.ts src/type-utils.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration && cp src/contractkit.ohm dist/",
"build:ci": "eslint --max-warnings=0 && pnpm run build",

@@ -43,0 +47,0 @@ "lint": "eslint --fix",

@@ -1,4 +0,107 @@

import type { ContractTypeNode, ContractRootNode, ModelNode, OpRootNode, ParamSource } from './ast.js';
import type { ContractTypeNode, ContractRootNode, FieldNode, ModelNode, OpRootNode, ParamSource } from './ast.js';
import { resolveModifiers } from './ast.js';
// ─── Effective-field resolution ────────────────────────────────────────────
/** Result of `resolveEffectiveFields` — the flattened field set plus any refs that
* couldn't be resolved against the supplied model index. */
export interface EffectiveFields {
fields: FieldNode[];
/** Model names that the resolution touched but couldn't find in the index. */
unresolved: string[];
}
/**
* Flattens a type or model name into an effective field list, following all forms of
* composition recognized by the contractkit language:
*
* - `contract Foo: { a: int }` → own fields
* - `contract Foo: A & B & { c: int }` → bases (`A`, `B`) contribute, own fields appended
* - `Foo: A & B` (no `{ ... }`) → type alias to intersection; both members contribute
* - Alias chains (`Foo: SomeOther`), nested intersections, inline objects, and `lazy` wrappers
* - Multi-base inheritance with diamond dedup (later declarations override earlier)
* - Cycle protection (`A: B`, `B: A` → resolved once, no infinite loop)
*
* Shapes that can't contribute named fields (scalars, unions, enums, arrays, records,
* tuples) yield an empty field list — they're meant to be rendered as their own thing,
* not flattened. Unresolved refs are captured for the caller to surface as a diagnostic.
*/
export function resolveEffectiveFields(
target: string | ContractTypeNode,
modelIndex: ReadonlyMap<string, ModelNode>,
): EffectiveFields {
const unresolved: string[] = [];
const visited = new Set<string>();
const recordUnresolved = (name: string): void => {
if (!unresolved.includes(name)) unresolved.push(name);
};
const fromName = (name: string): FieldNode[] => {
if (visited.has(name)) return [];
visited.add(name);
const model = modelIndex.get(name);
if (!model) {
recordUnresolved(name);
return [];
}
if (model.type) return fromType(model.type);
return collectModelFields(model);
};
const collectModelFields = (model: ModelNode): FieldNode[] => {
const merged: FieldNode[] = [];
const index = new Map<string, number>();
const push = (f: FieldNode): void => {
const existing = index.get(f.name);
if (existing !== undefined) merged[existing] = f;
else {
index.set(f.name, merged.length);
merged.push(f);
}
};
if (model.bases) {
for (const base of model.bases) {
for (const f of fromName(base)) push(f);
}
}
for (const f of model.fields) push(f);
return merged;
};
const fromType = (type: ContractTypeNode): FieldNode[] => {
switch (type.kind) {
case 'inlineObject': return type.fields;
case 'ref': return fromName(type.name);
case 'intersection': {
const merged: FieldNode[] = [];
const index = new Map<string, number>();
for (const member of type.members) {
for (const f of fromType(member)) {
const existing = index.get(f.name);
if (existing !== undefined) merged[existing] = f;
else {
index.set(f.name, merged.length);
merged.push(f);
}
}
}
return merged;
}
case 'lazy': return fromType(type.inner);
default: return [];
}
};
const fields = typeof target === 'string' ? fromName(target) : fromType(target);
return { fields, unresolved };
}
/** Builds a lookup map suitable for {@link resolveEffectiveFields}. */
export function buildModelIndex(models: readonly ModelNode[]): Map<string, ModelNode> {
const out = new Map<string, ModelNode>();
for (const m of models) out.set(m.name, m);
return out;
}
// ─── Type collection ──────────────────────────────────────────────────────

@@ -5,0 +108,0 @@

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for apply-options-defaults.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> apply-options-defaults.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">98% </span>
<span class="quiet">Statements</span>
<span class='fraction'>49/50</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">95.34% </span>
<span class="quiet">Branches</span>
<span class='fraction'>41/43</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>9/9</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>38/38</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Normalization pass — merges options-level request/response headers into each operation's AST
* so downstream codegen plugins remain unaware of the options-vs-operation distinction.
*
* Runs after parsing and before validation. Mutates the root in place.
*
* Merge rules:
* - Request headers: applied to every operation. Op-level headers with the same name win.
* If the op declares `headers: none`, the merge is skipped. If the op uses a referenced
* or compound type for headers (rather than inline params), the merge is skipped with a warning.
* - Response headers: applied to every status code on every operation, regardless of body
* presence or status class. Per-status `headers: none` skips the merge for that code.
* Per-status header with same name wins.
*/
import type { CkRootNode, OpOperationNode, OpParamNode, OpResponseHeaderNode, OpResponseNode, OpRootNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
type RootWithGlobals = Pick&lt;CkRootNode, 'file' | 'routes' | 'requestHeaders' | 'responseHeaders'&gt; | Pick&lt;OpRootNode, 'file' | 'routes' | 'requestHeaders' | 'responseHeaders'&gt;;
&nbsp;
export function applyOptionsDefaults(root: RootWithGlobals, diag: DiagnosticCollector): void {
const reqGlobals = root.requestHeaders ?? [];
const resGlobals = root.responseHeaders ?? [];
if (reqGlobals.length === 0 &amp;&amp; resGlobals.length === 0) return;
&nbsp;
for (const route of root.routes) {
const pathParams = new Set([...route.path.matchAll(/\{(\w+)\}/g)].map(m =&gt; m[1]!));
for (const g of reqGlobals) {
if (pathParams.has(g.name)) {
diag.error(root.file, route.loc.line, `Global request header '${g.name}' collides with path parameter on '${route.path}'`);
}
}
&nbsp;
for (const op of route.operations) {
mergeRequestHeaders(op, reqGlobals, root.file, diag);
for (const res of op.responses) mergeResponseHeaders(res, resGlobals);
}
}
}
&nbsp;
function mergeRequestHeaders(op: OpOperationNode, globals: OpResponseHeaderNode[], file: string, diag: DiagnosticCollector): void {
if (globals.length === 0 || op.requestHeadersOptOut) return;
&nbsp;
const src = op.headers;
if (src &amp;&amp; (src.kind === 'ref' || src.kind === 'type')) {
diag.warn(
file,
op.loc.line,
`Operation uses a referenced headers type — global request headers from options are not merged. Inline the headers or use 'headers: none' to silence.`,
);
return;
}
&nbsp;
const existing: OpParamNode[] = src?.kind === 'params' ? src.nodes : [];
const existingNames = new Set(existing.map(p =&gt; p.name));
const additions: OpParamNode[] = [];
const overridden: string[] = [];
&nbsp;
for (const g of globals) {
if (existingNames.has(g.name)) {
overridden.push(g.name);
continue;
}
additions.push(headerToParam(g, op));
}
&nbsp;
if (overridden.length &gt; 0) {
diag.warn(file, op.loc.line, `Operation overrides global request header${overridden.length &gt; 1 ? <span class="branch-0 cbranch-no" title="branch not covered" >'s' : '</span>'} ${overridden.map(n =&gt; `'${n}'`).join(', ')}`);
}
&nbsp;
if (additions.length === 0 &amp;&amp; src) return;
op.headers = { kind: 'params', nodes: [...additions, ...existing] };
}
&nbsp;
function mergeResponseHeaders(res: OpResponseNode, globals: OpResponseHeaderNode[]): void {
if (globals.length === 0 || res.headersOptOut) return;
&nbsp;
const existing = res.headers ?? [];
const existingNames = new Set(existing.map(h =&gt; h.name));
const additions = globals.filter(g =&gt; !existingNames.has(g.name));
if (additions.length === 0 &amp;&amp; res.headers) return;
&nbsp;
res.headers = [...additions, ...existing];
}
&nbsp;
function headerToParam(h: OpResponseHeaderNode, op: OpOperationNode): OpParamNode {
const param: OpParamNode = {
name: h.name,
optional: h.optional,
nullable: false,
type: h.type,
loc: op.loc,
};
<span class="missing-if-branch" title="if path not taken" >I</span>if (h.description) <span class="cstat-no" title="statement not covered" >param.description = h.description;</span>
return param;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for apply-variable-substitution.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> apply-variable-substitution.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.56% </span>
<span class="quiet">Statements</span>
<span class='fraction'>40/41</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">94.87% </span>
<span class="quiet">Branches</span>
<span class='fraction'>37/39</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>33/33</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">349x</span>
<span class="cline-any cline-yes">319x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">270x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Normalization pass — substitutes `{{name}}` references in every string-bearing field
* of the AST with values from `root.meta` (the file's `options { keys }` block) or, when
* absent, from a workspace-wide `fallbackKeys` map (typically merged from each plugin's
* `options.keys` in `contractkit.config.json`).
*
* Runs in the CLI between `parseCk` and `decomposeCk`, after `applyOptionsDefaults`. It
* deliberately does NOT run inside `parseCk` so the prettier plugin sees the un-substituted
* source form and can round-trip the file.
*
* Substitution rules:
* - `{{name}}` → value lookup; warns and emits the literal string `undefined` when missing.
* - `\{{name}}` → literal `{{name}}` (no substitution, no warning).
*
* `root.meta` is itself excluded from the walk — keys are not recursively substituted.
*/
import type { CkRootNode, OpRootNode, SourceLocation } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
const SUBSTITUTION_RE = /\\\{\{(\w+)\}\}|\{\{(\w+)\}\}/g;
&nbsp;
type Root = CkRootNode | OpRootNode;
&nbsp;
export function applyVariableSubstitution(root: Root, diag: DiagnosticCollector, fallbackKeys: Record&lt;string, string&gt; = {}): void {
const file = root.file;
const meta = root.meta ?? <span class="branch-1 cbranch-no" title="branch not covered" >{};</span>
&nbsp;
const lookup = (name: string): string | undefined =&gt; {
if (Object.prototype.hasOwnProperty.call(meta, name)) return meta[name];
if (Object.prototype.hasOwnProperty.call(fallbackKeys, name)) return fallbackKeys[name];
return undefined;
};
&nbsp;
const substitute = (input: string, line: number): string =&gt; {
if (!input.includes('{{')) return input;
return input.replace(SUBSTITUTION_RE, (_match, escapedName: string | undefined, varName: string | undefined) =&gt; {
if (escapedName !== undefined) return `{{${escapedName}}}`;
const value = lookup(varName!);
if (value === undefined) {
diag.warn(file, line, `Unknown variable '{{${varName}}}'`);
return 'undefined';
}
return value;
});
};
&nbsp;
walk(root, substitute, 0, /* isRoot */ true);
}
&nbsp;
function isLoc(value: unknown): value is SourceLocation {
return typeof value === 'object' &amp;&amp; value !== null &amp;&amp; typeof (value as SourceLocation).line === 'number' &amp;&amp; typeof (value as SourceLocation).file === 'string';
}
&nbsp;
function walk(node: unknown, substitute: (s: string, line: number) =&gt; string, currentLine: number, isRoot: boolean): void {
<span class="missing-if-branch" title="if path not taken" >I</span>if (node === null || typeof node !== 'object') <span class="cstat-no" title="statement not covered" >return;</span>
&nbsp;
if (Array.isArray(node)) {
for (const item of node) walk(item, substitute, currentLine, false);
return;
}
&nbsp;
const obj = node as Record&lt;string, unknown&gt;;
&nbsp;
// Promote `loc.line` to the running context so warnings emitted while walking
// this node's descendants can attribute themselves accurately.
const ownLoc = obj['loc'];
const lineHere = isLoc(ownLoc) ? ownLoc.line : currentLine;
&nbsp;
for (const key of Object.keys(obj)) {
// Skip book-keeping fields and the substitution source itself.
if (key === 'loc' || key === 'file') continue;
if (isRoot &amp;&amp; key === 'meta') continue;
&nbsp;
const value = obj[key];
if (typeof value === 'string') {
obj[key] = substitute(value, lineHere);
} else if (value !== null &amp;&amp; typeof value === 'object') {
walk(value, substitute, lineHere, false);
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for ast.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> ast.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Statements</span>
<span class='fraction'>9/10</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">57.14% </span>
<span class="quiet">Branches</span>
<span class='fraction'>4/7</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>3/3</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">85.71% </span>
<span class="quiet">Lines</span>
<span class='fraction'>6/7</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a>
<a name='L239'></a><a href='#L239'>239</a>
<a name='L240'></a><a href='#L240'>240</a>
<a name='L241'></a><a href='#L241'>241</a>
<a name='L242'></a><a href='#L242'>242</a>
<a name='L243'></a><a href='#L243'>243</a>
<a name='L244'></a><a href='#L244'>244</a>
<a name='L245'></a><a href='#L245'>245</a>
<a name='L246'></a><a href='#L246'>246</a>
<a name='L247'></a><a href='#L247'>247</a>
<a name='L248'></a><a href='#L248'>248</a>
<a name='L249'></a><a href='#L249'>249</a>
<a name='L250'></a><a href='#L250'>250</a>
<a name='L251'></a><a href='#L251'>251</a>
<a name='L252'></a><a href='#L252'>252</a>
<a name='L253'></a><a href='#L253'>253</a>
<a name='L254'></a><a href='#L254'>254</a>
<a name='L255'></a><a href='#L255'>255</a>
<a name='L256'></a><a href='#L256'>256</a>
<a name='L257'></a><a href='#L257'>257</a>
<a name='L258'></a><a href='#L258'>258</a>
<a name='L259'></a><a href='#L259'>259</a>
<a name='L260'></a><a href='#L260'>260</a>
<a name='L261'></a><a href='#L261'>261</a>
<a name='L262'></a><a href='#L262'>262</a>
<a name='L263'></a><a href='#L263'>263</a>
<a name='L264'></a><a href='#L264'>264</a>
<a name='L265'></a><a href='#L265'>265</a>
<a name='L266'></a><a href='#L266'>266</a>
<a name='L267'></a><a href='#L267'>267</a>
<a name='L268'></a><a href='#L268'>268</a>
<a name='L269'></a><a href='#L269'>269</a>
<a name='L270'></a><a href='#L270'>270</a>
<a name='L271'></a><a href='#L271'>271</a>
<a name='L272'></a><a href='#L272'>272</a>
<a name='L273'></a><a href='#L273'>273</a>
<a name='L274'></a><a href='#L274'>274</a>
<a name='L275'></a><a href='#L275'>275</a>
<a name='L276'></a><a href='#L276'>276</a>
<a name='L277'></a><a href='#L277'>277</a>
<a name='L278'></a><a href='#L278'>278</a>
<a name='L279'></a><a href='#L279'>279</a>
<a name='L280'></a><a href='#L280'>280</a>
<a name='L281'></a><a href='#L281'>281</a>
<a name='L282'></a><a href='#L282'>282</a>
<a name='L283'></a><a href='#L283'>283</a>
<a name='L284'></a><a href='#L284'>284</a>
<a name='L285'></a><a href='#L285'>285</a>
<a name='L286'></a><a href='#L286'>286</a>
<a name='L287'></a><a href='#L287'>287</a>
<a name='L288'></a><a href='#L288'>288</a>
<a name='L289'></a><a href='#L289'>289</a>
<a name='L290'></a><a href='#L290'>290</a>
<a name='L291'></a><a href='#L291'>291</a>
<a name='L292'></a><a href='#L292'>292</a>
<a name='L293'></a><a href='#L293'>293</a>
<a name='L294'></a><a href='#L294'>294</a>
<a name='L295'></a><a href='#L295'>295</a>
<a name='L296'></a><a href='#L296'>296</a>
<a name='L297'></a><a href='#L297'>297</a>
<a name='L298'></a><a href='#L298'>298</a>
<a name='L299'></a><a href='#L299'>299</a>
<a name='L300'></a><a href='#L300'>300</a>
<a name='L301'></a><a href='#L301'>301</a>
<a name='L302'></a><a href='#L302'>302</a>
<a name='L303'></a><a href='#L303'>303</a>
<a name='L304'></a><a href='#L304'>304</a>
<a name='L305'></a><a href='#L305'>305</a>
<a name='L306'></a><a href='#L306'>306</a>
<a name='L307'></a><a href='#L307'>307</a>
<a name='L308'></a><a href='#L308'>308</a>
<a name='L309'></a><a href='#L309'>309</a>
<a name='L310'></a><a href='#L310'>310</a>
<a name='L311'></a><a href='#L311'>311</a>
<a name='L312'></a><a href='#L312'>312</a>
<a name='L313'></a><a href='#L313'>313</a>
<a name='L314'></a><a href='#L314'>314</a>
<a name='L315'></a><a href='#L315'>315</a>
<a name='L316'></a><a href='#L316'>316</a>
<a name='L317'></a><a href='#L317'>317</a>
<a name='L318'></a><a href='#L318'>318</a>
<a name='L319'></a><a href='#L319'>319</a>
<a name='L320'></a><a href='#L320'>320</a>
<a name='L321'></a><a href='#L321'>321</a>
<a name='L322'></a><a href='#L322'>322</a>
<a name='L323'></a><a href='#L323'>323</a>
<a name='L324'></a><a href='#L324'>324</a>
<a name='L325'></a><a href='#L325'>325</a>
<a name='L326'></a><a href='#L326'>326</a>
<a name='L327'></a><a href='#L327'>327</a>
<a name='L328'></a><a href='#L328'>328</a>
<a name='L329'></a><a href='#L329'>329</a>
<a name='L330'></a><a href='#L330'>330</a>
<a name='L331'></a><a href='#L331'>331</a>
<a name='L332'></a><a href='#L332'>332</a>
<a name='L333'></a><a href='#L333'>333</a>
<a name='L334'></a><a href='#L334'>334</a>
<a name='L335'></a><a href='#L335'>335</a>
<a name='L336'></a><a href='#L336'>336</a>
<a name='L337'></a><a href='#L337'>337</a>
<a name='L338'></a><a href='#L338'>338</a>
<a name='L339'></a><a href='#L339'>339</a>
<a name='L340'></a><a href='#L340'>340</a>
<a name='L341'></a><a href='#L341'>341</a>
<a name='L342'></a><a href='#L342'>342</a>
<a name='L343'></a><a href='#L343'>343</a>
<a name='L344'></a><a href='#L344'>344</a>
<a name='L345'></a><a href='#L345'>345</a>
<a name='L346'></a><a href='#L346'>346</a>
<a name='L347'></a><a href='#L347'>347</a>
<a name='L348'></a><a href='#L348'>348</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// ─── Shared ────────────────────────────────────────────────────────────────
&nbsp;
export interface SourceLocation {
file: string;
line: number;
}
&nbsp;
export const SCALAR_NAMES: ReadonlySet&lt;string&gt; = new Set&lt;ScalarTypeNode['name']&gt;([
'string',
'number',
'int',
'bigint',
'boolean',
'date',
'time',
'datetime',
'duration',
'interval',
'email',
'url',
'uuid',
'unknown',
'null',
'object',
'binary',
'json',
]);
&nbsp;
// ─── Contracts AST (.ck) ──────────────────────────────────────────────────
&nbsp;
export type ContractTypeNode =
| ScalarTypeNode
| ArrayTypeNode
| TupleTypeNode
| RecordTypeNode
| EnumTypeNode
| LiteralTypeNode
| UnionTypeNode
| DiscriminatedUnionTypeNode
| IntersectionTypeNode
| ModelRefTypeNode
| InlineObjectTypeNode
| LazyTypeNode;
&nbsp;
export interface ScalarTypeNode {
kind: 'scalar';
name:
| 'string'
| 'number'
| 'int'
| 'bigint'
| 'boolean'
| 'date'
| 'time'
| 'datetime'
| 'duration'
| 'interval'
| 'email'
| 'url'
| 'uuid'
| 'unknown'
| 'null'
| 'object'
| 'binary'
| 'json';
min?: number | bigint | string;
max?: number | bigint | string;
len?: number;
regex?: string;
format?: string;
}
&nbsp;
export interface ArrayTypeNode {
kind: 'array';
item: ContractTypeNode;
min?: number;
max?: number;
}
&nbsp;
export interface TupleTypeNode {
kind: 'tuple';
items: ContractTypeNode[];
}
&nbsp;
export interface RecordTypeNode {
kind: 'record';
key: ContractTypeNode;
value: ContractTypeNode;
}
&nbsp;
export interface EnumTypeNode {
kind: 'enum';
values: string[];
}
&nbsp;
export interface LiteralTypeNode {
kind: 'literal';
value: string | number | boolean;
}
&nbsp;
export interface UnionTypeNode {
kind: 'union';
members: ContractTypeNode[];
}
&nbsp;
export interface DiscriminatedUnionTypeNode {
kind: 'discriminatedUnion';
discriminator: string;
members: ContractTypeNode[];
}
&nbsp;
export interface ModelRefTypeNode {
kind: 'ref';
name: string;
lazy?: boolean;
}
&nbsp;
export interface InlineObjectTypeNode {
kind: 'inlineObject';
fields: FieldNode[];
mode?: ObjectMode;
}
&nbsp;
export interface IntersectionTypeNode {
kind: 'intersection';
members: ContractTypeNode[];
}
&nbsp;
export interface LazyTypeNode {
kind: 'lazy';
inner: ContractTypeNode;
}
&nbsp;
export interface FieldNode {
name: string;
optional: boolean;
nullable: boolean;
visibility: 'readonly' | 'writeonly' | 'normal';
type: ContractTypeNode;
default?: string | number | boolean;
deprecated?: boolean;
/** Set when the field is declared with the `override` modifier — used by inheritance validation
* to confirm the field is intentionally redeclaring a conflicting base field. */
override?: boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface ModelNode {
kind: 'model';
name: string;
/** Names of base contracts this model extends, in left-to-right declaration order.
* `contract C: A &amp; B &amp; { ... }` produces `bases: ['A', 'B']`. Empty/undefined for non-inherited models. */
bases?: string[];
fields: FieldNode[];
type?: ContractTypeNode; // type alias: Name: typeExpression (fields will be empty)
mode?: ObjectMode; // object validation mode — defaults to 'strict'
inputCase?: 'camel' | 'snake' | 'pascal'; // format(input=) — key casing of incoming data
outputCase?: 'camel' | 'snake' | 'pascal'; // format(output=) — key casing of emitted data
deprecated?: boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface ContractRootNode {
kind: 'contractRoot';
meta: Record&lt;string, string&gt;;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record&lt;string, string&gt;;
models: ModelNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array&lt;{ line: number; text: string }&gt;;
}
&nbsp;
// ─── Operations AST (.op) ──────────────────────────────────────────────────
&nbsp;
/** Constrained security declaration. */
export interface SecurityFields {
/** Named policy required for this endpoint, or `false` to explicitly bypass policy enforcement. */
policy?: string | false;
/** Inline comment attached to the `policy:` line. */
policyDescription?: string;
loc: SourceLocation;
}
&nbsp;
/** Sentinel value for explicitly public endpoints (`security: none`). */
export const SECURITY_NONE = 'none' as const;
export type SecurityNone = typeof SECURITY_NONE;
&nbsp;
/** Security declaration: explicit public (`none`), or constrained auth fields. */
export type SecurityNode = SecurityNone | SecurityFields;
&nbsp;
export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
&nbsp;
/** Controls how Zod handles unknown keys on an object schema. */
export type ObjectMode = 'strict' | 'strip' | 'loose';
&nbsp;
/** Visibility/lifecycle modifiers on routes and operations.
* `public` is operation-only: overrides inherited route-level modifiers. */
export type RouteModifier = 'internal' | 'deprecated' | 'public';
&nbsp;
/** JSON-like value tree used for `plugins` entries — strings, numbers, booleans, null, nested objects, and arrays. */
export type PluginValue = string | number | boolean | null | PluginValue[] | { [key: string]: PluginValue };
&nbsp;
export interface OpParamNode {
name: string;
optional: boolean;
nullable: boolean;
type: ContractTypeNode;
default?: string | number | boolean;
description?: string;
loc: SourceLocation;
}
&nbsp;
/** Either inline param declarations, a single type reference name, or a ContractTypeNode. */
export type ParamSource = { kind: 'params'; nodes: OpParamNode[] } | { kind: 'ref'; name: string } | { kind: 'type'; node: ContractTypeNode };
&nbsp;
/**
* Recognized request mime types that codegen has dedicated handling for. Other strings are
* still permitted (any RFC 6838-shaped `type/subtype`) and pass through unchanged; codegen
* falls back to a JSON-ish default for `+json` suffixes and a generic body for everything else.
*/
export type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
&nbsp;
export interface OpRequestBodyNode {
contentType: string;
bodyType: ContractTypeNode;
}
&nbsp;
export interface OpRequestNode {
bodies: OpRequestBodyNode[];
}
&nbsp;
export interface OpResponseHeaderNode {
/** Header name as written in the .ck source (preserves casing/hyphens, e.g. `preference-applied`, `ETag`). */
name: string;
optional: boolean;
type: ContractTypeNode;
description?: string;
}
&nbsp;
export interface OpResponseNode {
statusCode: number;
contentType?: string;
bodyType?: ContractTypeNode;
/** Declared response headers for this status code. Undefined = none declared. */
headers?: OpResponseHeaderNode[];
/** Set when the status code body declares `headers: none` — suppresses options-level response header merge for this code. */
headersOptOut?: boolean;
}
&nbsp;
export interface OpOperationNode {
method: HttpMethod;
name?: string; // e.g. "Create an Offer" — human-readable name for docs/collections
service?: string; // e.g. "LedgerService.updateCategoryNesting"
sdk?: string; // e.g. "getUser" — explicit SDK method name
/** HMAC signature key name for this endpoint (e.g. `WEBHOOK_SECRET`). */
signature?: string;
/** Inline comment attached to the `signature:` line. */
signatureDescription?: string;
request?: OpRequestNode;
responses: OpResponseNode[];
query?: ParamSource;
queryMode?: ObjectMode;
headers?: ParamSource;
headersMode?: ObjectMode;
/** Set when the operation declares `headers: none` — suppresses options-level request header merge for this op. */
requestHeadersOptOut?: boolean;
security?: SecurityNode; // overrides config default; "none" = explicitly public
/** Explicit modifiers. undefined = inherit from route; [] or array = override. */
modifiers?: RouteModifier[];
/** Raw plugin values from the grammar, e.g. `{ bruno: { template: "file://request-token.yml" } }`. */
plugins?: Record&lt;string, PluginValue&gt;;
/** Resolved plugin extension values keyed by plugin name. Populated by the CLI resolver — same shape as `plugins`, but every `file://` URL string is replaced with the file's contents. Never set by the parser. */
pluginExtensions?: Record&lt;string, PluginValue&gt;;
description?: string;
loc: SourceLocation;
}
&nbsp;
export interface OpRouteNode {
path: string;
params?: ParamSource;
paramsMode?: ObjectMode;
operations: OpOperationNode[];
/** Route-level modifiers — cascade to all operations unless overridden. */
modifiers?: RouteModifier[];
/** Route-level security default — cascades to operations that have no explicit security declaration. */
security?: SecurityNode;
description?: string;
loc: SourceLocation;
}
&nbsp;
/**
* Resolves the effective modifiers for an operation, applying route-level cascade.
* If the operation specifies any explicit modifiers, those replace (not merge) the route's.
* `public` on an operation acts as an explicit override that clears inherited modifiers;
* it is stripped from the returned array (it is not a codegen modifier itself).
*/
export function resolveModifiers(route: OpRouteNode, op: OpOperationNode): RouteModifier[] {
const raw = op.modifiers ?? <span class="branch-1 cbranch-no" title="branch not covered" >route.modifiers </span>?? <span class="branch-2 cbranch-no" title="branch not covered" >[];</span>
return raw.filter(m =&gt; m !== 'public');
}
&nbsp;
/**
* Resolves the effective security for an operation, applying cascade from operation → route → file.
* Operation-level security always wins; if absent, the route's security is used; if absent, the file's.
*/
export function resolveSecurity(route: OpRouteNode, op: OpOperationNode, root?: OpRootNode): SecurityNode | undefined {
if (op.security !== undefined) return op.security;
<span class="missing-if-branch" title="else path not taken" >E</span>if (route.security !== undefined) return route.security;
<span class="cstat-no" title="statement not covered" > return root?.security;</span>
}
&nbsp;
export interface OpRootNode {
kind: 'opRoot';
meta: Record&lt;string, string&gt;;
/** Service name → module path mappings from `options { services { ... } }`. */
services?: Record&lt;string, string&gt;;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
routes: OpRouteNode[];
file: string;
/** Comment lines not attached to any node, sorted by line number. */
orphanComments?: Array&lt;{ line: number; text: string }&gt;;
}
&nbsp;
// ─── Unified AST (.ck) ───────────────────────────────────────────────────
&nbsp;
export interface CkRootNode {
kind: 'ckRoot';
meta: Record&lt;string, string&gt;;
services: Record&lt;string, string&gt;;
/** File-level security default — cascades to all routes/operations unless overridden. */
security?: SecurityNode;
/** File-level request headers from `options { request: { headers { ... } } }` — merged into every operation's request headers. */
requestHeaders?: OpResponseHeaderNode[];
/** File-level response headers from `options { response: { headers { ... } } }` — merged into every status code on every operation. */
responseHeaders?: OpResponseHeaderNode[];
models: ModelNode[];
routes: OpRouteNode[];
file: string;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for decompose.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> decompose.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>3/3</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>3/3</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Decompose a unified CkRootNode into separate ContractRootNode + OpRootNode
* for downstream codegen functions that operate on models or routes independently.
*/
import type { CkRootNode, ContractRootNode, OpRootNode } from './ast.js';
&nbsp;
export function decomposeCk(ck: CkRootNode): { contract: ContractRootNode; op: OpRootNode } {
const contract: ContractRootNode = {
kind: 'contractRoot',
meta: { ...ck.meta },
services: { ...ck.services },
models: ck.models,
file: ck.file,
};
const op: OpRootNode = {
kind: 'opRoot',
meta: { ...ck.meta },
services: { ...ck.services },
security: ck.security,
routes: ck.routes,
file: ck.file,
};
return { contract, op };
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for diagnostics.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> diagnostics.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>15/15</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>8/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>12/12</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">239x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">44x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export interface Diagnostic {
file: string;
line: number;
message: string;
severity: 'error' | 'warning';
/** Optional stable identifier — consumed by editor tooling to dispatch quick-fixes. */
code?: string;
}
&nbsp;
export class DiagnosticCollector {
private diagnostics: Diagnostic[] = [];
&nbsp;
error(file: string, line: number, message: string, code?: string): void {
this.diagnostics.push({ file, line, message, severity: 'error', code });
}
&nbsp;
warn(file: string, line: number, message: string, code?: string): void {
this.diagnostics.push({ file, line, message, severity: 'warning', code });
}
&nbsp;
hasErrors(): boolean {
return this.diagnostics.some(d =&gt; d.severity === 'error');
}
&nbsp;
getAll(): Diagnostic[] {
return [...this.diagnostics];
}
&nbsp;
report(): void {
for (const d of this.diagnostics) {
const prefix = d.severity === 'error' ? '\x1b[31mERROR\x1b[0m' : '\x1b[33mWARN\x1b[0m';
console.error(`${prefix} ${d.file}:${d.line} ${d.message}`);
}
const errors = this.diagnostics.filter(d =&gt; d.severity === 'error').length;
const warns = this.diagnostics.filter(d =&gt; d.severity === 'warning').length;
if (errors &gt; 0 || warns &gt; 0) {
console.error(`\n${errors} error(s), ${warns} warning(s)`);
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for grammar.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> grammar.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">75% </span>
<span class="quiet">Statements</span>
<span class='fraction'>6/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">75% </span>
<span class="quiet">Lines</span>
<span class='fraction'>6/8</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Grammar loader — compiles the Ohm grammar and exports the grammar object.
* The .ohm file is the source of truth for the Contract DSL syntax.
*/
import * as ohm from 'ohm-js';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
&nbsp;
// Load the grammar source at module init (singleton pattern).
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
&nbsp;
// In dev (src/), the .ohm file is a sibling.
// In dist, the .ohm file is copied to the dist directory by the build.
let grammarPath = join(__dirname, 'contractkit.ohm');
&nbsp;
let grammarSource: string;
try {
grammarSource = readFileSync(grammarPath, 'utf-8');
} catch {
// Fallback: try relative to cwd (for tests running from source)
<span class="cstat-no" title="statement not covered" > grammarPath = join(process.cwd(), 'src', 'contractkit.ohm');</span>
<span class="cstat-no" title="statement not covered" > grammarSource = readFileSync(grammarPath, 'utf-8');</span>
}
&nbsp;
export const grammar: ohm.Grammar = ohm.grammar(grammarSource);
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for incremental.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> incremental.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.1% </span>
<span class="quiet">Statements</span>
<span class='fraction'>67/69</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">93.47% </span>
<span class="quiet">Branches</span>
<span class='fraction'>43/46</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">90.9% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/11</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">98.21% </span>
<span class="quiet">Lines</span>
<span class='fraction'>55/56</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { createHash } from 'node:crypto';
&nbsp;
/**
* Schema version for the on-disk manifest. Bumped if the manifest shape itself changes.
* Plugin codegen changes are tracked separately via `codegenVersion`.
*/
export const INCREMENTAL_MANIFEST_VERSION = 2;
&nbsp;
/** A single output file produced by a plugin: relative path within the plugin's output dir + content. */
export interface IncrementalOutputFile {
relativePath: string;
content: string;
}
&nbsp;
/** Cache record for one plugin "unit" — the smallest thing a plugin can decide to regenerate or reuse. */
export interface IncrementalUnitRecord {
/** Hash covering every input that affects this unit's output. */
fingerprint: string;
/** Relative paths this unit emitted last time. Used to verify on-disk presence on cache hit, and to detect file removals. */
files: string[];
}
&nbsp;
/**
* Persistent on-disk record describing what a plugin produced last run. Plugins write
* one of these per output dir. The `version` and `codegenVersion` fields together
* decide whether the cache can be honored at all on the current run.
*/
export interface IncrementalManifest {
version: number;
/** Plugin-defined version string. Bump in the plugin code to force a full regen across all units. */
codegenVersion: string;
/** Every relative path the plugin tracks (units' files plus global files plus the manifest itself). Used to compute deletions on the next run. */
files: string[];
/** Per-unit cache records keyed by stable unit ID. */
units: Record&lt;string, IncrementalUnitRecord&gt;;
}
&nbsp;
/** A cacheable codegen unit — a stable key + the inputs to fingerprint + a deferred renderer that's only invoked on cache miss. */
export interface IncrementalUnit {
/** Stable ID across runs (e.g. `&lt;file&gt;::&lt;METHOD&gt; &lt;path&gt;` for ops, `&lt;file&gt;` for per-file outputs). Renaming the source breaks the key, which is the correct behavior — old files get cleaned up, new ones are emitted. */
key: string;
/** Pre-computed fingerprint covering every input that affects this unit's output. The caller is responsible for hashing in any cross-unit inputs (e.g. transitively-referenced models, plugin config). */
fingerprint: string;
/** Renders the unit's output(s). Only called on cache miss. May produce zero, one, or many files (e.g. paired router + types files). */
render: () =&gt; IncrementalOutputFile[];
}
&nbsp;
/** What {@link runIncrementalCodegen} produces — output files the caller must write, the new manifest the caller must persist (separately, typically in the build cache directory), paths that should be deleted, and a count of skipped units (useful for logging). */
export interface IncrementalResult {
/** Output files the caller should write (changed/new units' files plus global files). The manifest is **not** included — persist it separately via {@link IncrementalResult.manifest}. */
filesToWrite: IncrementalOutputFile[];
/** New manifest the caller should persist (typically as JSON to a path under the build cache directory). */
manifest: IncrementalManifest;
/** Relative paths from the prior run that no longer appear in the new run. The caller should delete these from disk. */
deletedPaths: string[];
/** Number of units whose codegen was skipped because their fingerprint matched. */
skippedUnitCount: number;
}
&nbsp;
/** Construct a no-op manifest, used when no prior run exists or when the cache is being intentionally bypassed (`--force`, `cacheEnabled=false`). */
export function emptyIncrementalManifest(codegenVersion: string): IncrementalManifest {
return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files: [], units: {} };
}
&nbsp;
/**
* Parse a previously-persisted manifest file. Returns an empty manifest on any
* shape error (malformed JSON, wrong version, missing fields). Stale files
* never block a build: the worst case is a full regen.
*/
export function parseIncrementalManifest(content: string): IncrementalManifest {
try {
const parsed = JSON.parse(content);
if (!parsed || typeof parsed !== 'object' || parsed.version !== INCREMENTAL_MANIFEST_VERSION) {
return emptyIncrementalManifest('');
}
const codegenVersion = typeof parsed.codegenVersion === 'string' ? parsed.codegenVersion : <span class="branch-1 cbranch-no" title="branch not covered" >'';</span>
const files = Array.isArray(parsed.files) &amp;&amp; parsed.files.every((f: unknown) =&gt; typeof f === 'string') ? (parsed.files as string[]) : <span class="branch-1 cbranch-no" title="branch not covered" >[];</span>
const units: Record&lt;string, IncrementalUnitRecord&gt; = {};
if (parsed.units &amp;&amp; typeof parsed.units === 'object' &amp;&amp; !Array.isArray(parsed.units)) {
for (const [key, raw] of Object.entries(parsed.units as Record&lt;string, unknown&gt;)) {
<span class="missing-if-branch" title="if path not taken" >I</span>if (!raw || typeof raw !== 'object') <span class="cstat-no" title="statement not covered" >continue;</span>
const entry = raw as Record&lt;string, unknown&gt;;
const fp = entry['fingerprint'];
const fs = entry['files'];
if (typeof fp !== 'string') continue;
if (!Array.isArray(fs) || !fs.every(p =&gt; typeof p === 'string')) continue;
units[key] = { fingerprint: fp, files: fs as string[] };
}
}
return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files, units };
} catch {
return emptyIncrementalManifest('');
}
}
&nbsp;
/** sha256 hex of `value` after stable JSON serialization. Two payloads with the same content always hash the same. */
export function hashFingerprint(value: unknown): string {
return createHash('sha256').update(stableStringify(value)).digest('hex');
}
&nbsp;
/**
* JSON.stringify variant that sorts object keys recursively, so structurally
* equivalent values always serialize identically.
*
* Handles `bigint` values (which native `JSON.stringify` rejects) by emitting
* them as a tagged string `"&lt;bigint:VALUE&gt;"`. Tagging — rather than coercing to
* a plain string or number — keeps `1n` and `"1"` distinguishable in fingerprints.
* `undefined` is normalized to `null` (stable) instead of being dropped (which
* would make `{a: undefined}` and `{}` collide).
*/
export function stableStringify(value: unknown): string {
if (value === undefined) return 'null';
if (typeof value === 'bigint') return JSON.stringify(`&lt;bigint:${value.toString()}&gt;`);
if (value === null || typeof value !== 'object') return JSON.stringify(value);
if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
const keys = Object.keys(value as Record&lt;string, unknown&gt;).sort();
return '{' + keys.map(k =&gt; JSON.stringify(k) + ':' + stableStringify((value as Record&lt;string, unknown&gt;)[k])).join(',') + '}';
}
&nbsp;
/**
* Run incremental codegen.
*
* For each unit, compares its current `fingerprint` against the prior manifest's
* record. On match (and provided every previously-emitted file is still on disk)
* the unit's `render()` is skipped and its prior output paths carry forward.
* On mismatch (or a missing file, or a `codegenVersion` bump) the unit re-renders
* and its files land in `filesToWrite`.
*
* Global files always land in `filesToWrite` — they're for outputs that always
* regenerate (aggregators, barrels, constants).
*
* The caller is responsible for:
* 1. Writing every `filesToWrite` entry to disk.
* 2. Deleting every `deletedPaths` entry from disk.
* 3. Persisting `manifest` to its own location (typically `&lt;cacheDir&gt;/&lt;plugin&gt;-manifest.json`).
* The manifest is NOT in `filesToWrite` — it's deliberately separate so plugins can
* place build state under the CLI cache dir rather than mixing it with output files.
*/
export function runIncrementalCodegen(args: {
codegenVersion: string;
prevManifest: IncrementalManifest;
/** Files always written, regardless of cache state — typically aggregators or constants. */
globalFiles: IncrementalOutputFile[];
/** Cacheable units, in deterministic order. */
units: IncrementalUnit[];
/** Returns `true` if the given relative path currently exists on disk. Used to invalidate cache entries when a previously-emitted file was deleted. */
fileExists: (relativePath: string) =&gt; boolean;
}): IncrementalResult {
const { codegenVersion, prevManifest, globalFiles, units, fileExists } = args;
const filesToWrite: IncrementalOutputFile[] = [];
const trackedPaths = new Set&lt;string&gt;();
const newUnits: Record&lt;string, IncrementalUnitRecord&gt; = {};
let skippedUnitCount = 0;
&nbsp;
const cacheUsable = prevManifest.version === INCREMENTAL_MANIFEST_VERSION &amp;&amp; prevManifest.codegenVersion === codegenVersion;
&nbsp;
for (const file of globalFiles) {
filesToWrite.push(file);
trackedPaths.add(file.relativePath);
}
&nbsp;
for (const unit of units) {
const prev = cacheUsable ? prevManifest.units[unit.key] : undefined;
const cacheHit =
prev !== undefined &amp;&amp; prev.fingerprint === unit.fingerprint &amp;&amp; prev.files.length &gt; 0 &amp;&amp; prev.files.every(p =&gt; fileExists(p));
&nbsp;
if (cacheHit) {
newUnits[unit.key] = { fingerprint: unit.fingerprint, files: prev!.files };
for (const p of prev!.files) trackedPaths.add(p);
skippedUnitCount++;
continue;
}
&nbsp;
const rendered = unit.render();
const renderedPaths: string[] = [];
for (const file of rendered) {
filesToWrite.push(file);
trackedPaths.add(file.relativePath);
renderedPaths.push(file.relativePath);
}
newUnits[unit.key] = { fingerprint: unit.fingerprint, files: renderedPaths };
}
&nbsp;
const sortedFiles = [...trackedPaths].sort();
const manifest: IncrementalManifest = {
version: INCREMENTAL_MANIFEST_VERSION,
codegenVersion,
files: sortedFiles,
units: newUnits,
};
&nbsp;
const deletedPaths = prevManifest.files.filter(p =&gt; !trackedPaths.has(p));
return { filesToWrite, manifest, deletedPaths, skippedUnitCount };
}
&nbsp;
/** Serialize a manifest to the JSON form persisted on disk. */
export function <span class="fstat-no" title="function not covered" >serializeIncrementalManifest(m</span>anifest: IncrementalManifest): string {
<span class="cstat-no" title="statement not covered" > return JSON.stringify(manifest, null, 2) + '\n';</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for parser.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> parser.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>9/9</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">66.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>4/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>9/9</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">229x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">229x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">225x</span>
<span class="cline-any cline-yes">225x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Contract DSL Parser — Ohm grammar-driven implementation.
* Parses .ck files containing contract and operation declarations.
*/
import { grammar } from './grammar.js';
import { createSemantics } from './semantics.js';
import { DiagnosticCollector } from './diagnostics.js';
import type { CkRootNode } from './ast.js';
&nbsp;
const semantics = createSemantics(grammar);
&nbsp;
/**
* Parse a .ck source file into a CkRootNode AST.
*
* Does NOT merge options-level header globals into operations — that is a separate
* normalization step (see `applyOptionsDefaults`) that codegen pipelines opt into.
* Tools that need the original source shape (e.g. the prettier plugin for round-trip
* formatting) should use this raw output.
*/
export function parseCk(source: string, file: string, diag: DiagnosticCollector): CkRootNode {
const match = grammar.match(source, 'Root');
&nbsp;
if (match.failed()) {
const lineMatch = match.message?.match(/Line (\d+)/);
const line = lineMatch ? parseInt(lineMatch[1]!, 10) : <span class="branch-1 cbranch-no" title="branch not covered" >0;</span>
diag.error(file, line, match.message ?? <span class="branch-1 cbranch-no" title="branch not covered" >'Parse error');</span>
return { kind: 'ckRoot', meta: {}, services: {}, models: [], routes: [], file };
}
&nbsp;
const ast = semantics(match).toAst(file, diag) as CkRootNode;
return ast;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

Sorry, the diff of this file is too big to display

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for type-builders.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> type-builders.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">87.38% </span>
<span class="quiet">Statements</span>
<span class='fraction'>97/111</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">68.21% </span>
<span class="quiet">Branches</span>
<span class='fraction'>88/129</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>20/20</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">87.91% </span>
<span class="quiet">Lines</span>
<span class='fraction'>80/91</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">311x</span>
<span class="cline-any cline-yes">212x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">99x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">216x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">213x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">214x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Shared type-building utilities used by both contract and operation semantic actions.
* Extracted from visitor-contract.ts and visitor-op.ts to eliminate duplication.
*/
import type { ContractTypeNode, ScalarTypeNode, UnionTypeNode } from './ast.js';
import { SCALAR_NAMES } from './ast.js';
&nbsp;
export const OBJECT_MODES = new Set&lt;string&gt;(['strict', 'strip', 'loose']);
export const ROUTE_MODIFIERS = new Set&lt;string&gt;(['internal', 'deprecated', 'public']);
export const HTTP_METHODS = new Set&lt;string&gt;(['get', 'post', 'put', 'patch', 'delete']);
&nbsp;
/** Parsed type argument — either a key=value constraint or a positional value. */
export type TypeArgKeyValue = { key: string; value: string | number | boolean };
export type TypeArgString = { type: 'string'; value: string };
export type TypeArgNumber = { type: 'number'; value: number };
export type TypeArgBoolean = { type: 'boolean'; value: boolean };
export type TypeArgType = { type: 'type'; value: ContractTypeNode };
export type TypeArg = TypeArgKeyValue | TypeArgString | TypeArgNumber | TypeArgBoolean | TypeArgType;
&nbsp;
/** Resolve a simple type name to a ContractTypeNode (scalar or model ref). */
export function resolveSimpleType(name: string): ContractTypeNode {
if (SCALAR_NAMES.has(name)) {
return { kind: 'scalar', name: name as ScalarTypeNode['name'] };
}
return { kind: 'ref', name };
}
&nbsp;
/** Build a compound or constrained type from a type name and parsed arguments. */
export function buildCompoundType(name: string, args: TypeArg[]): ContractTypeNode {
switch (name) {
case 'array':
return buildArrayType(args);
case 'tuple':
return buildTupleType(args);
case 'record':
return buildRecordType(args);
case 'enum':
return buildEnumType(args);
case 'literal':
return buildLiteralType(args);
case 'lazy':
return buildLazyType(args);
case 'discriminated':
return buildDiscriminatedUnionType(args);
default: {
<span class="missing-if-branch" title="else path not taken" >E</span>if (SCALAR_NAMES.has(name)) {
return buildScalarWithModifiers(name as ScalarTypeNode['name'], args);
}
<span class="cstat-no" title="statement not covered" > return { kind: 'ref', name };</span>
}
}
}
&nbsp;
function buildArrayType(args: TypeArg[]): ContractTypeNode {
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const item: ContractTypeNode = typeArgs[0]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
let min: number | undefined;
let max: number | undefined;
for (const a of args) {
if ('key' in a &amp;&amp; a.key === 'min') min = Number(a.value);
if ('key' in a &amp;&amp; a.key === 'max') max = Number(a.value);
}
return { kind: 'array', item, min, max };
}
&nbsp;
function buildTupleType(args: TypeArg[]): ContractTypeNode {
const items = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type').map(a =&gt; a.value);
return { kind: 'tuple', items };
}
&nbsp;
function buildRecordType(args: TypeArg[]): ContractTypeNode {
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const key: ContractTypeNode = typeArgs[0]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'string' };</span>
const value: ContractTypeNode = typeArgs[1]?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
return { kind: 'record', key, value };
}
&nbsp;
function buildEnumType(args: TypeArg[]): ContractTypeNode {
const values: string[] = [];
for (const a of args) {
if ('type' in a &amp;&amp; a.type === 'type' &amp;&amp; a.value.kind === 'ref') {
values.push(a.value.name);
} else if ('type' in a &amp;&amp; a.type === 'string') {
values.push(a.value);
} else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if ('type' in a &amp;&amp; a.type === 'type' &amp;&amp; a.value.kind === 'scalar') {</span>
<span class="cstat-no" title="statement not covered" > values.push(a.value.name);</span>
}
}
return { kind: 'enum', values };
}
&nbsp;
function buildLiteralType(args: TypeArg[]): ContractTypeNode {
const arg = args[0];
<span class="missing-if-branch" title="if path not taken" >I</span>if (!arg) <span class="cstat-no" title="statement not covered" >return { kind: 'literal', value: '' };</span>
<span class="missing-if-branch" title="else path not taken" >E</span>if ('type' in arg) {
if (arg.type === 'string') return { kind: 'literal', value: arg.value };
if (arg.type === 'number') return { kind: 'literal', value: arg.value };
<span class="missing-if-branch" title="else path not taken" >E</span>if (arg.type === 'boolean') return { kind: 'literal', value: arg.value };
}
<span class="cstat-no" title="statement not covered" > return { kind: 'literal', value: String('value' in arg ? arg.value : '') };</span>
}
&nbsp;
function buildLazyType(args: TypeArg[]): ContractTypeNode {
const typeArg = args.find((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const inner: ContractTypeNode = typeArg?.value ?? <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'scalar', name: 'unknown' };</span>
return { kind: 'lazy', inner };
}
&nbsp;
function buildDiscriminatedUnionType(args: TypeArg[]): ContractTypeNode {
let discriminator = '';
for (const a of args) {
if ('key' in a &amp;&amp; a.key === 'by') {
discriminator = String(a.value);
}
}
&nbsp;
const typeArgs = args.filter((a): a is TypeArgType =&gt; 'type' in a &amp;&amp; a.type === 'type');
const members: ContractTypeNode[] = [];
for (const ta of typeArgs) {
// A single TypeExpression like `A | B | C` arrives as one TypeArgType with a union node.
// Flatten it so members ends up as the leaf types.
if (ta.value.kind === 'union') {
members.push(...ta.value.members);
} else {
members.push(ta.value);
}
}
&nbsp;
return { kind: 'discriminatedUnion', discriminator, members };
}
&nbsp;
function buildScalarWithModifiers(name: ScalarTypeNode['name'], args: TypeArg[]): ScalarTypeNode {
const scalar: ScalarTypeNode = { kind: 'scalar', name };
for (const a of args) {
// Positional string argument (quoted): used as format for date/time types
<span class="missing-if-branch" title="if path not taken" >I</span>if ('type' in a &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >a.type === 'string' &amp;&amp; <span class="branch-2 cbranch-no" title="branch not covered" >!</span>('key' in a)) {</span>
<span class="cstat-no" title="statement not covered" > if (name === 'date' || name === 'time' || name === 'datetime') {</span>
<span class="cstat-no" title="statement not covered" > scalar.format = String(a.value);</span>
}
<span class="cstat-no" title="statement not covered" > continue;</span>
}
// Positional ref argument (unquoted identifier): used as format for date/time types
<span class="missing-if-branch" title="if path not taken" >I</span>if ('type' in a &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >a.type === 'type' &amp;&amp; <span class="branch-2 cbranch-no" title="branch not covered" >a</span>.value?.kind === 'ref' &amp;&amp; <span class="branch-3 cbranch-no" title="branch not covered" >!</span>('key' in a)) {</span>
<span class="cstat-no" title="statement not covered" > if (name === 'date' || name === 'time' || name === 'datetime') {</span>
<span class="cstat-no" title="statement not covered" > scalar.format = String(a.value.name);</span>
}
<span class="cstat-no" title="statement not covered" > continue;</span>
}
<span class="missing-if-branch" title="if path not taken" >I</span>if (!('key' in a)) <span class="cstat-no" title="statement not covered" >continue;</span>
if (a.key === 'min') scalar.min = name === 'bigint' ? <span class="branch-0 cbranch-no" title="branch not covered" >BigInt(a.value) : n</span>ame === 'duration' ? <span class="branch-0 cbranch-no" title="branch not covered" >String(a.value) : N</span>umber(a.value);
if (a.key === 'max') scalar.max = name === 'bigint' ? <span class="branch-0 cbranch-no" title="branch not covered" >BigInt(a.value) : n</span>ame === 'duration' ? <span class="branch-0 cbranch-no" title="branch not covered" >String(a.value) : N</span>umber(a.value);
if (a.key === 'len' || a.key === 'length') scalar.len = Number(a.value);
if (a.key === 'regex') scalar.regex = String(a.value);
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.key === 'format') <span class="cstat-no" title="statement not covered" >scalar.format = String(a.value);</span>
}
return scalar;
}
&nbsp;
/**
* Extract nullability from a type node.
* If the type is a union containing `null`, remove the null member and return nullable=true.
*/
export function extractNullability(type: ContractTypeNode): { type: ContractTypeNode; nullable: boolean } {
if (type.kind === 'union') {
const union = type as UnionTypeNode;
const nullIdx = union.members.findIndex(m =&gt; m.kind === 'scalar' &amp;&amp; (m as ScalarTypeNode).name === 'null');
if (nullIdx !== -1) {
const filtered = [...union.members];
filtered.splice(nullIdx, 1);
return { type: filtered.length === 1 ? filtered[0]! : <span class="branch-1 cbranch-no" title="branch not covered" >{ kind: 'union', members: filtered }, n</span>ullable: true };
}
} else if (type.kind === 'scalar' &amp;&amp; type.name === 'null') {
return { type, nullable: true };
}
return { type, nullable: false };
}
&nbsp;
/**
* Convert a ContractTypeNode to ParamSource for query/headers blocks.
*/
export function typeNodeToParamSource(node: ContractTypeNode): import('./ast.js').ParamSource {
if (node.kind === 'ref') return { kind: 'ref', name: node.name };
<span class="missing-if-branch" title="else path not taken" >E</span>if (node.kind === 'inlineObject') {
return {
kind: 'params',
nodes: node.fields.map(f =&gt; ({
name: f.name,
optional: f.optional,
nullable: f.nullable,
type: f.type,
default: f.default,
description: f.description,
loc: f.loc,
})),
};
}
<span class="cstat-no" title="statement not covered" > return { kind: 'type', node: node };</span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for validate-inheritance.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> validate-inheritance.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">68.45% </span>
<span class="quiet">Statements</span>
<span class='fraction'>102/149</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">58.51% </span>
<span class="quiet">Branches</span>
<span class='fraction'>55/94</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">71.42% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/14</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">70.68% </span>
<span class="quiet">Lines</span>
<span class='fraction'>82/116</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">67x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">65x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Validates multi-base inheritance: detects cycles, enforces that conflicting fields across
* bases are explicitly redeclared with the `override` modifier, and rejects spurious `override`
* modifiers on fields not present in any base.
*
* Resolution rules:
* - Each base contributes its fully-resolved field set (recursive). Diamond inheritance is
* deduplicated — a model reachable via multiple paths only contributes its fields once.
* - Two fields with the same name conflict iff their `FieldNode` shapes differ on any of:
* type, optional, nullable, visibility, default, deprecated. `description` and `loc` are ignored.
* - When two or more bases contribute a same-named field, the model must redeclare that field
* in its own inline block with `override`.
* - A field with `override` must shadow at least one base-contributed field.
*
* Runs after `validate-refs` so we know all referenced bases resolve. Cycles short-circuit
* the recursion so a downstream conflict check on a cyclic chain emits one cycle error per cycle,
* not one per node.
*/
import type { ContractRootNode, ContractTypeNode, FieldNode, ModelNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
export function validateInheritance(contractRoots: ContractRootNode[], diag: DiagnosticCollector): void {
const modelMap = new Map&lt;string, ModelNode&gt;();
for (const root of contractRoots) {
for (const model of root.models) modelMap.set(model.name, model);
}
&nbsp;
const cycleNodes = detectCycles(modelMap, diag);
&nbsp;
for (const root of contractRoots) {
for (const model of root.models) {
if (!model.bases || model.bases.length === 0) continue;
if (cycleNodes.has(model.name)) continue;
checkModel(model, modelMap, cycleNodes, diag);
}
}
}
&nbsp;
function detectCycles(modelMap: Map&lt;string, ModelNode&gt;, diag: DiagnosticCollector): Set&lt;string&gt; {
const onStack = new Set&lt;string&gt;();
const visited = new Set&lt;string&gt;();
const inCycle = new Set&lt;string&gt;();
&nbsp;
function visit(name: string, path: string[]): void {
if (onStack.has(name)) {
const start = path.indexOf(name);
const cycle = path.slice(start).concat(name).join(' → ');
for (const n of path.slice(start)) inCycle.add(n);
const model = modelMap.get(name);
<span class="missing-if-branch" title="else path not taken" >E</span>if (model) diag.error(model.loc.file, model.loc.line, `Inheritance cycle: ${cycle}`);
return;
}
if (visited.has(name)) return;
const model = modelMap.get(name);
if (!model || !model.bases) {
visited.add(name);
return;
}
onStack.add(name);
path.push(name);
for (const b of model.bases) visit(b, path);
path.pop();
onStack.delete(name);
visited.add(name);
}
&nbsp;
for (const name of modelMap.keys()) visit(name, []);
return inCycle;
}
&nbsp;
/** Resolve a base model to its **effective** field set: the merge of all its inherited
* fields with its own (own fields shadow inherited by name). Memoized per model. */
function resolveEffectiveFields(
baseName: string,
modelMap: Map&lt;string, ModelNode&gt;,
cycleNodes: Set&lt;string&gt;,
cache: Map&lt;string, Map&lt;string, FieldNode&gt;&gt;,
): Map&lt;string, FieldNode&gt; {
const cached = cache.get(baseName);
if (cached) return cached;
<span class="missing-if-branch" title="if path not taken" >I</span>if (cycleNodes.has(baseName)) {
const empty = <span class="cstat-no" title="statement not covered" >new Map&lt;string, FieldNode&gt;();</span>
<span class="cstat-no" title="statement not covered" > cache.set(baseName, empty);</span>
<span class="cstat-no" title="statement not covered" > return empty;</span>
}
const base = modelMap.get(baseName);
<span class="missing-if-branch" title="if path not taken" >I</span>if (!base) {
const empty = <span class="cstat-no" title="statement not covered" >new Map&lt;string, FieldNode&gt;();</span>
<span class="cstat-no" title="statement not covered" > cache.set(baseName, empty);</span>
<span class="cstat-no" title="statement not covered" > return empty;</span>
}
// Set early to break any unexpected recursion.
const effective = new Map&lt;string, FieldNode&gt;();
cache.set(baseName, effective);
if (base.bases) {
for (const grandparent of base.bases) {
const g = resolveEffectiveFields(grandparent, modelMap, cycleNodes, cache);
for (const [name, field] of g) effective.set(name, field);
}
}
for (const own of base.fields) {
effective.set(own.name, own);
}
return effective;
}
&nbsp;
function checkModel(model: ModelNode, modelMap: Map&lt;string, ModelNode&gt;, cycleNodes: Set&lt;string&gt;, diag: DiagnosticCollector): void {
const cache = new Map&lt;string, Map&lt;string, FieldNode&gt;&gt;();
&nbsp;
// For each direct base, resolve its effective field set. Cross-base conflicts are detected
// by comparing same-named contributions across bases (each base's own overrides already
// applied within its set).
const baseFieldsByName = new Map&lt;string, { source: string; field: FieldNode }[]&gt;();
for (const base of model.bases!) {
const effective = resolveEffectiveFields(base, modelMap, cycleNodes, cache);
for (const [name, field] of effective) {
const list = baseFieldsByName.get(name) ?? [];
list.push({ source: base, field });
baseFieldsByName.set(name, list);
}
}
&nbsp;
const localFieldsByName = new Map&lt;string, FieldNode&gt;();
for (const f of model.fields) localFieldsByName.set(f.name, f);
&nbsp;
// Rule: every cross-base conflict must be redeclared with override.
for (const [name, list] of baseFieldsByName) {
if (list.length &lt; 2) continue;
const allIdentical = list.every(item =&gt; fieldsAreIdentical(item.field, list[0]!.field));
if (allIdentical) continue;
const local = localFieldsByName.get(name);
if (!local) {
const sources = list.map(l =&gt; `'${l.source}'`).join(' and ');
diag.error(
model.loc.file,
model.loc.line,
`Field '${name}' is declared by ${sources} with different shapes — redeclare in '${model.name}' with 'override'`,
'missing-override',
);
continue;
}
if (!local.override) {
const sources = list.map(l =&gt; `'${l.source}'`).join(' and ');
diag.error(
local.loc.file,
local.loc.line,
`Field '${name}' conflicts across bases ${sources} — mark as 'override'`,
'missing-override',
);
}
}
&nbsp;
// Rule: `override` must shadow at least one base-contributed field.
for (const f of model.fields) {
if (!f.override) continue;
if (!baseFieldsByName.has(f.name)) {
diag.error(
f.loc.file,
f.loc.line,
`Field '${f.name}' has 'override' but is not declared in any base of '${model.name}'`,
'spurious-override',
);
}
}
}
&nbsp;
/** Structural deep-equality on a FieldNode for inheritance-conflict purposes.
* Compares: type (deep), optional, nullable, visibility, default, deprecated.
* Ignores: description, loc, override (the marker itself isn't part of "shape"). */
export function fieldsAreIdentical(a: FieldNode, b: FieldNode): boolean {
if (a.optional !== b.optional) return false;
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.nullable !== b.nullable) <span class="cstat-no" title="statement not covered" >return false;</span>
if (a.visibility !== b.visibility) return false;
<span class="missing-if-branch" title="if path not taken" >I</span>if ((a.deprecated ?? false) !== (b.deprecated ?? false)) <span class="cstat-no" title="statement not covered" >return false;</span>
if (a.default !== b.default) return false;
return typesAreIdentical(a.type, b.type);
}
&nbsp;
function typesAreIdentical(a: ContractTypeNode, b: ContractTypeNode): boolean {
<span class="missing-if-branch" title="if path not taken" >I</span>if (a.kind !== b.kind) <span class="cstat-no" title="statement not covered" >return false;</span>
switch (a.kind) {
case 'scalar': {
const bb = b as typeof a;
return a.name === bb.name &amp;&amp; a.min === bb.min &amp;&amp; a.max === bb.max &amp;&amp; a.len === bb.len &amp;&amp; a.regex === bb.regex &amp;&amp; a.format === bb.format;
}
<span class="branch-1 cbranch-no" title="branch not covered" > case 'literal': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.value === bb.value;</span>
}
<span class="branch-2 cbranch-no" title="branch not covered" > case 'enum': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.values.length === bb.values.length &amp;&amp; a.values.every(<span class="fstat-no" title="function not covered" >(v</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >v === bb.values[i])</span>;</span>
}
<span class="branch-3 cbranch-no" title="branch not covered" > case 'ref': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.name === bb.name;</span>
}
<span class="branch-4 cbranch-no" title="branch not covered" > case 'array': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.min !== bb.min || a.max !== bb.max) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.item, bb.item);</span>
}
<span class="branch-5 cbranch-no" title="branch not covered" > case 'tuple': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.items.length === bb.items.length &amp;&amp; a.items.every(<span class="fstat-no" title="function not covered" >(i</span>t, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(it, bb.items[i]!));</span></span>
}
<span class="branch-6 cbranch-no" title="branch not covered" > case 'record': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.key, bb.key) &amp;&amp; typesAreIdentical(a.value, bb.value);</span>
}
<span class="branch-7 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="branch-8 cbranch-no" title="branch not covered" > case 'intersection': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return a.members.length === bb.members.length &amp;&amp; a.members.every(<span class="fstat-no" title="function not covered" >(m</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(m, bb.members[i]!));</span></span>
}
<span class="branch-9 cbranch-no" title="branch not covered" > case 'discriminatedUnion': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.discriminator !== bb.discriminator) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > return a.members.length === bb.members.length &amp;&amp; a.members.every(<span class="fstat-no" title="function not covered" >(m</span>, i) =&gt; <span class="cstat-no" title="statement not covered" >typesAreIdentical(m, bb.members[i]!));</span></span>
}
<span class="branch-10 cbranch-no" title="branch not covered" > case 'lazy': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > return typesAreIdentical(a.inner, bb.inner);</span>
}
<span class="branch-11 cbranch-no" title="branch not covered" > case 'inlineObject': {</span>
const bb = <span class="cstat-no" title="statement not covered" >b as typeof a;</span>
<span class="cstat-no" title="statement not covered" > if (a.fields.length !== bb.fields.length) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > for (let i = <span class="cstat-no" title="statement not covered" >0; i</span> &lt; a.fields.length; i++) {</span>
const af = <span class="cstat-no" title="statement not covered" >a.fields[i]!</span>;
const bf = <span class="cstat-no" title="statement not covered" >bb.fields[i]!</span>;
<span class="cstat-no" title="statement not covered" > if (af.name !== bf.name) <span class="cstat-no" title="statement not covered" >return false;</span></span>
<span class="cstat-no" title="statement not covered" > if (!fieldsAreIdentical(af, bf)) <span class="cstat-no" title="statement not covered" >return false;</span></span>
}
<span class="cstat-no" title="statement not covered" > return true;</span>
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for validate-refs.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> validate-refs.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">40.32% </span>
<span class="quiet">Statements</span>
<span class='fraction'>50/124</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">37.31% </span>
<span class="quiet">Branches</span>
<span class='fraction'>25/67</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">50% </span>
<span class="quiet">Functions</span>
<span class='fraction'>9/18</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">42.59% </span>
<span class="quiet">Lines</span>
<span class='fraction'>46/108</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">30x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">30x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { ContractRootNode, OpRootNode, ContractTypeNode, ModelNode, FieldNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
/**
* After all files are parsed, validate that type references point to
* defined models.
*/
export function validateRefs(contractRoots: ContractRootNode[], opRoots: OpRootNode[], diag: DiagnosticCollector, allContractRoots?: ContractRootNode[]): void {
// Phase 1: Collect all defined model names from ALL contract files (not just changed ones)
// so that cached/unchanged files don't cause false "not defined" warnings.
const modelNames = new Set&lt;string&gt;();
const modelMap = new Map&lt;string, ModelNode&gt;();
for (const root of allContractRoots ?? contractRoots) {
for (const model of root.models) {
modelNames.add(model.name);
modelMap.set(model.name, model);
}
}
&nbsp;
// Phase 2: Check contract type references
for (const root of contractRoots) {
for (const model of root.models) {
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.bases) {
<span class="cstat-no" title="statement not covered" > for (const base of model.bases) {</span>
<span class="cstat-no" title="statement not covered" > if (!modelNames.has(base)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(model.loc.file, model.loc.line, `Base model "${base}" is not defined in any contract file`, 'unknown-model');</span>
}
}
}
if (model.type) {
checkTypeRefs(model.type, model.loc.file, model.loc.line, modelNames, diag);
checkDiscriminatedUnions(model.type, model.loc.file, model.loc.line, modelMap, diag);
}
for (const field of model.fields) {
checkTypeRefs(field.type, field.loc.file, field.loc.line, modelNames, diag);
checkDiscriminatedUnions(field.type, field.loc.file, field.loc.line, modelMap, diag);
}
}
}
&nbsp;
// Phase 3: Check OP type references
for (const root of opRoots) {
<span class="cstat-no" title="statement not covered" > for (const route of root.routes) {</span>
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(route.params, root.file, route.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > for (const op of route.operations) {</span>
<span class="cstat-no" title="statement not covered" > if (op.request) {</span>
<span class="cstat-no" title="statement not covered" > for (const body of op.request.bodies) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(body.bodyType, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(body.bodyType, root.file, op.loc.line, modelMap, diag);</span>
}
}
<span class="cstat-no" title="statement not covered" > for (const resp of op.responses) {</span>
<span class="cstat-no" title="statement not covered" > if (resp.bodyType) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(resp.bodyType, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(resp.bodyType, root.file, op.loc.line, modelMap, diag);</span>
}
}
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(op.query, root.file, op.loc.line, modelNames, diag);</span>
<span class="cstat-no" title="statement not covered" > checkParamSourceRefs(op.headers, root.file, op.loc.line, modelNames, diag);</span>
}
}
}
}
&nbsp;
function checkTypeRefs(type: ContractTypeNode, file: string, line: number, models: Set&lt;string&gt;, diag: DiagnosticCollector): void {
switch (type.kind) {
case 'ref':
<span class="missing-if-branch" title="if path not taken" >I</span>if (!models.has(type.name)) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `Referenced model "${type.name}" is not defined in any contract file`, 'unknown-model');</span>
}
break;
<span class="branch-1 cbranch-no" title="branch not covered" > case 'array':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.item, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-2 cbranch-no" title="branch not covered" > case 'tuple':</span>
<span class="cstat-no" title="statement not covered" > type.items.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-3 cbranch-no" title="branch not covered" > case 'record':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.key, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.value, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-4 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
case 'discriminatedUnion':
type.members.forEach(t =&gt; checkTypeRefs(t, file, line, models, diag));
break;
<span class="branch-6 cbranch-no" title="branch not covered" > case 'intersection':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-7 cbranch-no" title="branch not covered" > case 'lazy':</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(type.inner, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-8 cbranch-no" title="branch not covered" > case 'inlineObject':</span>
<span class="cstat-no" title="statement not covered" > type.fields.forEach(<span class="fstat-no" title="function not covered" >f =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckTypeRefs(f.type, file, f.loc.line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
}
}
&nbsp;
/**
* Validate discriminated unions: every member must be a model ref or inline object,
* and every member must contain a literal-typed field matching the discriminator.
*/
function checkDiscriminatedUnions(type: ContractTypeNode, file: string, line: number, models: Map&lt;string, ModelNode&gt;, diag: DiagnosticCollector): void {
switch (type.kind) {
case 'discriminatedUnion': {
<span class="missing-if-branch" title="if path not taken" >I</span>if (!type.discriminator) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `discriminated() requires a "by=&lt;field&gt;" discriminator key`);</span>
}
if (type.members.length &lt; 2) {
diag.warn(file, line, `discriminated() requires at least 2 union members`);
}
for (const member of type.members) {
const fields = resolveMemberFields(member, models);
<span class="missing-if-branch" title="if path not taken" >I</span>if (fields === null) {
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `discriminated union member must be a model ref or inline object (got ${describeKind(member)})`);</span>
<span class="cstat-no" title="statement not covered" > continue;</span>
}
const field = fields.find(f =&gt; f.name === type.discriminator);
if (!field) {
diag.warn(
file,
line,
`discriminated union member ${describeMember(member)} is missing discriminator field "${type.discriminator}"`,
);
continue;
}
if (field.type.kind !== 'literal' &amp;&amp; field.type.kind !== 'enum') {
diag.warn(
file,
line,
`discriminated union member ${describeMember(member)} field "${type.discriminator}" must be a literal or enum (got ${describeKind(field.type)})`,
);
}
}
// Recurse into nested types as well.
type.members.forEach(m =&gt; checkDiscriminatedUnions(m, file, line, models, diag));
break;
}
<span class="branch-1 cbranch-no" title="branch not covered" > case 'array':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.item, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-2 cbranch-no" title="branch not covered" > case 'tuple':</span>
<span class="cstat-no" title="statement not covered" > type.items.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-3 cbranch-no" title="branch not covered" > case 'record':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.key, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.value, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-4 cbranch-no" title="branch not covered" > case 'union':</span>
<span class="branch-5 cbranch-no" title="branch not covered" > case 'intersection':</span>
<span class="cstat-no" title="statement not covered" > type.members.forEach(<span class="fstat-no" title="function not covered" >t =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(t, file, line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-6 cbranch-no" title="branch not covered" > case 'lazy':</span>
<span class="cstat-no" title="statement not covered" > checkDiscriminatedUnions(type.inner, file, line, models, diag);</span>
<span class="cstat-no" title="statement not covered" > break;</span>
<span class="branch-7 cbranch-no" title="branch not covered" > case 'inlineObject':</span>
<span class="cstat-no" title="statement not covered" > type.fields.forEach(<span class="fstat-no" title="function not covered" >f =&gt; <span class="cstat-no" title="statement not covered" >c</span>heckDiscriminatedUnions(f.type, file, f.loc.line, models, diag));</span></span>
<span class="cstat-no" title="statement not covered" > break;</span>
}
}
&nbsp;
/** Resolve a discriminated-union member to its field list, following ref→model and base inheritance. */
function resolveMemberFields(member: ContractTypeNode, models: Map&lt;string, ModelNode&gt;): FieldNode[] | null {
<span class="missing-if-branch" title="if path not taken" >I</span>if (member.kind === 'inlineObject') <span class="cstat-no" title="statement not covered" >return member.fields;</span>
<span class="missing-if-branch" title="else path not taken" >E</span>if (member.kind === 'ref') {
const model = models.get(member.name);
<span class="missing-if-branch" title="if path not taken" >I</span>if (!model) <span class="cstat-no" title="statement not covered" >return null;</span>
// For aliased models, peer through to the aliased type.
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.type) <span class="cstat-no" title="statement not covered" >return resolveMemberFields(model.type, models);</span>
const fields = [...model.fields];
<span class="missing-if-branch" title="if path not taken" >I</span>if (model.bases) {
<span class="cstat-no" title="statement not covered" > for (const base of model.bases) {</span>
const baseFields = <span class="cstat-no" title="statement not covered" >resolveMemberFields({ kind: 'ref', name: base }, models);</span>
<span class="cstat-no" title="statement not covered" > if (baseFields) <span class="cstat-no" title="statement not covered" >fields.push(...baseFields);</span></span>
}
}
return fields;
}
<span class="cstat-no" title="statement not covered" > return null;</span>
}
&nbsp;
function describeMember(member: ContractTypeNode): string {
<span class="missing-if-branch" title="else path not taken" >E</span>if (member.kind === 'ref') return `"${member.name}"`;
<span class="cstat-no" title="statement not covered" > return `(${describeKind(member)})`;</span>
}
&nbsp;
function describeKind(type: ContractTypeNode): string {
return type.kind;
}
&nbsp;
function <span class="fstat-no" title="function not covered" >checkParamSourceRefs(</span>
source: import('./ast.js').ParamSource | undefined,
file: string,
line: number,
models: Set&lt;string&gt;,
diag: DiagnosticCollector,
): void {
<span class="cstat-no" title="statement not covered" > if (!source) <span class="cstat-no" title="statement not covered" >return;</span></span>
<span class="cstat-no" title="statement not covered" > if (source.kind === 'ref') {</span>
<span class="cstat-no" title="statement not covered" > checkNameRef(source.name, file, line, models, diag);</span>
} else <span class="cstat-no" title="statement not covered" >if (source.kind === 'params') {</span>
<span class="cstat-no" title="statement not covered" > for (const param of source.nodes) {</span>
<span class="cstat-no" title="statement not covered" > checkTypeRefs(param.type, file, param.loc.line, models, diag);</span>
}
} else {
<span class="cstat-no" title="statement not covered" > checkTypeRefs(source.node, file, line, models, diag);</span>
}
}
&nbsp;
function <span class="fstat-no" title="function not covered" >checkNameRef(n</span>ame: string, file: string, line: number, models: Set&lt;string&gt;, diag: DiagnosticCollector): void {
<span class="cstat-no" title="statement not covered" > if (/^[A-Z]/.test(name) &amp;&amp; !models.has(name)) {</span>
<span class="cstat-no" title="statement not covered" > diag.warn(file, line, `Referenced type "${name}" is not defined in any contract file`, 'unknown-model');</span>
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-13T17:08:44.837Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display