@invinite-org/chartlang-compiler 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +254 -0
  2. package/dist/analysis/extractAlertConditions.js.map +1 -1
  3. package/dist/analysis/extractCapabilities.js.map +1 -1
  4. package/dist/analysis/extractDependencyGraph.js.map +1 -1
  5. package/dist/analysis/extractInputs.js.map +1 -1
  6. package/dist/analysis/extractMaxLookback.d.ts +2 -1
  7. package/dist/analysis/extractMaxLookback.d.ts.map +1 -1
  8. package/dist/analysis/extractMaxLookback.js +90 -6
  9. package/dist/analysis/extractMaxLookback.js.map +1 -1
  10. package/dist/analysis/extractRequestedIntervals.d.ts +43 -1
  11. package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -1
  12. package/dist/analysis/extractRequestedIntervals.js +95 -10
  13. package/dist/analysis/extractRequestedIntervals.js.map +1 -1
  14. package/dist/analysis/extractRequiresIntervals.js.map +1 -1
  15. package/dist/analysis/forbiddenConstructs.d.ts.map +1 -1
  16. package/dist/analysis/forbiddenConstructs.js +2 -41
  17. package/dist/analysis/forbiddenConstructs.js.map +1 -1
  18. package/dist/analysis/index.d.ts +3 -1
  19. package/dist/analysis/index.d.ts.map +1 -1
  20. package/dist/analysis/index.js +2 -1
  21. package/dist/analysis/index.js.map +1 -1
  22. package/dist/analysis/loopBounds.d.ts +91 -0
  23. package/dist/analysis/loopBounds.d.ts.map +1 -0
  24. package/dist/analysis/loopBounds.js +132 -0
  25. package/dist/analysis/loopBounds.js.map +1 -0
  26. package/dist/analysis/resolveIndexBound.d.ts +73 -0
  27. package/dist/analysis/resolveIndexBound.d.ts.map +1 -0
  28. package/dist/analysis/resolveIndexBound.js +336 -0
  29. package/dist/analysis/resolveIndexBound.js.map +1 -0
  30. package/dist/analysis/statefulCallInLoop.js.map +1 -1
  31. package/dist/analysis/structuralChecks.js.map +1 -1
  32. package/dist/analysis/validateLowerTfIntervals.js.map +1 -1
  33. package/dist/analysis/validateSecurityExpr.d.ts +25 -0
  34. package/dist/analysis/validateSecurityExpr.d.ts.map +1 -0
  35. package/dist/analysis/validateSecurityExpr.js +154 -0
  36. package/dist/analysis/validateSecurityExpr.js.map +1 -0
  37. package/dist/api.d.ts.map +1 -1
  38. package/dist/api.js +13 -3
  39. package/dist/api.js.map +1 -1
  40. package/dist/bundle.js.map +1 -1
  41. package/dist/dependency/index.js.map +1 -1
  42. package/dist/dependency/resolveProducer.js.map +1 -1
  43. package/dist/diagnostics.d.ts +4 -2
  44. package/dist/diagnostics.d.ts.map +1 -1
  45. package/dist/diagnostics.js.map +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/manifest.d.ts +2 -1
  48. package/dist/manifest.d.ts.map +1 -1
  49. package/dist/manifest.js +7 -0
  50. package/dist/manifest.js.map +1 -1
  51. package/dist/program.d.ts.map +1 -1
  52. package/dist/program.js +91 -14
  53. package/dist/program.js.map +1 -1
  54. package/dist/transformers/callsiteIdInjection.d.ts +21 -0
  55. package/dist/transformers/callsiteIdInjection.d.ts.map +1 -1
  56. package/dist/transformers/callsiteIdInjection.js +26 -3
  57. package/dist/transformers/callsiteIdInjection.js.map +1 -1
  58. package/dist/transformers/index.js.map +1 -1
  59. package/dist/transformers/plotKindFromCallsite.js.map +1 -1
  60. package/dist/transformers/resolveCallee.d.ts +21 -0
  61. package/dist/transformers/resolveCallee.d.ts.map +1 -1
  62. package/dist/transformers/resolveCallee.js +14 -1
  63. package/dist/transformers/resolveCallee.js.map +1 -1
  64. package/dist/transformers/rewriteDependencyAccessors.js.map +1 -1
  65. package/dist/typesEmit.js.map +1 -1
  66. package/package.json +2 -2
@@ -3,12 +3,15 @@
3
3
  import ts from "typescript";
4
4
  import { createDiagnostic } from "../diagnostics.js";
5
5
  import { resolveCalleeName } from "../transformers/resolveCallee.js";
6
+ import { unwrapParens } from "./loopBounds.js";
7
+ import { collectConstNumberEnv, resolveIndexUpperBound } from "./resolveIndexBound.js";
6
8
  const OHLCV_FIELDS = new Set(["close", "open", "high", "low", "volume", "time"]);
