@invinite-org/chartlang-compiler 1.0.1

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 (88) hide show
  1. package/CHANGELOG.md +683 -0
  2. package/LICENSE +21 -0
  3. package/README.md +53 -0
  4. package/dist/analysis/extractAlertConditions.d.ts +33 -0
  5. package/dist/analysis/extractAlertConditions.d.ts.map +1 -0
  6. package/dist/analysis/extractAlertConditions.js +118 -0
  7. package/dist/analysis/extractAlertConditions.js.map +1 -0
  8. package/dist/analysis/extractCapabilities.d.ts +22 -0
  9. package/dist/analysis/extractCapabilities.d.ts.map +1 -0
  10. package/dist/analysis/extractCapabilities.js +44 -0
  11. package/dist/analysis/extractCapabilities.js.map +1 -0
  12. package/dist/analysis/extractInputs.d.ts +44 -0
  13. package/dist/analysis/extractInputs.d.ts.map +1 -0
  14. package/dist/analysis/extractInputs.js +306 -0
  15. package/dist/analysis/extractInputs.js.map +1 -0
  16. package/dist/analysis/extractMaxLookback.d.ts +37 -0
  17. package/dist/analysis/extractMaxLookback.d.ts.map +1 -0
  18. package/dist/analysis/extractMaxLookback.js +90 -0
  19. package/dist/analysis/extractMaxLookback.js.map +1 -0
  20. package/dist/analysis/extractRequestedIntervals.d.ts +19 -0
  21. package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -0
  22. package/dist/analysis/extractRequestedIntervals.js +85 -0
  23. package/dist/analysis/extractRequestedIntervals.js.map +1 -0
  24. package/dist/analysis/extractRequiresIntervals.d.ts +16 -0
  25. package/dist/analysis/extractRequiresIntervals.d.ts.map +1 -0
  26. package/dist/analysis/extractRequiresIntervals.js +71 -0
  27. package/dist/analysis/extractRequiresIntervals.js.map +1 -0
  28. package/dist/analysis/forbiddenConstructs.d.ts +22 -0
  29. package/dist/analysis/forbiddenConstructs.d.ts.map +1 -0
  30. package/dist/analysis/forbiddenConstructs.js +214 -0
  31. package/dist/analysis/forbiddenConstructs.js.map +1 -0
  32. package/dist/analysis/index.d.ts +15 -0
  33. package/dist/analysis/index.d.ts.map +1 -0
  34. package/dist/analysis/index.js +13 -0
  35. package/dist/analysis/index.js.map +1 -0
  36. package/dist/analysis/statefulCallInLoop.d.ts +26 -0
  37. package/dist/analysis/statefulCallInLoop.d.ts.map +1 -0
  38. package/dist/analysis/statefulCallInLoop.js +64 -0
  39. package/dist/analysis/statefulCallInLoop.js.map +1 -0
  40. package/dist/analysis/structuralChecks.d.ts +73 -0
  41. package/dist/analysis/structuralChecks.d.ts.map +1 -0
  42. package/dist/analysis/structuralChecks.js +243 -0
  43. package/dist/analysis/structuralChecks.js.map +1 -0
  44. package/dist/analysis/validateLowerTfIntervals.d.ts +26 -0
  45. package/dist/analysis/validateLowerTfIntervals.d.ts.map +1 -0
  46. package/dist/analysis/validateLowerTfIntervals.js +91 -0
  47. package/dist/analysis/validateLowerTfIntervals.js.map +1 -0
  48. package/dist/api.d.ts +205 -0
  49. package/dist/api.d.ts.map +1 -0
  50. package/dist/api.js +354 -0
  51. package/dist/api.js.map +1 -0
  52. package/dist/bundle.d.ts +75 -0
  53. package/dist/bundle.d.ts.map +1 -0
  54. package/dist/bundle.js +90 -0
  55. package/dist/bundle.js.map +1 -0
  56. package/dist/diagnostics.d.ts +88 -0
  57. package/dist/diagnostics.d.ts.map +1 -0
  58. package/dist/diagnostics.js +95 -0
  59. package/dist/diagnostics.js.map +1 -0
  60. package/dist/index.d.ts +9 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +7 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/manifest.d.ts +40 -0
  65. package/dist/manifest.d.ts.map +1 -0
  66. package/dist/manifest.js +57 -0
  67. package/dist/manifest.js.map +1 -0
  68. package/dist/program.d.ts +68 -0
  69. package/dist/program.d.ts.map +1 -0
  70. package/dist/program.js +1391 -0
  71. package/dist/program.js.map +1 -0
  72. package/dist/transformers/callsiteIdInjection.d.ts +48 -0
  73. package/dist/transformers/callsiteIdInjection.d.ts.map +1 -0
  74. package/dist/transformers/callsiteIdInjection.js +91 -0
  75. package/dist/transformers/callsiteIdInjection.js.map +1 -0
  76. package/dist/transformers/index.d.ts +4 -0
  77. package/dist/transformers/index.d.ts.map +1 -0
  78. package/dist/transformers/index.js +5 -0
  79. package/dist/transformers/index.js.map +1 -0
  80. package/dist/transformers/resolveCallee.d.ts +39 -0
  81. package/dist/transformers/resolveCallee.d.ts.map +1 -0
  82. package/dist/transformers/resolveCallee.js +136 -0
  83. package/dist/transformers/resolveCallee.js.map +1 -0
  84. package/dist/typesEmit.d.ts +35 -0
  85. package/dist/typesEmit.d.ts.map +1 -0
  86. package/dist/typesEmit.js +27 -0
  87. package/dist/typesEmit.js.map +1 -0
  88. package/package.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Invinite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @invinite-org/chartlang-compiler
