@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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { runStructuralChecks } from "./structuralChecks.js";
|
|
2
|
+
export type { StructuralCheckResult } from "./structuralChecks.js";
|
|
3
|
+
export { runForbiddenConstructs } from "./forbiddenConstructs.js";
|
|
4
|
+
export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
|
|
5
|
+
export { extractCapabilities } from "./extractCapabilities.js";
|
|
6
|
+
export { extractMaxLookback } from "./extractMaxLookback.js";
|
|
7
|
+
export type { ExtractMaxLookbackResult } from "./extractMaxLookback.js";
|
|
8
|
+
export { extractInputs } from "./extractInputs.js";
|
|
9
|
+
export type { ExtractedDescriptor, ExtractInputsResult } from "./extractInputs.js";
|
|
10
|
+
export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
11
|
+
export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
|
|
12
|
+
export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
|
|
13
|
+
export { extractAlertConditions } from "./extractAlertConditions.js";
|
|
14
|
+
export type { ExtractAlertConditionsResult } from "./extractAlertConditions.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,MAAM,uBAAuB,CAAC;AACnE,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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
export { runStructuralChecks } from "./structuralChecks.js";
|
|
4
|
+
export { runForbiddenConstructs } from "./forbiddenConstructs.js";
|
|
5
|
+
export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
|
|
6
|
+
export { extractCapabilities } from "./extractCapabilities.js";
|
|
7
|
+
export { extractMaxLookback } from "./extractMaxLookback.js";
|
|
8
|
+
export { extractInputs } from "./extractInputs.js";
|
|
9
|
+
export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
10
|
+
export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
|
|
11
|
+
export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
|
|
12
|
+
export { extractAlertConditions } from "./extractAlertConditions.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StatefulPrimitiveEntry } from "@invinite-org/chartlang-core";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { type CompileDiagnostic } from "../diagnostics.js";
|
|
4
|
+
/**
|
|
5
|
+
* Walk the source file and flag every stateful primitive call that sits
|
|
6
|
+
* inside any loop kind (`for`, `for-of`, `for-in`, `while`, `do-while`).
|
|
7
|
+
* Mirrors Pine's identical restriction: a stateful call inside a loop would
|
|
8
|
+
* receive one slot id per iteration, silently corrupting per-call state.
|
|
9
|
+
* `slot: false` entries (e.g. `ta.nz`) are flagged too — they're stateless
|
|
10
|
+
* but Pine still forbids them in loops, and the diagnostic message stays
|
|
11
|
+
* the same.
|
|
12
|
+
*
|
|
13
|
+
* The walk runs on the **original** AST (positions match the user's source).
|
|
14
|
+
* Loop ancestry detection uses `node.parent` directly — the source file is
|
|
15
|
+
* created with `setParentNodes: true` so `.parent` is populated.
|
|
16
|
+
*
|
|
17
|
+
* @since 0.1
|
|
18
|
+
* @example
|
|
19
|
+
* // const diagnostics = runStatefulCallInLoop(
|
|
20
|
+
* // sourceFile, checker, "demo.chart.ts", STATEFUL_PRIMITIVES_BY_NAME,
|
|
21
|
+
* // );
|
|
22
|
+
* const fn: typeof runStatefulCallInLoop = runStatefulCallInLoop;
|
|
23
|
+
* void fn;
|
|
24
|
+
*/
|
|
25
|
+
export declare function runStatefulCallInLoop(sourceFile: ts.SourceFile, checker: ts.TypeChecker, sourcePath: string, statefulByName: ReadonlyMap<string, StatefulPrimitiveEntry>): ReadonlyArray<CompileDiagnostic>;
|
|
26
|
+
//# sourceMappingURL=statefulCallInLoop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statefulCallInLoop.d.ts","sourceRoot":"","sources":["../../src/analysis/statefulCallInLoop.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAG7E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CACjC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,GAC5D,aAAa,CAAC,iBAAiB,CAAC,CA0BlC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Walk the source file and flag every stateful primitive call that sits
|
|
8
|
+
* inside any loop kind (`for`, `for-of`, `for-in`, `while`, `do-while`).
|
|
9
|
+
* Mirrors Pine's identical restriction: a stateful call inside a loop would
|
|
10
|
+
* receive one slot id per iteration, silently corrupting per-call state.
|
|
11
|
+
* `slot: false` entries (e.g. `ta.nz`) are flagged too — they're stateless
|
|
12
|
+
* but Pine still forbids them in loops, and the diagnostic message stays
|
|
13
|
+
* the same.
|
|
14
|
+
*
|
|
15
|
+
* The walk runs on the **original** AST (positions match the user's source).
|
|
16
|
+
* Loop ancestry detection uses `node.parent` directly — the source file is
|
|
17
|
+
* created with `setParentNodes: true` so `.parent` is populated.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1
|
|
20
|
+
* @example
|
|
21
|
+
* // const diagnostics = runStatefulCallInLoop(
|
|
22
|
+
* // sourceFile, checker, "demo.chart.ts", STATEFUL_PRIMITIVES_BY_NAME,
|
|
23
|
+
* // );
|
|
24
|
+
* const fn: typeof runStatefulCallInLoop = runStatefulCallInLoop;
|
|
25
|
+
* void fn;
|
|
26
|
+
*/
|
|
27
|
+
export function runStatefulCallInLoop(sourceFile, checker, sourcePath, statefulByName) {
|
|
28
|
+
const diagnostics = [];
|
|
29
|
+
const visit = (node) => {
|
|
30
|
+
if (ts.isCallExpression(node)) {
|
|
31
|
+
const calleeName = resolveCalleeName(node, checker);
|
|
32
|
+
if (calleeName !== null && statefulByName.has(calleeName)) {
|
|
33
|
+
if (insideLoop(node)) {
|
|
34
|
+
diagnostics.push(createDiagnostic({
|
|
35
|
+
severity: "error",
|
|
36
|
+
code: "stateful-call-inside-loop",
|
|
37
|
+
message: `Stateful primitive \`${calleeName}\` cannot be called inside a loop.`,
|
|
38
|
+
file: sourcePath,
|
|
39
|
+
node,
|
|
40
|
+
sourceFile,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
ts.forEachChild(node, visit);
|
|
46
|
+
};
|
|
47
|
+
ts.forEachChild(sourceFile, visit);
|
|
48
|
+
return Object.freeze(diagnostics.slice());
|
|
49
|
+
}
|
|
50
|
+
function insideLoop(node) {
|
|
51
|
+
let current = node.parent;
|
|
52
|
+
while (current) {
|
|
53
|
+
if (ts.isForStatement(current) ||
|
|
54
|
+
ts.isForOfStatement(current) ||
|
|
55
|
+
ts.isForInStatement(current) ||
|
|
56
|
+
ts.isWhileStatement(current) ||
|
|
57
|
+
ts.isDoStatement(current)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
current = current.parent;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=statefulCallInLoop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statefulCallInLoop.js","sourceRoot":"","sources":["../../src/analysis/statefulCallInLoop.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAG/D,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAA0B,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,qBAAqB,CACjC,UAAyB,EACzB,OAAuB,EACvB,UAAkB,EAClB,cAA2D;IAE3D,MAAM,WAAW,GAAwB,EAAE,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,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBACxD,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnB,WAAW,CAAC,IAAI,CACZ,gBAAgB,CAAC;wBACb,QAAQ,EAAE,OAAO;wBACjB,IAAI,EAAE,2BAA2B;wBACjC,OAAO,EAAE,wBAAwB,UAAU,oCAAoC;wBAC/E,IAAI,EAAE,UAAU;wBAChB,IAAI;wBACJ,UAAU;qBACb,CAAC,CACL,CAAC;gBACN,CAAC;YACL,CAAC;QACL,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,UAAU,CAAC,IAAa;IAC7B,IAAI,OAAO,GAAwB,IAAI,CAAC,MAAM,CAAC;IAC/C,OAAO,OAAO,EAAE,CAAC;QACb,IACI,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC;YAC1B,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAC5B,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAC5B,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAC5B,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,EAC3B,CAAC;YACC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { type CompileDiagnostic } from "../diagnostics.js";
|
|
3
|
+
type ValueFormat = "price" | "volume" | "percent" | "compact";
|
|
4
|
+
type ScaleAxis = "price" | "left" | "right" | "new";
|
|
5
|
+
/**
|
|
6
|
+
* Static script-author overrides extracted from the `define*` object
|
|
7
|
+
* literal. Non-literal values are ignored here; later Phase 4 passes attach
|
|
8
|
+
* dedicated diagnostics for stricter validation.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.4
|
|
11
|
+
* @example
|
|
12
|
+
* const o: StructuralScriptOverrides = { shortName: "EMA", format: "price" };
|
|
13
|
+
* void o;
|
|
14
|
+
*/
|
|
15
|
+
export type StructuralScriptOverrides = Readonly<{
|
|
16
|
+
maxBarsBack?: number;
|
|
17
|
+
format?: ValueFormat;
|
|
18
|
+
precision?: number;
|
|
19
|
+
scale?: ScaleAxis;
|
|
20
|
+
requiresIntervals?: ReadonlyArray<string>;
|
|
21
|
+
shortName?: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Result of `runStructuralChecks` — the discovered script `name` / `kind`
|
|
25
|
+
* for the manifest, plus any structural diagnostics. `name` is `""` when no
|
|
26
|
+
* default export is present; `kind` defaults to `"indicator"` for the same
|
|
27
|
+
* reason. The driver only consumes these fields when there are zero
|
|
28
|
+
* error-severity diagnostics.
|
|
29
|
+
*
|
|
30
|
+
* The `"drawing"` kind (Phase 3 / `defineDrawing` / PLAN.md §4.1) maps to
|
|
31
|
+
* the same code path the other two kinds use — only the manifest's
|
|
32
|
+
* discriminator differs so the editor can route the script to the
|
|
33
|
+
* drawing-tool picker vs the indicator-picker UI.
|
|
34
|
+
*
|
|
35
|
+
* @since 0.1
|
|
36
|
+
* @example
|
|
37
|
+
* const r: StructuralCheckResult = {
|
|
38
|
+
* diagnostics: [],
|
|
39
|
+
* name: "demo",
|
|
40
|
+
* kind: "indicator",
|
|
41
|
+
* };
|
|
42
|
+
* void r;
|
|
43
|
+
*/
|
|
44
|
+
export type StructuralCheckResult = Readonly<{
|
|
45
|
+
diagnostics: ReadonlyArray<CompileDiagnostic>;
|
|
46
|
+
name: string;
|
|
47
|
+
kind: "indicator" | "drawing" | "alert" | "alertCondition";
|
|
48
|
+
overrides: StructuralScriptOverrides;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Walk the source file's top-level statements to verify:
|
|
52
|
+
*
|
|
53
|
+
* - A default export exists and is `defineIndicator(...)`,
|
|
54
|
+
* `defineDrawing(...)`, `defineAlert(...)`, or
|
|
55
|
+
* `defineAlertCondition(...)` from
|
|
56
|
+
* `@invinite-org/chartlang-core`.
|
|
57
|
+
* - The first argument is an object literal carrying `apiVersion: 1`.
|
|
58
|
+
*
|
|
59
|
+
* On any violation, emits `missing-default-export` or
|
|
60
|
+
* `api-version-mismatch`. Returns the discovered script name + kind for
|
|
61
|
+
* the manifest assembly step.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.1
|
|
64
|
+
* @example
|
|
65
|
+
* // const { diagnostics, name, kind } = runStructuralChecks(
|
|
66
|
+
* // sourceFile, checker, "demo.chart.ts",
|
|
67
|
+
* // );
|
|
68
|
+
* const fn: typeof runStructuralChecks = runStructuralChecks;
|
|
69
|
+
* void fn;
|
|
70
|
+
*/
|
|
71
|
+
export declare function runStructuralChecks(sourceFile: ts.SourceFile, checker: ts.TypeChecker, sourcePath: string): StructuralCheckResult;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=structuralChecks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structuralChecks.d.ts","sourceRoot":"","sources":["../../src/analysis/structuralChecks.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAU7E,KAAK,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAC9D,KAAK,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,iBAAiB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IACzC,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,gBAAgB,CAAC;IAC3D,SAAS,EAAE,yBAAyB,CAAC;CACxC,CAAC,CAAC;AA+EH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,UAAU,EAAE,MAAM,GACnB,qBAAqB,CAsJvB"}
|
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
function readStringArray(node) {
|
|
13
|
+
if (!ts.isArrayLiteralExpression(node))
|
|
14
|
+
return undefined;
|
|
15
|
+
const values = [];
|
|
16
|
+
for (const element of node.elements) {
|
|
17
|
+
if (!ts.isStringLiteral(element))
|
|
18
|
+
return undefined;
|
|
19
|
+
values.push(element.text);
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze(values);
|
|
22
|
+
}
|
|
23
|
+
function readValueFormat(node) {
|
|
24
|
+
if (!ts.isStringLiteral(node))
|
|
25
|
+
return undefined;
|
|
26
|
+
if (node.text === "price" ||
|
|
27
|
+
node.text === "volume" ||
|
|
28
|
+
node.text === "percent" ||
|
|
29
|
+
node.text === "compact") {
|
|
30
|
+
return node.text;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function readScaleAxis(node) {
|
|
35
|
+
if (!ts.isStringLiteral(node))
|
|
36
|
+
return undefined;
|
|
37
|
+
if (node.text === "price" ||
|
|
38
|
+
node.text === "left" ||
|
|
39
|
+
node.text === "right" ||
|
|
40
|
+
node.text === "new") {
|
|
41
|
+
return node.text;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
function extractOverrides(argument, kind) {
|
|
46
|
+
let maxBarsBack;
|
|
47
|
+
let format;
|
|
48
|
+
let precision;
|
|
49
|
+
let scale;
|
|
50
|
+
let requiresIntervals;
|
|
51
|
+
let shortName;
|
|
52
|
+
for (const property of argument.properties) {
|
|
53
|
+
if (!ts.isPropertyAssignment(property))
|
|
54
|
+
continue;
|
|
55
|
+
const propertyName = property.name;
|
|
56
|
+
if (!ts.isIdentifier(propertyName))
|
|
57
|
+
continue;
|
|
58
|
+
const initializer = property.initializer;
|
|
59
|
+
if (propertyName.text === "maxBarsBack" && kind !== "drawing") {
|
|
60
|
+
if (ts.isNumericLiteral(initializer))
|
|
61
|
+
maxBarsBack = Number(initializer.text);
|
|
62
|
+
}
|
|
63
|
+
else if (propertyName.text === "format" && kind !== "alert") {
|
|
64
|
+
format = readValueFormat(initializer);
|
|
65
|
+
}
|
|
66
|
+
else if (propertyName.text === "precision" && kind !== "alert") {
|
|
67
|
+
if (ts.isNumericLiteral(initializer))
|
|
68
|
+
precision = Number(initializer.text);
|
|
69
|
+
}
|
|
70
|
+
else if (propertyName.text === "scale" && kind === "indicator") {
|
|
71
|
+
scale = readScaleAxis(initializer);
|
|
72
|
+
}
|
|
73
|
+
else if (propertyName.text === "requiresIntervals") {
|
|
74
|
+
requiresIntervals = readStringArray(initializer);
|
|
75
|
+
}
|
|
76
|
+
else if (propertyName.text === "shortName") {
|
|
77
|
+
if (ts.isStringLiteral(initializer))
|
|
78
|
+
shortName = initializer.text;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
...(maxBarsBack === undefined ? {} : { maxBarsBack }),
|
|
83
|
+
...(format === undefined ? {} : { format }),
|
|
84
|
+
...(precision === undefined ? {} : { precision }),
|
|
85
|
+
...(scale === undefined ? {} : { scale }),
|
|
86
|
+
...(requiresIntervals === undefined ? {} : { requiresIntervals }),
|
|
87
|
+
...(shortName === undefined ? {} : { shortName }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Walk the source file's top-level statements to verify:
|
|
92
|
+
*
|
|
93
|
+
* - A default export exists and is `defineIndicator(...)`,
|
|
94
|
+
* `defineDrawing(...)`, `defineAlert(...)`, or
|
|
95
|
+
* `defineAlertCondition(...)` from
|
|
96
|
+
* `@invinite-org/chartlang-core`.
|
|
97
|
+
* - The first argument is an object literal carrying `apiVersion: 1`.
|
|
98
|
+
*
|
|
99
|
+
* On any violation, emits `missing-default-export` or
|
|
100
|
+
* `api-version-mismatch`. Returns the discovered script name + kind for
|
|
101
|
+
* the manifest assembly step.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.1
|
|
104
|
+
* @example
|
|
105
|
+
* // const { diagnostics, name, kind } = runStructuralChecks(
|
|
106
|
+
* // sourceFile, checker, "demo.chart.ts",
|
|
107
|
+
* // );
|
|
108
|
+
* const fn: typeof runStructuralChecks = runStructuralChecks;
|
|
109
|
+
* void fn;
|
|
110
|
+
*/
|
|
111
|
+
export function runStructuralChecks(sourceFile, checker, sourcePath) {
|
|
112
|
+
const diagnostics = [];
|
|
113
|
+
let name = "";
|
|
114
|
+
let kind = "indicator";
|
|
115
|
+
const exportAssignment = sourceFile.statements.find((statement) => ts.isExportAssignment(statement) && !statement.isExportEquals);
|
|
116
|
+
if (!exportAssignment) {
|
|
117
|
+
diagnostics.push(createDiagnostic({
|
|
118
|
+
severity: "error",
|
|
119
|
+
code: "missing-default-export",
|
|
120
|
+
message: "Script must default-export a defineIndicator(...), defineDrawing(...), defineAlert(...), or defineAlertCondition(...) call.",
|
|
121
|
+
file: sourcePath,
|
|
122
|
+
node: sourceFile,
|
|
123
|
+
sourceFile,
|
|
124
|
+
}));
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
diagnostics: Object.freeze(diagnostics),
|
|
127
|
+
name,
|
|
128
|
+
kind,
|
|
129
|
+
overrides: Object.freeze({}),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const expression = exportAssignment.expression;
|
|
133
|
+
if (!ts.isCallExpression(expression)) {
|
|
134
|
+
diagnostics.push(createDiagnostic({
|
|
135
|
+
severity: "error",
|
|
136
|
+
code: "missing-default-export",
|
|
137
|
+
message: "Default export must be a defineIndicator/defineDrawing/defineAlert/defineAlertCondition call.",
|
|
138
|
+
file: sourcePath,
|
|
139
|
+
node: expression,
|
|
140
|
+
sourceFile,
|
|
141
|
+
}));
|
|
142
|
+
return Object.freeze({
|
|
143
|
+
diagnostics: Object.freeze(diagnostics),
|
|
144
|
+
name,
|
|
145
|
+
kind,
|
|
146
|
+
overrides: Object.freeze({}),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const calleeName = resolveCalleeName(expression, checker);
|
|
150
|
+
if (calleeName === null || !DEFINE_CALLS.has(calleeName)) {
|
|
151
|
+
diagnostics.push(createDiagnostic({
|
|
152
|
+
severity: "error",
|
|
153
|
+
code: "missing-default-export",
|
|
154
|
+
message: "Default export must call defineIndicator, defineDrawing, defineAlert, or defineAlertCondition from core.",
|
|
155
|
+
file: sourcePath,
|
|
156
|
+
node: expression,
|
|
157
|
+
sourceFile,
|
|
158
|
+
}));
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
diagnostics: Object.freeze(diagnostics),
|
|
161
|
+
name,
|
|
162
|
+
kind,
|
|
163
|
+
overrides: Object.freeze({}),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (calleeName === "defineAlert") {
|
|
167
|
+
kind = "alert";
|
|
168
|
+
}
|
|
169
|
+
else if (calleeName === "defineAlertCondition") {
|
|
170
|
+
kind = "alertCondition";
|
|
171
|
+
}
|
|
172
|
+
else if (calleeName === "defineDrawing") {
|
|
173
|
+
kind = "drawing";
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
kind = "indicator";
|
|
177
|
+
}
|
|
178
|
+
const argument = expression.arguments[0];
|
|
179
|
+
if (!argument || !ts.isObjectLiteralExpression(argument)) {
|
|
180
|
+
diagnostics.push(createDiagnostic({
|
|
181
|
+
severity: "error",
|
|
182
|
+
code: "api-version-mismatch",
|
|
183
|
+
message: "defineIndicator/defineDrawing/defineAlert/defineAlertCondition requires an object-literal argument.",
|
|
184
|
+
file: sourcePath,
|
|
185
|
+
node: expression,
|
|
186
|
+
sourceFile,
|
|
187
|
+
}));
|
|
188
|
+
return Object.freeze({
|
|
189
|
+
diagnostics: Object.freeze(diagnostics),
|
|
190
|
+
name,
|
|
191
|
+
kind,
|
|
192
|
+
overrides: Object.freeze({}),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
let apiVersionOk = false;
|
|
196
|
+
for (const property of argument.properties) {
|
|
197
|
+
if (!ts.isPropertyAssignment(property))
|
|
198
|
+
continue;
|
|
199
|
+
const propertyName = property.name;
|
|
200
|
+
if (!ts.isIdentifier(propertyName))
|
|
201
|
+
continue;
|
|
202
|
+
if (propertyName.text === "apiVersion") {
|
|
203
|
+
const initializer = property.initializer;
|
|
204
|
+
if (ts.isNumericLiteral(initializer) && Number(initializer.text) === 1) {
|
|
205
|
+
apiVersionOk = true;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const found = initializer.getText(sourceFile);
|
|
209
|
+
diagnostics.push(createDiagnostic({
|
|
210
|
+
severity: "error",
|
|
211
|
+
code: "api-version-mismatch",
|
|
212
|
+
message: `\`apiVersion: ${found}\` is not supported — this compiler implements the frozen \`apiVersion: 1\` contract. Future language versions require a compiler that declares support for them.`,
|
|
213
|
+
file: sourcePath,
|
|
214
|
+
node: initializer,
|
|
215
|
+
sourceFile,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (propertyName.text === "name") {
|
|
220
|
+
const initializer = property.initializer;
|
|
221
|
+
if (ts.isStringLiteral(initializer)) {
|
|
222
|
+
name = initializer.text;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!apiVersionOk && diagnostics.length === 0) {
|
|
227
|
+
diagnostics.push(createDiagnostic({
|
|
228
|
+
severity: "error",
|
|
229
|
+
code: "api-version-mismatch",
|
|
230
|
+
message: "defineIndicator/defineDrawing/defineAlert/defineAlertCondition requires `apiVersion: 1` — the frozen language version this compiler implements.",
|
|
231
|
+
file: sourcePath,
|
|
232
|
+
node: argument,
|
|
233
|
+
sourceFile,
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
diagnostics: Object.freeze(diagnostics),
|
|
238
|
+
name,
|
|
239
|
+
kind,
|
|
240
|
+
overrides: extractOverrides(argument, kind),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=structuralChecks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structuralChecks.js","sourceRoot":"","sources":["../../src/analysis/structuralChecks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type IntervalDescriptor } from "@invinite-org/chartlang-core";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { type CompileDiagnostic } from "../diagnostics.js";
|
|
4
|
+
/**
|
|
5
|
+
* Validate static `request.lowerTf({ interval })` calls against declared main
|
|
6
|
+
* intervals and emit `lower-tf-not-lower` when the requested interval is not
|
|
7
|
+
* strictly lower than the smallest declared main interval. Non-literal and
|
|
8
|
+
* unparseable interval values are skipped — the literal-check pass and the
|
|
9
|
+
* runtime's `unsupported-interval` gate own those.
|
|
10
|
+
*
|
|
11
|
+
* Ordering uses {@link intervalToSeconds}, which treats `1M` as 30 days and
|
|
12
|
+
* `1Y` as 365 days. Comparisons that hinge on calendar-exact durations (e.g.
|
|
13
|
+
* `30D` vs `1M`) should provide `intervalSeconds` on the {@link IntervalDescriptor}
|
|
14
|
+
* to override the approximation.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.6
|
|
17
|
+
* @stable
|
|
18
|
+
* @example
|
|
19
|
+
* // const diagnostics = validateLowerTfIntervals(
|
|
20
|
+
* // sourceFile, checker, "demo.chart.ts", capabilities.intervals,
|
|
21
|
+
* // );
|
|
22
|
+
* const fn: typeof validateLowerTfIntervals = validateLowerTfIntervals;
|
|
23
|
+
* void fn;
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateLowerTfIntervals(sourceFile: ts.SourceFile, checker: ts.TypeChecker, filePath: string, declaredIntervals: ReadonlyArray<IntervalDescriptor>): ReadonlyArray<CompileDiagnostic>;
|
|
26
|
+
//# sourceMappingURL=validateLowerTfIntervals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateLowerTfIntervals.d.ts","sourceRoot":"","sources":["../../src/analysis/validateLowerTfIntervals.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AA0B7E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CACpC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACrD,aAAa,CAAC,iBAAiB,CAAC,CAmClC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
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 { intervalToSeconds } from "@invinite-org/chartlang-core";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { createDiagnostic } from "../diagnostics.js";
|
|
6
|
+
import { resolveCalleeName } from "../transformers/resolveCallee.js";
|
|
7
|
+
function secondsOrNull(descriptor) {
|
|
8
|
+
try {
|
|
9
|
+
return intervalToSeconds(descriptor);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function smallestParseableMain(declaredIntervals) {
|
|
16
|
+
let smallest = null;
|
|
17
|
+
for (const descriptor of declaredIntervals) {
|
|
18
|
+
const seconds = secondsOrNull(descriptor);
|
|
19
|
+
if (seconds !== null && (smallest === null || seconds < smallest.seconds)) {
|
|
20
|
+
smallest = { descriptor, seconds };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return smallest;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate static `request.lowerTf({ interval })` calls against declared main
|
|
27
|
+
* intervals and emit `lower-tf-not-lower` when the requested interval is not
|
|
28
|
+
* strictly lower than the smallest declared main interval. Non-literal and
|
|
29
|
+
* unparseable interval values are skipped — the literal-check pass and the
|
|
30
|
+
* runtime's `unsupported-interval` gate own those.
|
|
31
|
+
*
|
|
32
|
+
* Ordering uses {@link intervalToSeconds}, which treats `1M` as 30 days and
|
|
33
|
+
* `1Y` as 365 days. Comparisons that hinge on calendar-exact durations (e.g.
|
|
34
|
+
* `30D` vs `1M`) should provide `intervalSeconds` on the {@link IntervalDescriptor}
|
|
35
|
+
* to override the approximation.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.6
|
|
38
|
+
* @stable
|
|
39
|
+
* @example
|
|
40
|
+
* // const diagnostics = validateLowerTfIntervals(
|
|
41
|
+
* // sourceFile, checker, "demo.chart.ts", capabilities.intervals,
|
|
42
|
+
* // );
|
|
43
|
+
* const fn: typeof validateLowerTfIntervals = validateLowerTfIntervals;
|
|
44
|
+
* void fn;
|
|
45
|
+
*/
|
|
46
|
+
export function validateLowerTfIntervals(sourceFile, checker, filePath, declaredIntervals) {
|
|
47
|
+
const main = smallestParseableMain(declaredIntervals);
|
|
48
|
+
if (main === null)
|
|
49
|
+
return [];
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const checkCall = (call) => {
|
|
52
|
+
const literal = readLiteralInterval(call);
|
|
53
|
+
if (literal === null)
|
|
54
|
+
return;
|
|
55
|
+
const requestedSec = secondsOrNull({
|
|
56
|
+
value: literal.text,
|
|
57
|
+
label: literal.text,
|
|
58
|
+
group: "request",
|
|
59
|
+
});
|
|
60
|
+
if (requestedSec === null || requestedSec < main.seconds)
|
|
61
|
+
return;
|
|
62
|
+
diagnostics.push(createDiagnostic({
|
|
63
|
+
severity: "error",
|
|
64
|
+
code: "lower-tf-not-lower",
|
|
65
|
+
message: `request.lowerTf({ interval: "${literal.text}" }) must be strictly lower than the main interval "${main.descriptor.value}" (requested ${requestedSec}s >= main ${main.seconds}s)`,
|
|
66
|
+
file: filePath,
|
|
67
|
+
node: literal,
|
|
68
|
+
sourceFile,
|
|
69
|
+
}));
|
|
70
|
+
};
|
|
71
|
+
const visit = (node) => {
|
|
72
|
+
if (ts.isCallExpression(node) && resolveCalleeName(node, checker) === "request.lowerTf") {
|
|
73
|
+
checkCall(node);
|
|
74
|
+
}
|
|
75
|
+
ts.forEachChild(node, visit);
|
|
76
|
+
};
|
|
77
|
+
ts.forEachChild(sourceFile, visit);
|
|
78
|
+
return Object.freeze(diagnostics.slice());
|
|
79
|
+
}
|
|
80
|
+
function readLiteralInterval(call) {
|
|
81
|
+
const opts = call.arguments[0];
|
|
82
|
+
if (opts === undefined || !ts.isObjectLiteralExpression(opts))
|
|
83
|
+
return null;
|
|
84
|
+
const property = opts.properties
|
|
85
|
+
.filter(ts.isPropertyAssignment)
|
|
86
|
+
.find((p) => ts.isIdentifier(p.name) && p.name.text === "interval");
|
|
87
|
+
if (property === undefined || !ts.isStringLiteral(property.initializer))
|
|
88
|
+
return null;
|
|
89
|
+
return property.initializer;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=validateLowerTfIntervals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateLowerTfIntervals.js","sourceRoot":"","sources":["../../src/analysis/validateLowerTfIntervals.ts"],"names":[],"mappings":"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"}
|