7
9
  /**
8
10
  * Walk the source file's `ElementAccessExpression` nodes and infer
9
11
  * `maxLookback` plus any `dynamicFallback` capacity from non-literal index
10
12
  * reads on Phase-1 series shapes: `bar.<ohlcv>[N]`, `ta.<name>(...)[N]`,
11
- * and identifier-bound series variables (`const e = ta.ema(...); e[N];`).
13
+ * and identifier-bound series variables (`const e = ta.ema(...); e[N];` or
14
+ * `const s = state.series(...); s[N];`).
12
15
  *
13
16
  * The optional `scope` parameter narrows both the series-variable
14
17
  * collection and the lookback walk to a single AST subtree (typically
@@ -28,13 +31,27 @@ export function extractMaxLookback(sourceFile, checker, sourcePath, scope = sour
28
31
  const diagnostics = [];
29
32
  const seriesVarNames = collectSeriesVarNames(scope, checker);
30
33
  const visit = (node) => {
34
+ if (ts.isCallExpression(node)) {
35
+ const calleeName = resolveCalleeName(node, checker);
36
+ if (calleeName?.startsWith("ta.")) {
37
+ const barsDepth = readHighestLowestBarsDepth(calleeName, node);
38
+ if (barsDepth > maxLookback)
39
+ maxLookback = barsDepth;
40
+ }
41
+ if (isBarPointCall(node)) {
42
+ const depth = readBarPointLookback(node);
43
+ if (depth > maxLookback)
44
+ maxLookback = depth;
45
+ }
46
+ }
31
47
  if (ts.isElementAccessExpression(node)) {
32
48
  if (isSeriesShapedAccess(node, checker, seriesVarNames)) {
33
49
  const argument = node.argumentExpression;
34
- if (ts.isNumericLiteral(argument)) {
35
- const n = Number(argument.text);
36
- if (Number.isFinite(n) && n > maxLookback)
37
- maxLookback = n;
50
+ const constEnv = collectConstNumberEnv(argument, scope);
51
+ const bound = resolveIndexUpperBound(argument, node, { constEnv, checker });
52
+ if (bound !== null) {
53
+ if (bound > maxLookback)
54
+ maxLookback = bound;
38
55
  }
39
56
  else {
40
57
  diagnostics.push(createDiagnostic({
@@ -58,6 +75,46 @@ export function extractMaxLookback(sourceFile, checker, sourcePath, scope = sour
58
75
  diagnostics: Object.freeze(diagnostics.slice()),
59
76
  });
60
77
  }
78
+ /**
79
+ * Whether a call is a `bar.point(…)` invocation. Matched textually on the
80
+ * `bar.point` property-access shape — the same OHLCV-style textual recognition
81
+ * `isSeriesShapedAccess` uses — so it fires for both the destructured
82
+ * `compute({ bar })` binding and a `declare const bar: Bar` test fixture.
83
+ */
84
+ function isBarPointCall(call) {
85
+ const expression = call.expression;
86
+ return (ts.isPropertyAccessExpression(expression) &&
87
+ expression.name.text === "point" &&
88
+ ts.isIdentifier(expression.expression) &&
89
+ expression.expression.text === "bar");
90
+ }
91
+ /**
92
+ * The historical-lookback depth a `bar.point(offset, …)` call contributes,
93
+ * or `0` when it reads the current / a future bar. A negative integer-literal
94
+ * first argument (`bar.point(-N, …)` — or the converter's parenthesised
95
+ * `bar.point(-(N), …)`) anchors `N` bars back, so the runtime's time ring
96
+ * buffer must retain `N` extra slots — exactly like a `series[N]` lookback.
97
+ * `bar.point(0, …)` (current) and positive offsets (future, extrapolated, no
98
+ * buffer depth) contribute `0`; a non-literal / dynamic offset (e.g. a bound
99
+ * `-k` or a computed `-(2 + 3)`) cannot be sized at compile time and also
100
+ * contributes `0` (reads past retention degrade to a NaN time at runtime, per
101
+ * `bar.point`'s contract).
102
+ */
103
+ function readBarPointLookback(call) {
104
+ const first = call.arguments[0];
105
+ if (first === undefined)
106
+ return 0;
107
+ const expr = unwrapParens(first);
108
+ if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.MinusToken) {
109
+ const operand = unwrapParens(expr.operand);
110
+ if (ts.isNumericLiteral(operand)) {
111
+ const n = Number(operand.text);
112
+ if (Number.isFinite(n) && n > 0)
113
+ return n;
114
+ }
115
+ }
116
+ return 0;
117
+ }
61
118
  function collectSeriesVarNames(scope, checker) {
62
119
  const names = new Set();
63
120
  const visit = (node) => {
@@ -65,7 +122,14 @@ function collectSeriesVarNames(scope, checker) {
65
122
  const initializer = node.initializer;
66
123
  if (initializer && ts.isCallExpression(initializer)) {
67
124
  const calleeName = resolveCalleeName(initializer, checker);
68
- if (calleeName?.startsWith("ta.")) {
125
+ // A `state.series(...)`-bound variable is series-shaped just
126
+ // like a `ta.*`-bound one: `s[N]` reads the slot's ring buffer,
127
+ // so its literal index must fold into `maxLookback`. Matched on
128
+ // the resolved callee name (the slot-injection path) so an
129
+ // element-access form like `state["series"](...)` is not
130
+ // recognised — that form is rejected upstream as
131
+ // `stateful-call-element-access`.
132
+ if (calleeName?.startsWith("ta.") || calleeName === "state.series") {
69
133
  names.add(node.name.text);
70
134
  }
71
135
  }
@@ -75,6 +139,26 @@ function collectSeriesVarNames(scope, checker) {
75
139
  visit(scope);
76
140
  return names;
77
141
  }
142
+ /**
143
+ * The historical-lookback depth a `ta.highestbars` / `ta.lowestbars` call
144
+ * contributes. Both primitives return the bar OFFSET (≤ 0) to the extreme
145
+ * over the trailing `length`-bar window, so the deepest offset they can
146
+ * return is `-(length − 1)`. A downstream `bar.point(<that offset>, …)`
147
+ * anchor reads `time.at(length − 1)`, so the runtime's time ring buffer
148
+ * must retain `length − 1` slots. Only a LITERAL second positional `length`
149
+ * arg can be sized at compile time; a non-literal length contributes `0`.
150
+ */
151
+ function readHighestLowestBarsDepth(calleeName, call) {
152
+ if (calleeName !== "ta.highestbars" && calleeName !== "ta.lowestbars")
153
+ return 0;
154
+ const lengthArg = call.arguments[1];
155
+ if (lengthArg === undefined || !ts.isNumericLiteral(lengthArg))
156
+ return 0;
157
+ const length = Number(lengthArg.text);
158
+ if (!Number.isFinite(length) || length <= 1)
159
+ return 0;
160
+ return length - 1;
161
+ }
78
162
  function isSeriesShapedAccess(node, checker, seriesVarNames) {
79
163
  const expression = node.expression;
80
164
  if (ts.isPropertyAccessExpression(expression)) {
@@ -1 +1 @@
1
- {"version":3,"file":"extractMaxLookback.js","sourceRoot":"","sources":["../../src/analysis/extractMaxLookback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"extractMaxLookback.js","sourceRoot":"","sources":["../../src/analysis/extractMaxLookback.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAA0B,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEvF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAuBjF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,kBAAkB,CAC9B,UAAyB,EACzB,OAAuB,EACvB,UAAkB,EAClB,QAAiB,UAAU;IAE3B,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,gBAAgB,GAA2B,EAAE,CAAC;IACpD,MAAM,WAAW,GAAwB,EAAE,CAAC;IAE5C,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAE7D,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QAClC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACpD,IAAI,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,MAAM,SAAS,GAAG,0BAA0B,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBAC/D,IAAI,SAAS,GAAG,WAAW;oBAAE,WAAW,GAAG,SAAS,CAAC;YACzD,CAAC;YACD,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;gBACzC,IAAI,KAAK,GAAG,WAAW;oBAAE,WAAW,GAAG,KAAK,CAAC;YACjD,CAAC;QACL,CAAC;QACD,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC;gBACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC;gBACzC,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACxD,MAAM,KAAK,GAAG,sBAAsB,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC5E,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACjB,IAAI,KAAK,GAAG,WAAW;wBAAE,WAAW,GAAG,KAAK,CAAC;gBACjD,CAAC;qBAAM,CAAC;oBACJ,WAAW,CAAC,IAAI,CACZ,gBAAgB,CAAC;wBACb,QAAQ,EAAE,SAAS;wBACnB,IAAI,EAAE,sBAAsB;wBAC5B,OAAO,EACH,oFAAoF;wBACxF,IAAI,EAAE,UAAU;wBAChB,IAAI,EAAE,QAAQ;wBACd,UAAU;qBACb,CAAC,CACL,CAAC;oBACF,gBAAgB,CAAC,eAAe,GAAG,IAAI,CAAC;gBAC5C,CAAC;YACL,CAAC;QACL,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IACF,KAAK,CAAC,KAAK,CAAC,CAAC;IAEb,OAAO,MAAM,CAAC,MAAM,CAAC;QACjB,WAAW;QACX,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAAC;QACxD,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;KAClD,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,IAAuB;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACnC,OAAO,CACH,EAAE,CAAC,0BAA0B,CAAC,UAAU,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO;QAChC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC;QACtC,UAAU,CAAC,UAAU,CAAC,IAAI,KAAK,KAAK,CACvC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,oBAAoB,CAAC,IAAuB;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QACjF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc,EAAE,OAAuB;IAClE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QAClC,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;YACrC,IAAI,WAAW,IAAI,EAAE,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClD,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBAC3D,6DAA6D;gBAC7D,gEAAgE;gBAChE,gEAAgE;gBAChE,2DAA2D;gBAC3D,yDAAyD;gBACzD,iDAAiD;gBACjD,kCAAkC;gBAClC,IAAI,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,KAAK,cAAc,EAAE,CAAC;oBACjE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9B,CAAC;YACL,CAAC;QACL,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IACF,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,0BAA0B,CAAC,UAAkB,EAAE,IAAuB;IAC3E,IAAI,UAAU,KAAK,gBAAgB,IAAI,UAAU,KAAK,eAAe;QAAE,OAAO,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACtD,OAAO,MAAM,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,SAAS,oBAAoB,CACzB,IAAgC,EAChC,OAAuB,EACvB,cAAmC;IAEnC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACnC,IAAI,EAAE,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,IAAI,EAAE,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,iBAAiB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC1D,IAAI,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;IACnD,CAAC;IACD,IAAI,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACzD,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { resolveCalleeName } from \"../transformers/resolveCallee.js\";\nimport { unwrapParens } from \"./loopBounds.js\";\nimport { collectConstNumberEnv, resolveIndexUpperBound } from \"./resolveIndexBound.js\";\n\nconst OHLCV_FIELDS = new Set([\"close\", \"open\", \"high\", \"low\", \"volume\", \"time\"]);\n\n/**\n * Maximum literal lookback `N` discovered across every series read in the\n * source plus the inferred `seriesCapacities` record. `dynamicFallback`\n * captures the §6.6 contract: any non-literal series index contributes\n * `5000` so the runtime can size its ring buffers safely.\n *\n * @since 0.1\n * @example\n * const r: ExtractMaxLookbackResult = {\n * maxLookback: 20,\n * seriesCapacities: {},\n * diagnostics: [],\n * };\n * void r;\n */\nexport type ExtractMaxLookbackResult = Readonly<{\n maxLookback: number;\n seriesCapacities: Readonly<Record<string, number>>;\n diagnostics: ReadonlyArray<CompileDiagnostic>;\n}>;\n\n/**\n * Walk the source file's `ElementAccessExpression` nodes and infer\n * `maxLookback` plus any `dynamicFallback` capacity from non-literal index\n * reads on Phase-1 series shapes: `bar.<ohlcv>[N]`, `ta.<name>(...)[N]`,\n * and identifier-bound series variables (`const e = ta.ema(...); e[N];` or\n * `const s = state.series(...); s[N];`).\n *\n * The optional `scope` parameter narrows both the series-variable\n * collection and the lookback walk to a single AST subtree (typically\n * one binding's `defineCall`) so multi-export files derive per-binding\n * `maxLookback` values. Defaults to the whole `sourceFile`.\n *\n * @since 0.1\n * @example\n * // const { maxLookback, seriesCapacities, diagnostics } =\n * // extractMaxLookback(sourceFile, checker, \"demo.chart.ts\");\n * const fn: typeof extractMaxLookback = extractMaxLookback;\n * void fn;\n */\nexport function extractMaxLookback(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n sourcePath: string,\n scope: ts.Node = sourceFile,\n): ExtractMaxLookbackResult {\n let maxLookback = 0;\n const seriesCapacities: Record<string, number> = {};\n const diagnostics: CompileDiagnostic[] = [];\n\n const seriesVarNames = collectSeriesVarNames(scope, checker);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const calleeName = resolveCalleeName(node, checker);\n if (calleeName?.startsWith(\"ta.\")) {\n const barsDepth = readHighestLowestBarsDepth(calleeName, node);\n if (barsDepth > maxLookback) maxLookback = barsDepth;\n }\n if (isBarPointCall(node)) {\n const depth = readBarPointLookback(node);\n if (depth > maxLookback) maxLookback = depth;\n }\n }\n if (ts.isElementAccessExpression(node)) {\n if (isSeriesShapedAccess(node, checker, seriesVarNames)) {\n const argument = node.argumentExpression;\n const constEnv = collectConstNumberEnv(argument, scope);\n const bound = resolveIndexUpperBound(argument, node, { constEnv, checker });\n if (bound !== null) {\n if (bound > maxLookback) maxLookback = bound;\n } else {\n diagnostics.push(\n createDiagnostic({\n severity: \"warning\",\n code: \"dynamic-series-index\",\n message:\n \"Non-literal series index — runtime will use the 5000-slot dynamic fallback buffer.\",\n file: sourcePath,\n node: argument,\n sourceFile,\n }),\n );\n seriesCapacities.dynamicFallback = 5000;\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(scope);\n\n return Object.freeze({\n maxLookback,\n seriesCapacities: Object.freeze({ ...seriesCapacities }),\n diagnostics: Object.freeze(diagnostics.slice()),\n });\n}\n\n/**\n * Whether a call is a `bar.point(…)` invocation. Matched textually on the\n * `bar.point` property-access shape — the same OHLCV-style textual recognition\n * `isSeriesShapedAccess` uses — so it fires for both the destructured\n * `compute({ bar })` binding and a `declare const bar: Bar` test fixture.\n */\nfunction isBarPointCall(call: ts.CallExpression): boolean {\n const expression = call.expression;\n return (\n ts.isPropertyAccessExpression(expression) &&\n expression.name.text === \"point\" &&\n ts.isIdentifier(expression.expression) &&\n expression.expression.text === \"bar\"\n );\n}\n\n/**\n * The historical-lookback depth a `bar.point(offset, …)` call contributes,\n * or `0` when it reads the current / a future bar. A negative integer-literal\n * first argument (`bar.point(-N, …)` — or the converter's parenthesised\n * `bar.point(-(N), …)`) anchors `N` bars back, so the runtime's time ring\n * buffer must retain `N` extra slots — exactly like a `series[N]` lookback.\n * `bar.point(0, …)` (current) and positive offsets (future, extrapolated, no\n * buffer depth) contribute `0`; a non-literal / dynamic offset (e.g. a bound\n * `-k` or a computed `-(2 + 3)`) cannot be sized at compile time and also\n * contributes `0` (reads past retention degrade to a NaN time at runtime, per\n * `bar.point`'s contract).\n */\nfunction readBarPointLookback(call: ts.CallExpression): number {\n const first = call.arguments[0];\n if (first === undefined) return 0;\n const expr = unwrapParens(first);\n if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.MinusToken) {\n const operand = unwrapParens(expr.operand);\n if (ts.isNumericLiteral(operand)) {\n const n = Number(operand.text);\n if (Number.isFinite(n) && n > 0) return n;\n }\n }\n return 0;\n}\n\nfunction collectSeriesVarNames(scope: ts.Node, checker: ts.TypeChecker): ReadonlySet<string> {\n const names = new Set<string>();\n const visit = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const initializer = node.initializer;\n if (initializer && ts.isCallExpression(initializer)) {\n const calleeName = resolveCalleeName(initializer, checker);\n // A `state.series(...)`-bound variable is series-shaped just\n // like a `ta.*`-bound one: `s[N]` reads the slot's ring buffer,\n // so its literal index must fold into `maxLookback`. Matched on\n // the resolved callee name (the slot-injection path) so an\n // element-access form like `state[\"series\"](...)` is not\n // recognised — that form is rejected upstream as\n // `stateful-call-element-access`.\n if (calleeName?.startsWith(\"ta.\") || calleeName === \"state.series\") {\n names.add(node.name.text);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(scope);\n return names;\n}\n\n/**\n * The historical-lookback depth a `ta.highestbars` / `ta.lowestbars` call\n * contributes. Both primitives return the bar OFFSET (≤ 0) to the extreme\n * over the trailing `length`-bar window, so the deepest offset they can\n * return is `-(length − 1)`. A downstream `bar.point(<that offset>, …)`\n * anchor reads `time.at(length − 1)`, so the runtime's time ring buffer\n * must retain `length − 1` slots. Only a LITERAL second positional `length`\n * arg can be sized at compile time; a non-literal length contributes `0`.\n */\nfunction readHighestLowestBarsDepth(calleeName: string, call: ts.CallExpression): number {\n if (calleeName !== \"ta.highestbars\" && calleeName !== \"ta.lowestbars\") return 0;\n const lengthArg = call.arguments[1];\n if (lengthArg === undefined || !ts.isNumericLiteral(lengthArg)) return 0;\n const length = Number(lengthArg.text);\n if (!Number.isFinite(length) || length <= 1) return 0;\n return length - 1;\n}\n\nfunction isSeriesShapedAccess(\n node: ts.ElementAccessExpression,\n checker: ts.TypeChecker,\n seriesVarNames: ReadonlySet<string>,\n): boolean {\n const expression = node.expression;\n if (ts.isPropertyAccessExpression(expression)) {\n if (OHLCV_FIELDS.has(expression.name.text)) return true;\n }\n if (ts.isCallExpression(expression)) {\n const calleeName = resolveCalleeName(expression, checker);\n if (calleeName?.startsWith(\"ta.\")) return true;\n }\n if (ts.isIdentifier(expression)) {\n if (seriesVarNames.has(expression.text)) return true;\n }\n return false;\n}\n"]}
@@ -1,12 +1,54 @@
1
+ import type { SecurityExpressionDescriptor } from "@invinite-org/chartlang-core";
1
2
  import ts from "typescript";
2
3
  import { type CompileDiagnostic } from "../diagnostics.js";
3
4
  import type { ExtractedDescriptor } from "./extractInputs.js";
5
+ /**
6
+ * Combined result of the `request.*` analysis pass: the sorted, deduped list
7
+ * of requested intervals plus one {@link SecurityExpressionDescriptor} per
8
+ * `request.security({ interval }, (bar) => …)` expression callsite (sorted by
9
+ * `slotId`).
10
+ *
11
+ * @since 0.7
12
+ * @stable
13
+ * @example
14
+ * const r: RequestAnalysis = { intervals: ["1W"], securityExpressions: [] };
15
+ * void r;
16
+ */
17
+ export type RequestAnalysis = Readonly<{
18
+ intervals: ReadonlyArray<string>;
19
+ securityExpressions: ReadonlyArray<SecurityExpressionDescriptor>;
20
+ }>;
21
+ /**
22
+ * Walk a script's AST and collect every static `interval` argument to
23
+ * `request.security({ interval: ... })` and `request.lowerTf(...)`, plus every
24
+ * `request.security` *expression* callsite (a second arrow/function argument).
25
+ * Dynamic intervals emit `request-security-interval-not-literal` (for
26
+ * `request.security`) or `request-lower-tf-interval-not-literal` (for
27
+ * `request.lowerTf`) and are excluded.
28
+ *
29
+ * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}
30
+ * keyed by the same `slotId` the callsite-id transformer injects (via the
31
+ * shared `callsiteIdFor` helper) so the runtime can match the manifest entry
32
+ * to the inlined callback. When `validateExpressions` is `true`, each callback
33
+ * is also run through {@link validateSecurityExpr}, pushing
34
+ * `request-security-expr-captures-local` for any out-of-subset reference.
35
+ *
36
+ * @since 0.7
37
+ * @stable
38
+ * @example
39
+ * // const { intervals, securityExpressions } =
40
+ * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);
41
+ * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;
42
+ * void fn;
43
+ */
44
+ export declare function extractRequestAnalysis(sourceFile: ts.SourceFile, checker: ts.TypeChecker, inputs: Readonly<Record<string, ExtractedDescriptor>>, diagnostics: CompileDiagnostic[], sourcePath?: string, validateExpressions?: boolean): RequestAnalysis;
4
45
  /**
5
46
  * Walk a script's AST and collect every static `interval` argument to
6
47
  * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic
7
48
  * arguments emit `request-security-interval-not-literal` (for `request.security`)
8
49
  * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are
9
- * excluded.
50
+ * excluded. Thin delegate over {@link extractRequestAnalysis} kept for callers
51
+ * that only need the interval list.
10
52
  *
11
53
  * @since 0.4
12
54
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"extractRequestedIntervals.d.ts","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,GACzC,aAAa,CAAC,MAAM,CAAC,CAuBvB"}
1
+ {"version":3,"file":"extractRequestedIntervals.d.ts","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAG7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACnC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,mBAAmB,EAAE,aAAa,CAAC,4BAA4B,CAAC,CAAC;CACpE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,EACxC,mBAAmB,UAAQ,GAC5B,eAAe,CAuCjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,GACzC,aAAa,CAAC,MAAM,CAAC,CAEvB"}
@@ -2,34 +2,119 @@
2
2
  // See the LICENSE file in the repo root for full license text.
3
3
  import ts from "typescript";
4
4
  import { createDiagnostic } from "../diagnostics.js";
5
+ import { callsiteIdFor } from "../transformers/callsiteIdInjection.js";
5
6
  import { resolveCalleeName } from "../transformers/resolveCallee.js";
7
+ import { validateSecurityExpr } from "./validateSecurityExpr.js";
6
8
  /**
7
9
  * Walk a script's AST and collect every static `interval` argument to
8
- * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic
9
- * arguments emit `request-security-interval-not-literal` (for `request.security`)
10
- * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are
11
- * excluded.
10
+ * `request.security({ interval: ... })` and `request.lowerTf(...)`, plus every
11
+ * `request.security` *expression* callsite (a second arrow/function argument).
12
+ * Dynamic intervals emit `request-security-interval-not-literal` (for
13
+ * `request.security`) or `request-lower-tf-interval-not-literal` (for
14
+ * `request.lowerTf`) and are excluded.
12
15
  *
13
- * @since 0.4
16
+ * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}
17
+ * keyed by the same `slotId` the callsite-id transformer injects (via the
18
+ * shared `callsiteIdFor` helper) so the runtime can match the manifest entry
19
+ * to the inlined callback. When `validateExpressions` is `true`, each callback
20
+ * is also run through {@link validateSecurityExpr}, pushing
21
+ * `request-security-expr-captures-local` for any out-of-subset reference.
22
+ *
23
+ * @since 0.7
24
+ * @stable
14
25
  * @example
15
- * // const intervals = extractRequestedIntervals(sf, checker, inputs, diagnostics);
16
- * // intervals === ["1D", "5m"];
17
- * const fn: typeof extractRequestedIntervals = extractRequestedIntervals;
26
+ * // const { intervals, securityExpressions } =
27
+ * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);
28
+ * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;
18
29
  * void fn;
19
30
  */
20
- export function extractRequestedIntervals(sourceFile, checker, inputs, diagnostics, sourcePath = sourceFile.fileName) {
31
+ export function extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath = sourceFile.fileName, validateExpressions = false) {
21
32
  const intervals = new Set();
33
+ const securityExpressions = [];
22
34
  const visit = (node) => {
23
35
  if (ts.isCallExpression(node)) {
24
36
  const calleeName = resolveCalleeName(node, checker);
25
37
  if (calleeName === "request.security" || calleeName === "request.lowerTf") {
26
38
  readRequestInterval(node, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals);
27
39
  }
40
+ if (calleeName === "request.security") {
41
+ readSecurityExpression(node, sourceFile, sourcePath, checker, diagnostics, validateExpressions, securityExpressions);
42
+ }
28
43
  }
29
44
  ts.forEachChild(node, visit);
30
45
  };
31
46
  ts.forEachChild(sourceFile, visit);
32
- return Object.freeze(Array.from(intervals).sort());
47
+ securityExpressions.sort((a, b) => a.slotId.localeCompare(b.slotId));
48
+ return Object.freeze({
49
+ intervals: Object.freeze(Array.from(intervals).sort()),
50
+ securityExpressions: Object.freeze(securityExpressions.slice()),
51
+ });
52
+ }
53
+ /**
54
+ * Walk a script's AST and collect every static `interval` argument to
55
+ * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic
56
+ * arguments emit `request-security-interval-not-literal` (for `request.security`)
57
+ * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are
58
+ * excluded. Thin delegate over {@link extractRequestAnalysis} kept for callers
59
+ * that only need the interval list.
60
+ *
61
+ * @since 0.4
62
+ * @example
63
+ * // const intervals = extractRequestedIntervals(sf, checker, inputs, diagnostics);
64
+ * // intervals === ["1D", "5m"];
65
+ * const fn: typeof extractRequestedIntervals = extractRequestedIntervals;
66
+ * void fn;
67
+ */
68
+ export function extractRequestedIntervals(sourceFile, checker, inputs, diagnostics, sourcePath = sourceFile.fileName) {
69
+ return extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath).intervals;
70
+ }
71
+ /**
72
+ * Detect and record a `request.security` expression callsite — a second
73
+ * argument that is an arrow or function expression. Mints the descriptor's
74
+ * `slotId` via `callsiteIdFor` (lockstep with the injector), reads the literal
75
+ * `interval` and the callback's single parameter name, and — when
76
+ * `validate` — runs the capture check. A callsite whose interval is not a
77
+ * compile-time literal already emitted `request-security-interval-not-literal`
78
+ * via `readRequestInterval`; it is skipped here (no descriptor).
79
+ */
80
+ function readSecurityExpression(call, sourceFile, sourcePath, checker, diagnostics, validate, out) {
81
+ const callback = call.arguments[1];
82
+ if (callback === undefined ||
83
+ !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
84
+ return;
85
+ }
86
+ if (validate) {
87
+ validateSecurityExpr(callback, checker, diagnostics, sourcePath);
88
+ }
89
+ const interval = readLiteralInterval(call);
90
+ if (interval === null)
91
+ return;
92
+ const firstParam = callback.parameters[0];
93
+ const paramName = firstParam !== undefined && ts.isIdentifier(firstParam.name) ? firstParam.name.text : "";
94
+ out.push(Object.freeze({
95
+ slotId: callsiteIdFor(sourceFile, call, sourcePath),
96
+ interval,
97
+ paramName,
98
+ }));
99
+ }
100
+ /**
101
+ * Read the literal `interval` string off a `request.security` call's opts
102
+ * object, or `null` when it is absent or non-literal. Only string-literal
103
+ * intervals key an expression unit; an `input.enum` interval expands to
104
+ * multiple intervals for the requested-interval list but cannot anchor a
105
+ * single expression clock, so it is treated as non-literal here.
106
+ */
107
+ function readLiteralInterval(call) {
108
+ const opts = call.arguments[0];
109
+ if (opts === undefined || !ts.isObjectLiteralExpression(opts))
110
+ return null;
111
+ const intervalProperty = opts.properties
112
+ .filter(ts.isPropertyAssignment)
113
+ .find((property) => ts.isIdentifier(property.name) && property.name.text === "interval");
114
+ if (intervalProperty === undefined)
115
+ return null;
116
+ const initializer = intervalProperty.initializer;
117
+ return ts.isStringLiteral(initializer) ? initializer.text : null;
33
118
  }
34
119
  function readRequestInterval(call, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals) {
35
120
  const opts = call.arguments[0];
@@ -1 +1 @@
1
- {"version":3,"file":"extractRequestedIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"extractRequestedIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport type { SecurityExpressionDescriptor } from \"@invinite-org/chartlang-core\";\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { callsiteIdFor } from \"../transformers/callsiteIdInjection.js\";\nimport { resolveCalleeName } from \"../transformers/resolveCallee.js\";\nimport type { ExtractedDescriptor } from \"./extractInputs.js\";\nimport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\n\n/**\n * Combined result of the `request.*` analysis pass: the sorted, deduped list\n * of requested intervals plus one {@link SecurityExpressionDescriptor} per\n * `request.security({ interval }, (bar) => …)` expression callsite (sorted by\n * `slotId`).\n *\n * @since 0.7\n * @stable\n * @example\n * const r: RequestAnalysis = { intervals: [\"1W\"], securityExpressions: [] };\n * void r;\n */\nexport type RequestAnalysis = Readonly<{\n intervals: ReadonlyArray<string>;\n securityExpressions: ReadonlyArray<SecurityExpressionDescriptor>;\n}>;\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`, plus every\n * `request.security` *expression* callsite (a second arrow/function argument).\n * Dynamic intervals emit `request-security-interval-not-literal` (for\n * `request.security`) or `request-lower-tf-interval-not-literal` (for\n * `request.lowerTf`) and are excluded.\n *\n * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}\n * keyed by the same `slotId` the callsite-id transformer injects (via the\n * shared `callsiteIdFor` helper) so the runtime can match the manifest entry\n * to the inlined callback. When `validateExpressions` is `true`, each callback\n * is also run through {@link validateSecurityExpr}, pushing\n * `request-security-expr-captures-local` for any out-of-subset reference.\n *\n * @since 0.7\n * @stable\n * @example\n * // const { intervals, securityExpressions } =\n * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);\n * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;\n * void fn;\n */\nexport function extractRequestAnalysis(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n validateExpressions = false,\n): RequestAnalysis {\n const intervals = new Set<string>();\n const securityExpressions: SecurityExpressionDescriptor[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const calleeName = resolveCalleeName(node, checker);\n if (calleeName === \"request.security\" || calleeName === \"request.lowerTf\") {\n readRequestInterval(\n node,\n calleeName,\n sourceFile,\n sourcePath,\n inputs,\n diagnostics,\n intervals,\n );\n }\n if (calleeName === \"request.security\") {\n readSecurityExpression(\n node,\n sourceFile,\n sourcePath,\n checker,\n diagnostics,\n validateExpressions,\n securityExpressions,\n );\n }\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n securityExpressions.sort((a, b) => a.slotId.localeCompare(b.slotId));\n return Object.freeze({\n intervals: Object.freeze(Array.from(intervals).sort()),\n securityExpressions: Object.freeze(securityExpressions.slice()),\n });\n}\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic\n * arguments emit `request-security-interval-not-literal` (for `request.security`)\n * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are\n * excluded. Thin delegate over {@link extractRequestAnalysis} kept for callers\n * that only need the interval list.\n *\n * @since 0.4\n * @example\n * // const intervals = extractRequestedIntervals(sf, checker, inputs, diagnostics);\n * // intervals === [\"1D\", \"5m\"];\n * const fn: typeof extractRequestedIntervals = extractRequestedIntervals;\n * void fn;\n */\nexport function extractRequestedIntervals(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n): ReadonlyArray<string> {\n return extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath).intervals;\n}\n\n/**\n * Detect and record a `request.security` expression callsite — a second\n * argument that is an arrow or function expression. Mints the descriptor's\n * `slotId` via `callsiteIdFor` (lockstep with the injector), reads the literal\n * `interval` and the callback's single parameter name, and — when\n * `validate` — runs the capture check. A callsite whose interval is not a\n * compile-time literal already emitted `request-security-interval-not-literal`\n * via `readRequestInterval`; it is skipped here (no descriptor).\n */\nfunction readSecurityExpression(\n call: ts.CallExpression,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n checker: ts.TypeChecker,\n diagnostics: CompileDiagnostic[],\n validate: boolean,\n out: SecurityExpressionDescriptor[],\n): void {\n const callback = call.arguments[1];\n if (\n callback === undefined ||\n !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))\n ) {\n return;\n }\n if (validate) {\n validateSecurityExpr(callback, checker, diagnostics, sourcePath);\n }\n const interval = readLiteralInterval(call);\n if (interval === null) return;\n const firstParam = callback.parameters[0];\n const paramName =\n firstParam !== undefined && ts.isIdentifier(firstParam.name) ? firstParam.name.text : \"\";\n out.push(\n Object.freeze({\n slotId: callsiteIdFor(sourceFile, call, sourcePath),\n interval,\n paramName,\n }),\n );\n}\n\n/**\n * Read the literal `interval` string off a `request.security` call's opts\n * object, or `null` when it is absent or non-literal. Only string-literal\n * intervals key an expression unit; an `input.enum` interval expands to\n * multiple intervals for the requested-interval list but cannot anchor a\n * single expression clock, so it is treated as non-literal here.\n */\nfunction readLiteralInterval(call: ts.CallExpression): string | null {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return null;\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return null;\n const initializer = intervalProperty.initializer;\n return ts.isStringLiteral(initializer) ? initializer.text : null;\n}\n\nfunction readRequestInterval(\n call: ts.CallExpression,\n calleeName: \"request.security\" | \"request.lowerTf\",\n sourceFile: ts.SourceFile,\n sourcePath: string,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n intervals: Set<string>,\n): void {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return;\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return;\n\n const initializer = intervalProperty.initializer;\n if (ts.isStringLiteral(initializer)) {\n intervals.add(initializer.text);\n return;\n }\n\n const enumOptions = getInputsEnumOptions(initializer, inputs);\n if (enumOptions !== null) {\n for (const option of enumOptions) intervals.add(option);\n return;\n }\n\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code:\n calleeName === \"request.lowerTf\"\n ? \"request-lower-tf-interval-not-literal\"\n : \"request-security-interval-not-literal\",\n message: `${calleeName}({ interval }) must be a string literal or input.enum value`,\n file: sourcePath,\n node: initializer,\n sourceFile,\n }),\n );\n}\n\nfunction getInputsEnumOptions(\n expr: ts.Expression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): ReadonlyArray<string> | null {\n if (\n !ts.isPropertyAccessExpression(expr) ||\n !ts.isIdentifier(expr.expression) ||\n expr.expression.text !== \"inputs\"\n ) {\n return null;\n }\n const descriptor = inputs[expr.name.text];\n if (descriptor === undefined || descriptor.kind !== \"enum\") return null;\n const options = descriptor.options;\n if (!Array.isArray(options)) return null;\n const strings: string[] = [];\n for (const option of options) {\n if (typeof option !== \"string\") return null;\n strings.push(option);\n }\n return Object.freeze(strings);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"extractRequiresIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequiresIntervals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"extractRequiresIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequiresIntervals.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { resolveCalleeName } from \"../transformers/resolveCallee.js\";\n\nconst DEFINE_CALLS = new Set([\n \"defineIndicator\",\n \"defineAlert\",\n \"defineDrawing\",\n \"defineAlertCondition\",\n]);\n\n/**\n * Walk a script's default define call and collect a static\n * `requiresIntervals: [\"...\"]` array. Non-string entries emit\n * `requires-intervals-not-literal` and are excluded from the result.\n *\n * @since 0.4\n * @example\n * // const intervals = extractRequiresIntervals(sf, checker, diagnostics);\n * // intervals === [\"1D\", \"1W\"];\n * const fn: typeof extractRequiresIntervals = extractRequiresIntervals;\n * void fn;\n */\nexport function extractRequiresIntervals(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n): ReadonlyArray<string> {\n const intervals = new Set<string>();\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && isDefineCall(node, checker)) {\n readRequiresIntervals(node, sourceFile, sourcePath, diagnostics, intervals);\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return Object.freeze(Array.from(intervals).sort());\n}\n\nfunction isDefineCall(node: ts.CallExpression, checker: ts.TypeChecker): boolean {\n const calleeName = resolveCalleeName(node, checker);\n return calleeName !== null && DEFINE_CALLS.has(calleeName);\n}\n\nfunction readRequiresIntervals(\n call: ts.CallExpression,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n diagnostics: CompileDiagnostic[],\n intervals: Set<string>,\n): void {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return;\n const property = opts.properties\n .filter(ts.isPropertyAssignment)\n .find(\n (candidate) =>\n ts.isIdentifier(candidate.name) && candidate.name.text === \"requiresIntervals\",\n );\n if (property === undefined) return;\n\n if (!ts.isArrayLiteralExpression(property.initializer)) {\n addDiagnostic(property.initializer, sourceFile, sourcePath, diagnostics);\n return;\n }\n\n for (const element of property.initializer.elements) {\n if (ts.isStringLiteral(element)) {\n intervals.add(element.text);\n } else {\n addDiagnostic(element, sourceFile, sourcePath, diagnostics);\n }\n }\n}\n\nfunction addDiagnostic(\n node: ts.Node,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n diagnostics: CompileDiagnostic[],\n): void {\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code: \"requires-intervals-not-literal\",\n message: \"defineIndicator({ requiresIntervals }) must be a static string-literal array\",\n file: sourcePath,\n node,\n sourceFile,\n }),\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"forbiddenConstructs.d.ts","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAwB7E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,UAAU,EAAE,MAAM,GACnB,aAAa,CAAC,iBAAiB,CAAC,CAoLlC"}
1
+ {"version":3,"file":"forbiddenConstructs.d.ts","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAkB7E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,UAAU,EAAE,MAAM,GACnB,aAAa,CAAC,iBAAiB,CAAC,CA8JlC"}
@@ -2,6 +2,7 @@
2
2
  // See the LICENSE file in the repo root for full license text.
3
3
  import ts from "typescript";
4
4
  import { createDiagnostic } from "../diagnostics.js";
5
+ import { parseBoundedForLoop } from "./loopBounds.js";
5
6
  const HOSTILE_GLOBAL_NAMES = new Set([
6
7
  "fetch",
7
8
  "setTimeout",
@@ -16,12 +17,6 @@ const HOSTILE_GLOBAL_NAMES = new Set([
16
17
  // this helper; user scripts must not name-collide with the slot.
17
18
  "__chartlang_depOutput",
18
19
  ]);
19
- const COMPARISON_OPS = new Set([
20
- ts.SyntaxKind.LessThanToken,
21
- ts.SyntaxKind.LessThanEqualsToken,
22
- ts.SyntaxKind.GreaterThanToken,
23
- ts.SyntaxKind.GreaterThanEqualsToken,
24
- ]);
25
20
  /**
26
21
  * Walk the source file and emit a diagnostic for every forbidden construct:
27
22
  *
@@ -51,40 +46,6 @@ export function runForbiddenConstructs(sourceFile, sourcePath) {
51
46
  sourceFile,
52
47
  }));
53
48
  }
54
- function checkForStatement(node) {
55
- const init = node.initializer;
56
- const condition = node.condition;
57
- const incrementor = node.incrementor;
58
- if (!init || !condition || !incrementor)
59
- return false;
60
- if (!ts.isVariableDeclarationList(init))
61
- return false;
62
- if (init.declarations.length !== 1)
63
- return false;
64
- const declaration = init.declarations[0];
65
- if (!declaration || !ts.isIdentifier(declaration.name))
66
- return false;
67
- const initializer = declaration.initializer;
68
- if (!initializer || !ts.isNumericLiteral(initializer))
69
- return false;
70
- if (!ts.isBinaryExpression(condition))
71
- return false;
72
- if (!COMPARISON_OPS.has(condition.operatorToken.kind))
73
- return false;
74
- if (!ts.isNumericLiteral(condition.right))
75
- return false;
76
- if (!ts.isIdentifier(condition.left))
77
- return false;
78
- if (condition.left.text !== declaration.name.text)
79
- return false;
80
- if (!ts.isPostfixUnaryExpression(incrementor))
81
- return false;
82
- if (!ts.isIdentifier(incrementor.operand))
83
- return false;
84
- if (incrementor.operand.text !== declaration.name.text)
85
- return false;
86
- return true;
87
- }
88
49
  function isInsideAncestor(node, predicate) {
89
50
  let current = node.parent;
90
51
  while (current) {
@@ -140,7 +101,7 @@ export function runForbiddenConstructs(sourceFile, sourcePath) {
140
101
  emit(node, "unbounded-loop", "`for…in` loops are not allowed.");
141
102
  }
142
103
  else if (ts.isForStatement(node)) {
143
- if (!checkForStatement(node)) {
104
+ if (parseBoundedForLoop(node) === null) {
144
105
  emit(node, "unbounded-loop", "`for` loops must use literal numeric bounds: for (let i = <num>; i </<= <num>; i++).");
145
106
  }
146
107
  }
@@ -1 +1 @@
1
- {"version":3,"file":"forbiddenConstructs.js","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"forbiddenConstructs.js","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { parseBoundedForLoop } from \"./loopBounds.js\";\n\nconst HOSTILE_GLOBAL_NAMES = new Set([\n \"fetch\",\n \"setTimeout\",\n \"setInterval\",\n \"queueMicrotask\",\n \"Promise\",\n \"requestAnimationFrame\",\n \"Date\",\n \"eval\",\n \"require\",\n // Phase 7 — the indicator-composition rewriter synthesises calls to\n // this helper; user scripts must not name-collide with the slot.\n \"__chartlang_depOutput\",\n]);\n\n/**\n * Walk the source file and emit a diagnostic for every forbidden construct:\n *\n * - `while` / `do-while` / `for-of` / `for-in` / unbounded `for` →\n * `unbounded-loop`.\n * - Self-recursive function declaration → `recursion-not-allowed`.\n * - References to hostile globals (`Math.random`, `Date.*`, `fetch`,\n * `setTimeout`, `setInterval`, `queueMicrotask`, `Promise`,\n * `requestAnimationFrame`), plus `require(...)`, dynamic `import(...)`,\n * `eval(...)`, `new Function(...)` → `hostile-global`.\n *\n * @since 0.1\n * @example\n * // const diagnostics = runForbiddenConstructs(sourceFile, \"demo.chart.ts\");\n * const fn: typeof runForbiddenConstructs = runForbiddenConstructs;\n * void fn;\n */\nexport function runForbiddenConstructs(\n sourceFile: ts.SourceFile,\n sourcePath: string,\n): ReadonlyArray<CompileDiagnostic> {\n const diagnostics: CompileDiagnostic[] = [];\n\n function emit(\n node: ts.Node,\n code: \"unbounded-loop\" | \"recursion-not-allowed\" | \"hostile-global\",\n message: string,\n ): void {\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code,\n message,\n file: sourcePath,\n node,\n sourceFile,\n }),\n );\n }\n\n function isInsideAncestor(node: ts.Node, predicate: (parent: ts.Node) => boolean): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (predicate(current)) return true;\n current = current.parent;\n }\n return false;\n }\n\n function isDeclarationName(parent: ts.Node, node: ts.Identifier): boolean {\n const named = parent as { readonly name?: ts.Node };\n if (named.name !== node) return false;\n return (\n ts.isFunctionDeclaration(parent) ||\n ts.isFunctionExpression(parent) ||\n ts.isClassDeclaration(parent) ||\n ts.isClassExpression(parent) ||\n ts.isVariableDeclaration(parent) ||\n ts.isParameter(parent) ||\n ts.isInterfaceDeclaration(parent) ||\n ts.isTypeAliasDeclaration(parent) ||\n ts.isEnumDeclaration(parent) ||\n ts.isModuleDeclaration(parent)\n );\n }\n\n function enclosingFunctionName(node: ts.Node): string | null {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isFunctionDeclaration(current) && current.name) {\n return current.name.text;\n }\n if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {\n const initializer = current.initializer;\n if (\n initializer &&\n (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer))\n ) {\n return current.name.text;\n }\n }\n current = current.parent;\n }\n return null;\n }\n\n const visit = (node: ts.Node): void => {\n if (ts.isWhileStatement(node)) {\n emit(node, \"unbounded-loop\", \"`while` loops are not allowed.\");\n } else if (ts.isDoStatement(node)) {\n emit(node, \"unbounded-loop\", \"`do…while` loops are not allowed.\");\n } else if (ts.isForOfStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…of` loops are not allowed in Phase 1.\");\n } else if (ts.isForInStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…in` loops are not allowed.\");\n } else if (ts.isForStatement(node)) {\n if (parseBoundedForLoop(node) === null) {\n emit(\n node,\n \"unbounded-loop\",\n \"`for` loops must use literal numeric bounds: for (let i = <num>; i </<= <num>; i++).\",\n );\n }\n } else if (ts.isCallExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression)) {\n if (expression.text === \"eval\") {\n emit(node, \"hostile-global\", \"`eval` is not allowed.\");\n } else if (expression.text === \"require\") {\n emit(node, \"hostile-global\", \"`require` is not allowed.\");\n }\n } else if (expression.kind === ts.SyntaxKind.ImportKeyword) {\n emit(node, \"hostile-global\", \"Dynamic `import()` is not allowed.\");\n }\n const functionName = enclosingFunctionName(node);\n if (\n functionName !== null &&\n ts.isIdentifier(expression) &&\n expression.text === functionName\n ) {\n emit(\n node,\n \"recursion-not-allowed\",\n `Self-recursive call to \\`${functionName}\\` is not allowed.`,\n );\n }\n } else if (ts.isNewExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression) && expression.text === \"Function\") {\n emit(node, \"hostile-global\", \"`new Function(...)` is not allowed.\");\n }\n } else if (ts.isPropertyAccessExpression(node)) {\n const objectName = node.expression;\n if (ts.isIdentifier(objectName) && objectName.text === \"Math\") {\n if (node.name.text === \"random\") {\n emit(node, \"hostile-global\", \"`Math.random` is not allowed.\");\n }\n } else if (ts.isIdentifier(objectName) && objectName.text === \"Date\") {\n emit(node, \"hostile-global\", \"`Date.*` is not allowed.\");\n }\n } else if (ts.isIdentifier(node)) {\n if (!HOSTILE_GLOBAL_NAMES.has(node.text)) {\n ts.forEachChild(node, visit);\n return;\n }\n const parent = node.parent;\n if (parent && isDeclarationName(parent, node)) {\n ts.forEachChild(node, visit);\n return;\n }\n if (\n parent &&\n (ts.isPropertyAccessExpression(parent) ||\n ts.isPropertyAssignment(parent) ||\n ts.isPropertySignature(parent) ||\n ts.isBindingElement(parent) ||\n ts.isImportSpecifier(parent) ||\n ts.isExportSpecifier(parent))\n ) {\n ts.forEachChild(node, visit);\n return;\n }\n const isInsideTypeContext = isInsideAncestor(\n node,\n (ancestor) => ts.isTypeNode(ancestor) || ts.isTypeReferenceNode(ancestor),\n );\n if (isInsideTypeContext) {\n ts.forEachChild(node, visit);\n return;\n }\n emit(node, \"hostile-global\", `\\`${node.text}\\` is not allowed.`);\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return Object.freeze(diagnostics.slice());\n}\n"]}
@@ -7,7 +7,9 @@ export { extractMaxLookback } from "./extractMaxLookback.js";
7
7
  export type { ExtractMaxLookbackResult } from "./extractMaxLookback.js";
8
8
  export { extractInputs } from "./extractInputs.js";
9
9
  export type { ExtractedDescriptor, ExtractInputsResult } from "./extractInputs.js";
10
- export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
10
+ export { extractRequestAnalysis, extractRequestedIntervals } from "./extractRequestedIntervals.js";
11
+ export type { RequestAnalysis } from "./extractRequestedIntervals.js";
12
+ export { validateSecurityExpr } from "./validateSecurityExpr.js";
11
13
  export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
12
14
  export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
13
15
  export { extractAlertConditions } from "./extractAlertConditions.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACR,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,GAClB,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACR,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,GAClB,MAAM,6BAA6B,CAAC"}
@@ -6,7 +6,8 @@ export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
6
6
  export { extractCapabilities } from "./extractCapabilities.js";
7
7
  export { extractMaxLookback } from "./extractMaxLookback.js";
8
8
  export { extractInputs } from "./extractInputs.js";
9
- export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
9
+ export { extractRequestAnalysis, extractRequestedIntervals } from "./extractRequestedIntervals.js";
10
+ export { validateSecurityExpr } from "./validateSecurityExpr.js";
10
11
  export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
11
12
  export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
12
13
  export { extractAlertConditions } from "./extractAlertConditions.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nexport { runStructuralChecks } from \"./structuralChecks.js\";\nexport type { StructuralBindingInfo, StructuralCheckResult } from \"./structuralChecks.js\";\nexport { runForbiddenConstructs } from \"./forbiddenConstructs.js\";\nexport { runStatefulCallInLoop } from \"./statefulCallInLoop.js\";\nexport { extractCapabilities } from \"./extractCapabilities.js\";\nexport { extractMaxLookback } from \"./extractMaxLookback.js\";\nexport type { ExtractMaxLookbackResult } from \"./extractMaxLookback.js\";\nexport { extractInputs } from \"./extractInputs.js\";\nexport type { ExtractedDescriptor, ExtractInputsResult } from \"./extractInputs.js\";\nexport { extractRequestAnalysis, extractRequestedIntervals } from \"./extractRequestedIntervals.js\";\nexport type { RequestAnalysis } from \"./extractRequestedIntervals.js\";\nexport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\nexport { validateLowerTfIntervals } from \"./validateLowerTfIntervals.js\";\nexport { extractRequiresIntervals } from \"./extractRequiresIntervals.js\";\nexport { extractAlertConditions } from \"./extractAlertConditions.js\";\nexport type { ExtractAlertConditionsResult } from \"./extractAlertConditions.js\";\nexport { extractDependencyGraph } from \"./extractDependencyGraph.js\";\nexport type {\n DepConsumesEntry,\n DepGraph,\n DrawnScript,\n PrivateDep,\n ProducerRef,\n ProducerSnapshot,\n ResolveProducer,\n} from \"./extractDependencyGraph.js\";\n"]}