2
+
3
+ `experimental`
4
+
5
+ TypeScript transformer + bundler for `.chart.ts` files. It builds deterministic
6
+ programs, rejects unsupported language constructs, injects slot ids, extracts
7
+ manifests, bundles ESM, and emits declaration siblings.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @invinite-org/chartlang-compiler
13
+ ```
14
+
15
+ ## Public surface
16
+
17
+ - `compile(source, opts)` — compile a script string to module source,
18
+ sourcemap, manifest, and `.d.ts` text.
19
+ - `compileFile(path, opts)` — compile one `.chart.ts` and atomically write
20
+ `<base>.chart.js`, `<base>.chart.manifest.json`, and `<base>.chart.d.ts`.
21
+ - `compileProject(rootDir, opts)` — path-sorted in-memory project compile.
22
+ - `transformAndAnalyse(source, opts)` — run AST passes without bundling.
23
+ - Extractors: `extractCapabilities`, `extractInputs`,
24
+ `extractRequestedIntervals`, `extractRequiresIntervals`, and max-lookback /
25
+ drawing-budget helpers.
26
+ - Errors and types: `CompileError`, `CompileDiagnostic`,
27
+ `CompileDiagnosticCode`, `CompiledScript`, `CompileOptions`,
28
+ `CompileFileOptions`, `BundleModuleOptions`, `EmitTypesOptions`.
29
+ - Phase 4 diagnostics include `input-call-not-literal`,
30
+ `input-schema-not-literal`, `requires-intervals-not-literal`, and
31
+ `request-security-interval-not-literal`.
32
+
33
+ ## Minimum-viable API call
34
+
35
+ ```ts
36
+ import { compile } from "@invinite-org/chartlang-compiler";
37
+
38
+ const result = await compile(source, {
39
+ apiVersion: 1,
40
+ sourcePath: "indicator.chart.ts",
41
+ });
42
+
43
+ console.log(result.manifest.inputs);
44
+ console.log(result.manifest.requestedIntervals);
45
+ ```
46
+
47
+ ## Docs
48
+
49
+ See [`docs/spec/grammar.md`](../../docs/spec/grammar.md).
50
+
51
+ ## License
52
+
53
+ MIT
@@ -0,0 +1,33 @@
1
+ import type { AlertConditionDefinition } from "@invinite-org/chartlang-core";
2
+ import ts from "typescript";
3
+ import { type CompileDiagnostic } from "../diagnostics.js";
4
+ /**
5
+ * Result of extracting `defineAlertCondition({ conditions })` metadata.
6
+ *
7
+ * @since 0.5
8
+ * @stable
9
+ * @example
10
+ * const r: ExtractAlertConditionsResult = {
11
+ * alertConditions: [],
12
+ * diagnostics: [],
13
+ * };
14
+ * void r;
15
+ */
16
+ export type ExtractAlertConditionsResult = Readonly<{
17
+ alertConditions: ReadonlyArray<AlertConditionDefinition>;
18
+ diagnostics: ReadonlyArray<CompileDiagnostic>;
19
+ }>;
20
+ /**
21
+ * Extract literal `defineAlertCondition({ conditions: { ... } })`
22
+ * descriptors into manifest-ready metadata. Dynamic condition maps or
23
+ * dynamic descriptor fields produce error diagnostics and are omitted.
24
+ *
25
+ * @since 0.5
26
+ * @stable
27
+ * @example
28
+ * // const r = extractAlertConditions(sourceFile, checker, "demo.chart.ts");
29
+ * const fn: typeof extractAlertConditions = extractAlertConditions;
30
+ * void fn;
31
+ */
32
+ export declare function extractAlertConditions(sourceFile: ts.SourceFile, checker: ts.TypeChecker, sourcePath?: string): ExtractAlertConditionsResult;
33
+ //# sourceMappingURL=extractAlertConditions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractAlertConditions.d.ts","sourceRoot":"","sources":["../../src/analysis/extractAlertConditions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAG7E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAChD,eAAe,EAAE,aAAa,CAAC,wBAAwB,CAAC,CAAC;IACzD,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACjD,CAAC,CAAC;AAkFH;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,UAAU,GAAE,MAA4B,GACzC,4BAA4B,CAuC9B"}
@@ -0,0 +1,118 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ import { createDiagnostic } from "../diagnostics.js";
5
+ import { resolveCalleeName } from "../transformers/resolveCallee.js";
6
+ function readPropertyName(name) {
7
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
8
+ return name.text;
9
+ }
10
+ return null;
11
+ }
12
+ function findConditionsInitializer(argument) {
13
+ for (const property of argument.properties) {
14
+ if (!ts.isPropertyAssignment(property))
15
+ continue;
16
+ if (!ts.isIdentifier(property.name) || property.name.text !== "conditions")
17
+ continue;
18
+ return property.initializer;
19
+ }
20
+ return null;
21
+ }
22
+ function addNotLiteralDiagnostic(node, context) {
23
+ context.diagnostics.push(createDiagnostic({
24
+ severity: "error",
25
+ code: "alert-condition-not-literal",
26
+ message: "defineAlertCondition conditions must be an object literal.",
27
+ file: context.sourcePath,
28
+ node,
29
+ sourceFile: context.sourceFile,
30
+ }));
31
+ }
32
+ function addFieldDiagnostic(node, field, context) {
33
+ context.diagnostics.push(createDiagnostic({
34
+ severity: "error",
35
+ code: "alert-condition-field-not-literal",
36
+ message: `defineAlertCondition condition field "${field}" must be a string literal.`,
37
+ file: context.sourcePath,
38
+ node,
39
+ sourceFile: context.sourceFile,
40
+ }));
41
+ }
42
+ function readStringField(object, field, context) {
43
+ for (const property of object.properties) {
44
+ if (!ts.isPropertyAssignment(property))
45
+ continue;
46
+ if (!ts.isIdentifier(property.name) || property.name.text !== field)
47
+ continue;
48
+ if (ts.isStringLiteral(property.initializer))
49
+ return property.initializer.text;
50
+ addFieldDiagnostic(property.initializer, field, context);
51
+ return null;
52
+ }
53
+ addFieldDiagnostic(object, field, context);
54
+ return null;
55
+ }
56
+ function readCondition(id, initializer, context) {
57
+ if (!ts.isObjectLiteralExpression(initializer)) {
58
+ addFieldDiagnostic(initializer, id, context);
59
+ return null;
60
+ }
61
+ const title = readStringField(initializer, "title", context);
62
+ const description = readStringField(initializer, "description", context);
63
+ const defaultMessage = readStringField(initializer, "defaultMessage", context);
64
+ if (title === null || description === null || defaultMessage === null)
65
+ return null;
66
+ return Object.freeze({ id, title, description, defaultMessage });
67
+ }
68
+ /**
69
+ * Extract literal `defineAlertCondition({ conditions: { ... } })`
70
+ * descriptors into manifest-ready metadata. Dynamic condition maps or
71
+ * dynamic descriptor fields produce error diagnostics and are omitted.
72
+ *
73
+ * @since 0.5
74
+ * @stable
75
+ * @example
76
+ * // const r = extractAlertConditions(sourceFile, checker, "demo.chart.ts");
77
+ * const fn: typeof extractAlertConditions = extractAlertConditions;
78
+ * void fn;
79
+ */
80
+ export function extractAlertConditions(sourceFile, checker, sourcePath = sourceFile.fileName) {
81
+ const alertConditions = [];
82
+ const diagnostics = [];
83
+ const context = { sourceFile, sourcePath, diagnostics };
84
+ const visit = (node) => {
85
+ if (ts.isCallExpression(node) &&
86
+ resolveCalleeName(node, checker) === "defineAlertCondition") {
87
+ const argument = node.arguments[0];
88
+ if (argument === undefined || !ts.isObjectLiteralExpression(argument)) {
89
+ addNotLiteralDiagnostic(node, context);
90
+ return;
91
+ }
92
+ const initializer = findConditionsInitializer(argument);
93
+ if (initializer === null || !ts.isObjectLiteralExpression(initializer)) {
94
+ addNotLiteralDiagnostic(initializer ?? argument, context);
95
+ return;
96
+ }
97
+ for (const property of initializer.properties) {
98
+ if (!ts.isPropertyAssignment(property))
99
+ continue;
100
+ const id = readPropertyName(property.name);
101
+ if (id === null) {
102
+ addNotLiteralDiagnostic(property.name, context);
103
+ continue;
104
+ }
105
+ const condition = readCondition(id, property.initializer, context);
106
+ if (condition !== null)
107
+ alertConditions.push(condition);
108
+ }
109
+ }
110
+ ts.forEachChild(node, visit);
111
+ };
112
+ ts.forEachChild(sourceFile, visit);
113
+ return Object.freeze({
114
+ alertConditions: Object.freeze(alertConditions.slice()),
115
+ diagnostics: Object.freeze(diagnostics.slice()),
116
+ });
117
+ }
118
+ //# sourceMappingURL=extractAlertConditions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractAlertConditions.js","sourceRoot":"","sources":["../../src/analysis/extractAlertConditions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import ts from "typescript";
2
+ type CapabilityId = "indicators" | "drawings" | "alerts" | "alertConditions";
3
+ /**
4
+ * Derive the manifest `capabilities` array from a script's AST. The seed
5
+ * capability is determined by the structural script `kind`:
6
+ * `defineIndicator` → `"indicators"`, `defineDrawing` → `"drawings"`,
7
+ * `defineAlert` → `"alerts"`. `"alerts"` is added in addition whenever the
8
+ * script calls `alert(...)` from `@invinite-org/chartlang-core` —
9
+ * user-shadowed identifiers named `alert` are filtered out via
10
+ * `resolveCalleeName`. The result is deduplicated and sorted for
11
+ * deterministic manifest output.
12
+ *
13
+ * @since 0.1
14
+ * @example
15
+ * // const caps = extractCapabilities(sourceFile, checker, "indicator");
16
+ * // caps === ["alerts", "indicators"]
17
+ * const fn: typeof extractCapabilities = extractCapabilities;
18
+ * void fn;
19
+ */
20
+ export declare function extractCapabilities(sourceFile: ts.SourceFile, checker: ts.TypeChecker, kind?: "indicator" | "drawing" | "alert" | "alertCondition"): ReadonlyArray<CapabilityId>;
21
+ export {};
22
+ //# sourceMappingURL=extractCapabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractCapabilities.d.ts","sourceRoot":"","sources":["../../src/analysis/extractCapabilities.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAI5B,KAAK,YAAY,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,iBAAiB,CAAC;AAE7E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,IAAI,GAAE,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,gBAA8B,GACzE,aAAa,CAAC,YAAY,CAAC,CAyB7B"}
@@ -0,0 +1,44 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ import { resolveCalleeName } from "../transformers/resolveCallee.js";
5
+ /**
6
+ * Derive the manifest `capabilities` array from a script's AST. The seed
7
+ * capability is determined by the structural script `kind`:
8
+ * `defineIndicator` → `"indicators"`, `defineDrawing` → `"drawings"`,
9
+ * `defineAlert` → `"alerts"`. `"alerts"` is added in addition whenever the
10
+ * script calls `alert(...)` from `@invinite-org/chartlang-core` —
11
+ * user-shadowed identifiers named `alert` are filtered out via
12
+ * `resolveCalleeName`. The result is deduplicated and sorted for
13
+ * deterministic manifest output.
14
+ *
15
+ * @since 0.1
16
+ * @example
17
+ * // const caps = extractCapabilities(sourceFile, checker, "indicator");
18
+ * // caps === ["alerts", "indicators"]
19
+ * const fn: typeof extractCapabilities = extractCapabilities;
20
+ * void fn;
21
+ */
22
+ export function extractCapabilities(sourceFile, checker, kind = "indicator") {
23
+ const SEED_BY_KIND = {
24
+ indicator: "indicators",
25
+ drawing: "drawings",
26
+ alert: "alerts",
27
+ alertCondition: "alertConditions",
28
+ };
29
+ const seed = SEED_BY_KIND[kind];
30
+ const found = new Set([seed]);
31
+ const visit = (node) => {
32
+ if (ts.isCallExpression(node)) {
33
+ const calleeName = resolveCalleeName(node, checker);
34
+ if (calleeName === "alert") {
35
+ found.add("alerts");
36
+ }
37
+ }
38
+ ts.forEachChild(node, visit);
39
+ };
40
+ ts.forEachChild(sourceFile, visit);
41
+ const ordered = Array.from(found).sort();
42
+ return Object.freeze(ordered);
43
+ }
44
+ //# sourceMappingURL=extractCapabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractCapabilities.js","sourceRoot":"","sources":["../../src/analysis/extractCapabilities.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAIrE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CAC/B,UAAyB,EACzB,OAAuB,EACvB,OAA6D,WAAW;IAExE,MAAM,YAAY,GAEd;QACA,SAAS,EAAE,YAAY;QACvB,OAAO,EAAE,UAAU;QACnB,KAAK,EAAE,QAAQ;QACf,cAAc,EAAE,iBAAiB;KACpC,CAAC;IACF,MAAM,IAAI,GAAiB,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5C,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,KAAK,OAAO,EAAE,CAAC;gBACzB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IACF,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,44 @@
1
+ import ts from "typescript";
2
+ import { type CompileDiagnostic } from "../diagnostics.js";
3
+ /**
4
+ * Frozen, JSON-clean input descriptor extracted from a script's
5
+ * `define*({ inputs })` object.
6
+ *
7
+ * @since 0.4
8
+ * @example
9
+ * const d: ExtractedDescriptor = { kind: "int", defaultValue: 14 };
10
+ * void d;
11
+ */
12
+ export type ExtractedDescriptor = Readonly<Record<string, unknown>>;
13
+ /**
14
+ * Result of input extraction. Diagnostics are hard errors when the
15
+ * declaration uses an unknown builder or non-literal descriptor values.
16
+ *
17
+ * @since 0.4
18
+ * @example
19
+ * const r: ExtractInputsResult = {
20
+ * inputs: { length: { kind: "int", defaultValue: 14 } },
21
+ * userPickableInterval: false,
22
+ * diagnostics: [],
23
+ * };
24
+ * void r;
25
+ */
26
+ export type ExtractInputsResult = Readonly<{
27
+ inputs: Readonly<Record<string, ExtractedDescriptor>>;
28
+ userPickableInterval: boolean;
29
+ diagnostics: ReadonlyArray<CompileDiagnostic>;
30
+ }>;
31
+ /**
32
+ * Walk a script's AST and serialise every `input.*` call inside
33
+ * `defineIndicator({ inputs: { ... } })`, `defineAlert`, or
34
+ * `defineDrawing` into the manifest's `inputs` record.
35
+ *
36
+ * @since 0.4
37
+ * @example
38
+ * // const r = extractInputs(sourceFile, checker, "demo.chart.ts");
39
+ * // r.inputs.length === { kind: "int", defaultValue: 14 };
40
+ * const fn: typeof extractInputs = extractInputs;
41
+ * void fn;
42
+ */
43
+ export declare function extractInputs(sourceFile: ts.SourceFile, checker: ts.TypeChecker, sourcePath?: string): ExtractInputsResult;
44
+ //# sourceMappingURL=extractInputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractInputs.d.ts","sourceRoot":"","sources":["../../src/analysis/extractInputs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AA0C7E;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACvC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACtD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACjD,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CACzB,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,UAAU,GAAE,MAA4B,GACzC,mBAAmB,CAwErB"}
@@ -0,0 +1,306 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ import { createDiagnostic } from "../diagnostics.js";
5
+ import { resolveCalleeName } from "../transformers/resolveCallee.js";
6
+ const DEFINE_CALLS = new Set([
7
+ "defineIndicator",
8
+ "defineAlert",
9
+ "defineDrawing",
10
+ "defineAlertCondition",
11
+ ]);
12
+ /** Names the walker recognises as `input.*` calls. */
13
+ const INPUT_KINDS = new Set([
14
+ "int",
15
+ "float",
16
+ "bool",
17
+ "string",
18
+ "enum",
19
+ "color",
20
+ "source",
21
+ "time",
22
+ "price",
23
+ "symbol",
24
+ "interval",
25
+ "externalSeries",
26
+ ]);
27
+ /** Wire-tag mapping — camelCase builder names become kebab-case manifest tags. */
28
+ const KIND_TO_WIRE = Object.freeze({
29
+ int: "int",
30
+ float: "float",
31
+ bool: "bool",
32
+ string: "string",
33
+ enum: "enum",
34
+ color: "color",
35
+ source: "source",
36
+ time: "time",
37
+ price: "price",
38
+ symbol: "symbol",
39
+ interval: "interval",
40
+ externalSeries: "external-series",
41
+ });
42
+ /**
43
+ * Walk a script's AST and serialise every `input.*` call inside
44
+ * `defineIndicator({ inputs: { ... } })`, `defineAlert`, or
45
+ * `defineDrawing` into the manifest's `inputs` record.
46
+ *
47
+ * @since 0.4
48
+ * @example
49
+ * // const r = extractInputs(sourceFile, checker, "demo.chart.ts");
50
+ * // r.inputs.length === { kind: "int", defaultValue: 14 };
51
+ * const fn: typeof extractInputs = extractInputs;
52
+ * void fn;
53
+ */
54
+ export function extractInputs(sourceFile, checker, sourcePath = sourceFile.fileName) {
55
+ const inputs = {};
56
+ const diagnostics = [];
57
+ let userPickableInterval = false;
58
+ let intervalCount = 0;
59
+ const visit = (node) => {
60
+ if (ts.isCallExpression(node) && isDefineCall(node, checker)) {
61
+ const inputsObject = readInputsArg(node);
62
+ if (inputsObject !== null) {
63
+ for (const property of inputsObject.properties) {
64
+ if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
65
+ continue;
66
+ }
67
+ const initializer = property.initializer;
68
+ if (!ts.isCallExpression(initializer))
69
+ continue;
70
+ const callee = resolveCalleeName(initializer, checker);
71
+ if (callee === null || !callee.startsWith("input."))
72
+ continue;
73
+ const kind = callee.slice("input.".length);
74
+ if (!INPUT_KINDS.has(kind)) {
75
+ diagnostics.push(createDiagnostic({
76
+ severity: "error",
77
+ code: "unknown-input-kind",
78
+ message: `input.${kind} is not a recognised input builder`,
79
+ file: sourcePath,
80
+ node: initializer.expression,
81
+ sourceFile,
82
+ }));
83
+ continue;
84
+ }
85
+ const wireKind = KIND_TO_WIRE[kind];
86
+ if (wireKind === "interval") {
87
+ intervalCount += 1;
88
+ userPickableInterval = true;
89
+ if (intervalCount > 1) {
90
+ diagnostics.push(createDiagnostic({
91
+ severity: "error",
92
+ code: "multiple-input-interval",
93
+ message: "Only one input.interval() per script (PLAN §4.5)",
94
+ file: sourcePath,
95
+ node: initializer.expression,
96
+ sourceFile,
97
+ }));
98
+ }
99
+ }
100
+ const descriptor = serialiseDescriptor(wireKind, kind, initializer, {
101
+ sourceFile,
102
+ sourcePath,
103
+ diagnostics,
104
+ });
105
+ if (descriptor !== null) {
106
+ inputs[property.name.text] = descriptor;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ts.forEachChild(node, visit);
112
+ };
113
+ ts.forEachChild(sourceFile, visit);
114
+ return Object.freeze({
115
+ inputs: Object.freeze({ ...inputs }),
116
+ userPickableInterval,
117
+ diagnostics: Object.freeze(diagnostics.slice()),
118
+ });
119
+ }
120
+ function isDefineCall(node, checker) {
121
+ const calleeName = resolveCalleeName(node, checker);
122
+ return calleeName !== null && DEFINE_CALLS.has(calleeName);
123
+ }
124
+ function readInputsArg(node) {
125
+ const argument = node.arguments[0];
126
+ if (argument === undefined || !ts.isObjectLiteralExpression(argument))
127
+ return null;
128
+ for (const property of argument.properties) {
129
+ if (!ts.isPropertyAssignment(property))
130
+ continue;
131
+ if (!ts.isIdentifier(property.name) || property.name.text !== "inputs")
132
+ continue;
133
+ const initializer = property.initializer;
134
+ if (ts.isObjectLiteralExpression(initializer))
135
+ return initializer;
136
+ }
137
+ return null;
138
+ }
139
+ function serialiseDescriptor(wireKind, builderKind, call, context) {
140
+ if (wireKind === "external-series") {
141
+ return serialiseExternalSeries(call, context);
142
+ }
143
+ const defaultArg = call.arguments[0];
144
+ if (defaultArg === undefined) {
145
+ addDefaultLiteralDiagnostic(builderKind, call.expression, context);
146
+ return null;
147
+ }
148
+ const defaultValue = readLiteral(defaultArg);
149
+ if (defaultValue === undefined || Array.isArray(defaultValue)) {
150
+ addDefaultLiteralDiagnostic(builderKind, defaultArg, context);
151
+ return null;
152
+ }
153
+ const descriptor = {
154
+ kind: wireKind,
155
+ defaultValue,
156
+ };
157
+ if (wireKind === "enum") {
158
+ const optionsArg = call.arguments[1];
159
+ const options = optionsArg === undefined ? null : readStringArray(optionsArg);
160
+ if (options === null) {
161
+ addDefaultLiteralDiagnostic(builderKind, optionsArg ?? call.expression, context);
162
+ return null;
163
+ }
164
+ descriptor.options = Object.freeze(options.slice());
165
+ copyObjectLiteralFields(call.arguments[2], descriptor, builderKind, context);
166
+ }
167
+ else {
168
+ copyObjectLiteralFields(call.arguments[1], descriptor, builderKind, context);
169
+ }
170
+ return Object.freeze(descriptor);
171
+ }
172
+ function serialiseExternalSeries(call, context) {
173
+ const arg = call.arguments[0];
174
+ if (arg === undefined || !ts.isObjectLiteralExpression(arg)) {
175
+ addDefaultLiteralDiagnostic("externalSeries", arg ?? call.expression, context);
176
+ return null;
177
+ }
178
+ const descriptor = { kind: "external-series" };
179
+ let sawName = false;
180
+ let sawSchema = false;
181
+ for (const property of arg.properties) {
182
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === "schema") {
183
+ descriptor.schema = Object.freeze({ kind: "external-series-schema" });
184
+ sawSchema = true;
185
+ continue;
186
+ }
187
+ if (!ts.isPropertyAssignment(property))
188
+ continue;
189
+ const key = propertyNameText(property.name);
190
+ if (key === null)
191
+ continue;
192
+ if (key === "name") {
193
+ const value = readLiteral(property.initializer);
194
+ if (typeof value !== "string") {
195
+ addDefaultLiteralDiagnostic("externalSeries", property.initializer, context);
196
+ return null;
197
+ }
198
+ descriptor.name = value;
199
+ sawName = true;
200
+ }
201
+ else if (key === "schema") {
202
+ descriptor.schema = Object.freeze({ kind: "external-series-schema" });
203
+ sawSchema = true;
204
+ }
205
+ else if (key === "title") {
206
+ const value = readLiteral(property.initializer);
207
+ if (typeof value !== "string") {
208
+ addDefaultLiteralDiagnostic("externalSeries", property.initializer, context);
209
+ return null;
210
+ }
211
+ descriptor.title = value;
212
+ }
213
+ }
214
+ if (!sawName || !sawSchema) {
215
+ addDefaultLiteralDiagnostic("externalSeries", arg, context);
216
+ return null;
217
+ }
218
+ return Object.freeze(descriptor);
219
+ }
220
+ function copyObjectLiteralFields(arg, descriptor, builderKind, context) {
221
+ if (arg === undefined)
222
+ return;
223
+ const unwrapped = unwrapConstAssertion(arg);
224
+ if (!ts.isObjectLiteralExpression(unwrapped)) {
225
+ addDefaultLiteralDiagnostic(builderKind, arg, context);
226
+ return;
227
+ }
228
+ for (const property of unwrapped.properties) {
229
+ if (!ts.isPropertyAssignment(property))
230
+ continue;
231
+ const key = propertyNameText(property.name);
232
+ if (key === null)
233
+ continue;
234
+ const value = readLiteral(property.initializer);
235
+ if (value === undefined) {
236
+ addDefaultLiteralDiagnostic(builderKind, property.initializer, context);
237
+ continue;
238
+ }
239
+ descriptor[key] = value;
240
+ }
241
+ }
242
+ function readLiteral(node) {
243
+ const unwrapped = unwrapConstAssertion(node);
244
+ if (ts.isNumericLiteral(unwrapped))
245
+ return Number(unwrapped.text);
246
+ if (ts.isPrefixUnaryExpression(unwrapped) &&
247
+ (unwrapped.operator === ts.SyntaxKind.MinusToken ||
248
+ unwrapped.operator === ts.SyntaxKind.PlusToken) &&
249
+ ts.isNumericLiteral(unwrapped.operand)) {
250
+ const value = Number(unwrapped.operand.text);
251
+ return unwrapped.operator === ts.SyntaxKind.MinusToken ? -value : value;
252
+ }
253
+ if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped)) {
254
+ return unwrapped.text;
255
+ }
256
+ if (unwrapped.kind === ts.SyntaxKind.TrueKeyword)
257
+ return true;
258
+ if (unwrapped.kind === ts.SyntaxKind.FalseKeyword)
259
+ return false;
260
+ if (ts.isArrayLiteralExpression(unwrapped)) {
261
+ const values = [];
262
+ for (const element of unwrapped.elements) {
263
+ const literal = readLiteral(element);
264
+ if (typeof literal !== "string")
265
+ return undefined;
266
+ values.push(literal);
267
+ }
268
+ return Object.freeze(values);
269
+ }
270
+ return undefined;
271
+ }
272
+ function readStringArray(node) {
273
+ const value = readLiteral(node);
274
+ if (!Array.isArray(value))
275
+ return null;
276
+ return Object.freeze(value.slice());
277
+ }
278
+ function unwrapConstAssertion(node) {
279
+ let current = node;
280
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current)) {
281
+ if (ts.isParenthesizedExpression(current)) {
282
+ current = current.expression;
283
+ }
284
+ else {
285
+ current = current.expression;
286
+ }
287
+ }
288
+ return current;
289
+ }
290
+ function propertyNameText(name) {
291
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
292
+ return name.text;
293
+ }
294
+ return null;
295
+ }
296
+ function addDefaultLiteralDiagnostic(kind, node, context) {
297
+ context.diagnostics.push(createDiagnostic({
298
+ severity: "error",
299
+ code: "input-default-not-literal",
300
+ message: `input.${kind} default must be a literal (number / string / boolean), not a variable reference`,
301
+ file: context.sourcePath,
302
+ node,
303
+ sourceFile: context.sourceFile,
304
+ }));
305
+ }
306
+ //# sourceMappingURL=extractInputs.js.map