@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.
- package/CHANGELOG.md +683 -0
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/analysis/extractAlertConditions.d.ts +33 -0
- package/dist/analysis/extractAlertConditions.d.ts.map +1 -0
- package/dist/analysis/extractAlertConditions.js +118 -0
- package/dist/analysis/extractAlertConditions.js.map +1 -0
- package/dist/analysis/extractCapabilities.d.ts +22 -0
- package/dist/analysis/extractCapabilities.d.ts.map +1 -0
- package/dist/analysis/extractCapabilities.js +44 -0
- package/dist/analysis/extractCapabilities.js.map +1 -0
- package/dist/analysis/extractInputs.d.ts +44 -0
- package/dist/analysis/extractInputs.d.ts.map +1 -0
- package/dist/analysis/extractInputs.js +306 -0
- package/dist/analysis/extractInputs.js.map +1 -0
- package/dist/analysis/extractMaxLookback.d.ts +37 -0
- package/dist/analysis/extractMaxLookback.d.ts.map +1 -0
- package/dist/analysis/extractMaxLookback.js +90 -0
- package/dist/analysis/extractMaxLookback.js.map +1 -0
- package/dist/analysis/extractRequestedIntervals.d.ts +19 -0
- package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -0
- package/dist/analysis/extractRequestedIntervals.js +85 -0
- package/dist/analysis/extractRequestedIntervals.js.map +1 -0
- package/dist/analysis/extractRequiresIntervals.d.ts +16 -0
- package/dist/analysis/extractRequiresIntervals.d.ts.map +1 -0
- package/dist/analysis/extractRequiresIntervals.js +71 -0
- package/dist/analysis/extractRequiresIntervals.js.map +1 -0
- package/dist/analysis/forbiddenConstructs.d.ts +22 -0
- package/dist/analysis/forbiddenConstructs.d.ts.map +1 -0
- package/dist/analysis/forbiddenConstructs.js +214 -0
- package/dist/analysis/forbiddenConstructs.js.map +1 -0
- package/dist/analysis/index.d.ts +15 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +13 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/statefulCallInLoop.d.ts +26 -0
- package/dist/analysis/statefulCallInLoop.d.ts.map +1 -0
- package/dist/analysis/statefulCallInLoop.js +64 -0
- package/dist/analysis/statefulCallInLoop.js.map +1 -0
- package/dist/analysis/structuralChecks.d.ts +73 -0
- package/dist/analysis/structuralChecks.d.ts.map +1 -0
- package/dist/analysis/structuralChecks.js +243 -0
- package/dist/analysis/structuralChecks.js.map +1 -0
- package/dist/analysis/validateLowerTfIntervals.d.ts +26 -0
- package/dist/analysis/validateLowerTfIntervals.d.ts.map +1 -0
- package/dist/analysis/validateLowerTfIntervals.js +91 -0
- package/dist/analysis/validateLowerTfIntervals.js.map +1 -0
- package/dist/api.d.ts +205 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +354 -0
- package/dist/api.js.map +1 -0
- package/dist/bundle.d.ts +75 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +90 -0
- package/dist/bundle.js.map +1 -0
- package/dist/diagnostics.d.ts +88 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +95 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +40 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +57 -0
- package/dist/manifest.js.map +1 -0
- package/dist/program.d.ts +68 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +1391 -0
- package/dist/program.js.map +1 -0
- package/dist/transformers/callsiteIdInjection.d.ts +48 -0
- package/dist/transformers/callsiteIdInjection.d.ts.map +1 -0
- package/dist/transformers/callsiteIdInjection.js +91 -0
- package/dist/transformers/callsiteIdInjection.js.map +1 -0
- package/dist/transformers/index.d.ts +4 -0
- package/dist/transformers/index.d.ts.map +1 -0
- package/dist/transformers/index.js +5 -0
- package/dist/transformers/index.js.map +1 -0
- package/dist/transformers/resolveCallee.d.ts +39 -0
- package/dist/transformers/resolveCallee.d.ts.map +1 -0
- package/dist/transformers/resolveCallee.js +136 -0
- package/dist/transformers/resolveCallee.js.map +1 -0
- package/dist/typesEmit.d.ts +35 -0
- package/dist/typesEmit.d.ts.map +1 -0
- package/dist/typesEmit.js +27 -0
- package/dist/typesEmit.js.map +1 -0
- 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
|