fhirpath-ts 0.2.1 → 0.2.2
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/README.md +12 -5
- package/dist/analyzer/analyze-dto.d.ts.map +1 -1
- package/dist/analyzer/analyze-dto.js +14 -8
- package/dist/analyzer/analyze-dto.js.map +1 -1
- package/dist/analyzer/analyze.d.ts +23 -12
- package/dist/analyzer/analyze.d.ts.map +1 -1
- package/dist/analyzer/analyze.js +138 -46
- package/dist/analyzer/analyze.js.map +1 -1
- package/dist/analyzer/declarations.d.ts +6 -0
- package/dist/analyzer/declarations.d.ts.map +1 -1
- package/dist/analyzer/declarations.js +7 -0
- package/dist/analyzer/declarations.js.map +1 -1
- package/dist/analyzer/expression-policy.d.ts +88 -14
- package/dist/analyzer/expression-policy.d.ts.map +1 -1
- package/dist/analyzer/expression-policy.js +296 -41
- package/dist/analyzer/expression-policy.js.map +1 -1
- package/dist/analyzer/operator-rules.d.ts.map +1 -1
- package/dist/analyzer/operator-rules.js +7 -6
- package/dist/analyzer/operator-rules.js.map +1 -1
- package/dist/analyzer/signatures.d.ts +49 -0
- package/dist/analyzer/signatures.d.ts.map +1 -1
- package/dist/analyzer/signatures.js +76 -31
- package/dist/analyzer/signatures.js.map +1 -1
- package/dist/analyzer/source-options.d.ts +17 -0
- package/dist/analyzer/source-options.d.ts.map +1 -0
- package/dist/analyzer/source-options.js +3 -0
- package/dist/analyzer/source-options.js.map +1 -0
- package/dist/api/dto.d.ts +9 -4
- package/dist/api/dto.d.ts.map +1 -1
- package/dist/api/dto.js +14 -3
- package/dist/api/dto.js.map +1 -1
- package/dist/api/strict.d.ts.map +1 -1
- package/dist/api/strict.js +8 -3
- package/dist/api/strict.js.map +1 -1
- package/dist/cli/dto-check.d.ts +9 -0
- package/dist/cli/dto-check.d.ts.map +1 -1
- package/dist/cli/dto-check.js +49 -6
- package/dist/cli/dto-check.js.map +1 -1
- package/dist/cli/fhirpath-check.js +161 -31
- package/dist/cli/fhirpath-check.js.map +1 -1
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +13 -8
- package/dist/eslint/index.js.map +1 -1
- package/dist/sites/index.d.ts +31 -2
- package/dist/sites/index.d.ts.map +1 -1
- package/dist/sites/index.js +351 -30
- package/dist/sites/index.js.map +1 -1
- package/dist/typed/infer.d.ts +2 -0
- package/dist/typed/infer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/analyzer/analyze-dto.ts +19 -9
- package/src/analyzer/analyze.ts +186 -54
- package/src/analyzer/declarations.ts +12 -0
- package/src/analyzer/expression-policy.ts +423 -47
- package/src/analyzer/operator-rules.ts +7 -6
- package/src/analyzer/signatures.ts +100 -36
- package/src/analyzer/source-options.ts +19 -0
- package/src/api/dto.ts +35 -9
- package/src/api/strict.ts +8 -3
- package/src/cli/dto-check.ts +56 -6
- package/src/cli/fhirpath-check.ts +180 -34
- package/src/eslint/index.ts +15 -12
- package/src/sites/index.ts +428 -40
- package/src/typed/infer.ts +2 -2
package/README.md
CHANGED
|
@@ -202,12 +202,12 @@ statusRows // [{ label: 'Active', tone: 'info' }, ..., { label: 'Unknown', tone:
|
|
|
202
202
|
|
|
203
203
|
### Join related resources
|
|
204
204
|
|
|
205
|
-
Pass
|
|
206
|
-
|
|
205
|
+
Pass the related resources through `env` and declare their types with
|
|
206
|
+
`envTypes`. Use `vars` to find the related item once per row. `%context` is the
|
|
207
|
+
resource for the current row. With the types declared, `fhirpath-check`
|
|
208
|
+
verifies every path, including the join itself.
|
|
207
209
|
|
|
208
210
|
```ts
|
|
209
|
-
const reports = [...reportsByOrderId].map(([orderId, report]) => ({ orderId, report }))
|
|
210
|
-
|
|
211
211
|
r4.project(
|
|
212
212
|
orders,
|
|
213
213
|
{
|
|
@@ -219,11 +219,18 @@ r4.project(
|
|
|
219
219
|
},
|
|
220
220
|
{
|
|
221
221
|
env: { reports },
|
|
222
|
-
|
|
222
|
+
envTypes: { reports: { type: 'DiagnosticReport', collection: true } },
|
|
223
|
+
vars: { report: "%reports.where(basedOn.reference = 'ServiceRequest/' + %context.id).first()" },
|
|
224
|
+
varTypes: { report: { type: 'DiagnosticReport' } },
|
|
223
225
|
},
|
|
224
226
|
)
|
|
225
227
|
```
|
|
226
228
|
|
|
229
|
+
When the join key is not on the resource, join in TypeScript instead and keep
|
|
230
|
+
each expression rooted in its own typed resource. Untyped values in `env`
|
|
231
|
+
cannot be verified: paths through them are reported as `unchecked-navigation`
|
|
232
|
+
warnings. See [Static checking](docs/static-checking.md).
|
|
233
|
+
|
|
227
234
|
### Follow references in a Bundle
|
|
228
235
|
|
|
229
236
|
`resolve()` follows contained references and references to another Bundle entry.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-dto.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyze-dto.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,QAAQ,EAAiB,MAAM,eAAe,CAAA;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAEjE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAA;AAGrB,2FAA2F;AAC3F,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACvD,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAA;IACd,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE;QACjB,KAAK,CAAC,EAAE,aAAa,CAAA;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;QAC5C,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,QAAQ,CAAC,EAAE,wBAAwB,CAAA;QACnC,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,wBAAwB,CAAA;KACpC,CAAA;CACF;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAA;CACnC;AAED,8FAA8F;AAC9F,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,kFAAkF;IAClF,MAAM,CAAC,EAAE,eAAe,CAAA;CACzB;AA4BD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,cAAc,GACvB,CAAC,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAIrC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"analyze-dto.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyze-dto.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,QAAQ,EAAiB,MAAM,eAAe,CAAA;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAEjE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAA;AAGrB,2FAA2F;AAC3F,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACvD,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAA;IACd,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE;QACjB,KAAK,CAAC,EAAE,aAAa,CAAA;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;QAC5C,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,QAAQ,CAAC,EAAE,wBAAwB,CAAA;QACnC,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,wBAAwB,CAAA;KACpC,CAAA;CACF;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAA;CACnC;AAED,8FAA8F;AAC9F,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,kFAAkF;IAClF,MAAM,CAAC,EAAE,eAAe,CAAA;CACzB;AA4BD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,cAAc,GACvB,CAAC,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAIrC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAyDtF"}
|
|
@@ -3,7 +3,7 @@ import { dtoDefinition } from "../api/dto.js";
|
|
|
3
3
|
import { bareEnvironmentName } from "../engine/context.js";
|
|
4
4
|
import { valueKindOfTypeName } from "../values/type-compat.js";
|
|
5
5
|
import { analyzeExpressionDetailed, } from "./analyze.js";
|
|
6
|
-
import { analyzerEnvironmentVariables, analyzerVariables } from "./declarations.js";
|
|
6
|
+
import { analyzerEnvironmentVariables, analyzerVariables, PROJECT_ROW_VARIABLES } from "./declarations.js";
|
|
7
7
|
/**
|
|
8
8
|
* The analyzer context for a DTO: what its engine supplies, with the caller's
|
|
9
9
|
* own options layered on top *per name* — one function or variable a host
|
|
@@ -49,13 +49,11 @@ export function analyzeDto(dto, options) {
|
|
|
49
49
|
const definition = dtoDefinition(dto);
|
|
50
50
|
const context = contextOf(options);
|
|
51
51
|
const inputType = context.inputType ?? definition.fhirType;
|
|
52
|
-
const declared = {
|
|
53
|
-
|
|
54
|
-
rowTotal: { types: ['System.Integer'], single: true },
|
|
55
|
-
};
|
|
56
|
-
for (const name of [...Object.keys(definition.env ?? {}), ...(definition.callerEnv ?? [])]) {
|
|
52
|
+
const declared = { ...PROJECT_ROW_VARIABLES };
|
|
53
|
+
for (const name of [...Object.keys(definition.env ?? {}), ...definition.callerEnvNames]) {
|
|
57
54
|
declared[bareEnvironmentName(name)] = {};
|
|
58
55
|
}
|
|
56
|
+
Object.assign(declared, analyzerVariables(undefined, definition.callerEnvTypes));
|
|
59
57
|
const diagnostics = [];
|
|
60
58
|
const analyze = (member, expression, column) => {
|
|
61
59
|
const perExpression = {
|
|
@@ -79,12 +77,20 @@ export function analyzeDto(dto, options) {
|
|
|
79
77
|
span: { start: 0, end: expression.length, line: 1, column: 1 },
|
|
80
78
|
});
|
|
81
79
|
}
|
|
80
|
+
return result;
|
|
82
81
|
};
|
|
83
82
|
for (const [name, value] of Object.entries(definition.vars ?? {})) {
|
|
84
83
|
const source = typeof value === 'string' ? value : sourceOf(value);
|
|
85
84
|
if (source !== undefined) {
|
|
86
|
-
// A var
|
|
87
|
-
|
|
85
|
+
// A var has no declared result to cross-check, but its inferred result is
|
|
86
|
+
// the input state for later vars and columns.
|
|
87
|
+
const result = analyze(`vars.${bareEnvironmentName(name)}`, source);
|
|
88
|
+
declared[bareEnvironmentName(name)] = {
|
|
89
|
+
...(result.types !== undefined && { types: result.types }),
|
|
90
|
+
...(result.single !== undefined && { single: result.single }),
|
|
91
|
+
...(result.ordered !== undefined && { ordered: result.ordered }),
|
|
92
|
+
};
|
|
93
|
+
continue;
|
|
88
94
|
}
|
|
89
95
|
declared[bareEnvironmentName(name)] = {};
|
|
90
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-dto.js","sourceRoot":"","sources":["../../src/analyzer/analyze-dto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAkC,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAG1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACL,yBAAyB,GAK1B,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"analyze-dto.js","sourceRoot":"","sources":["../../src/analyzer/analyze-dto.ts"],"names":[],"mappings":"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"}
|
|
@@ -51,6 +51,8 @@ export interface AnalyzeOptions {
|
|
|
51
51
|
functions?: Record<string, DeclaredFunction>;
|
|
52
52
|
/** Host-supplied environment variables by name (with or without the leading `%`). */
|
|
53
53
|
variables?: Record<string, DeclaredVariable>;
|
|
54
|
+
/** Report navigation that remains unchecked because a declared host variable has no type. */
|
|
55
|
+
reportUnchecked?: boolean;
|
|
54
56
|
}
|
|
55
57
|
/** Internal strict-analysis options whose variables may retain exact runtime focus types. */
|
|
56
58
|
export interface RuntimeAnalyzeOptions extends Omit<AnalyzeOptions, 'variables'> {
|
|
@@ -59,16 +61,17 @@ export interface RuntimeAnalyzeOptions extends Omit<AnalyzeOptions, 'variables'>
|
|
|
59
61
|
export interface AnalysisDetails {
|
|
60
62
|
diagnostics: AnalyzerDiagnostic[];
|
|
61
63
|
/**
|
|
62
|
-
* The expression's inferred result: canonical type names
|
|
63
|
-
* same StaticState the checks run on. `types: undefined` means
|
|
64
|
-
* cannot see through the expression (an unknown region), and
|
|
65
|
-
* undefined means
|
|
66
|
-
* caller cross-check a declared type against what the
|
|
67
|
-
* yields (see `analyzeDto`).
|
|
64
|
+
* The expression's inferred result: canonical type names, cardinality, and
|
|
65
|
+
* ordering, the same StaticState the checks run on. `types: undefined` means
|
|
66
|
+
* the analyzer cannot see through the expression (an unknown region), and an
|
|
67
|
+
* undefined `single` or `ordered` means that fact is unknown — none of these
|
|
68
|
+
* is an error. Lets a caller cross-check a declared type against what the
|
|
69
|
+
* expression really yields (see `analyzeDto`).
|
|
68
70
|
*/
|
|
69
71
|
result: {
|
|
70
72
|
types: string[] | undefined;
|
|
71
73
|
single: boolean | undefined;
|
|
74
|
+
ordered: boolean | undefined;
|
|
72
75
|
};
|
|
73
76
|
/**
|
|
74
77
|
* `Type.element` paths the expression reads (local type names), deduped in
|
|
@@ -81,6 +84,7 @@ export interface AnalysisDetails {
|
|
|
81
84
|
export interface AnalyzerRoot {
|
|
82
85
|
types: string[] | undefined;
|
|
83
86
|
single: boolean | undefined;
|
|
87
|
+
ordered: boolean | undefined;
|
|
84
88
|
/** Canonical runtime types in focus order; an empty array means an exactly empty focus. */
|
|
85
89
|
exactTypes?: string[];
|
|
86
90
|
}
|
|
@@ -93,17 +97,24 @@ export interface AnalyzerRoot {
|
|
|
93
97
|
export declare function analyzeExpression(expression: string, options?: AnalyzeOptions): AnalyzerDiagnostic[];
|
|
94
98
|
/**
|
|
95
99
|
* Analyzes one source site with only facts visible in that file. A declared root
|
|
96
|
-
* does not prove which variables the later call provides.
|
|
97
|
-
*
|
|
98
|
-
* `
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
100
|
+
* does not prove which variables the later call provides. A site whose call
|
|
101
|
+
* binds variables the source cannot name (`openVariables`) may resolve any
|
|
102
|
+
* `%variable` at runtime, so an unresolved one is reported as an
|
|
103
|
+
* `unchecked-variable` warning under `reportUnchecked` instead of an error.
|
|
104
|
+
* DTO sites may also receive variables and functions from base classes, other
|
|
105
|
+
* modules, or `project()`. Such sites omit unknown-variable diagnostics and
|
|
106
|
+
* report an unknown function only when it resembles a local column. A DTO
|
|
107
|
+
* without a known root receives syntax checks only. Use `analyzeDto` when the
|
|
108
|
+
* class and engine are loaded.
|
|
102
109
|
*/
|
|
103
110
|
export declare function analyzeSite(site: {
|
|
104
111
|
expression: string;
|
|
105
112
|
inputType?: string;
|
|
106
113
|
dto?: true;
|
|
114
|
+
/** Variables declared by the expression's call site, such as inline env/vars options. */
|
|
115
|
+
variables?: Readonly<Record<string, DeclaredVariable>>;
|
|
116
|
+
/** The call also binds variables the source cannot name — a computed key or spread in env/vars. */
|
|
117
|
+
openVariables?: true;
|
|
107
118
|
/** Functions the site's file declares — a DTO's `@column` fields (see `columnFunctionDeclaration`). */
|
|
108
119
|
functions?: Readonly<Record<string, DeclaredFunction>>;
|
|
109
120
|
}, options?: AnalyzeOptions): AnalyzerDiagnostic[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAA;AAGhC,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAEnE,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAYjE,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAG3B,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAEL,KAAK,uBAAuB,
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyze.ts"],"names":[],"mappings":"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"}
|
package/dist/analyzer/analyze.js
CHANGED
|
@@ -8,14 +8,16 @@ import { FHIR_PRIMITIVE_TO_SYSTEM, typeLocalName } from "../values/typed-value.j
|
|
|
8
8
|
import { analyzerEnvironmentVariables, isRuntimeAnalyzerVariable, runtimeAnalyzerEnvironmentVariables, } from "./declarations.js";
|
|
9
9
|
import { applyOperatorResultRule, applyTypeOperatorResultRule } from "./operator-rules.js";
|
|
10
10
|
import { hasNestedUnboundedQuantifier } from "./regex-safety.js";
|
|
11
|
-
import { applyResultRule, FUNCTION_SIGNATURES, singleAnd, unionStates, withSingle, } from "./signatures.js";
|
|
12
|
-
|
|
11
|
+
import { applyResultRule, FUNCTION_SIGNATURES, singleAnd, singleState, singletonOrder, unionStates, withSingle, } from "./signatures.js";
|
|
12
|
+
import { SOURCE_VARIABLE_DEFAULTS } from "./source-options.js";
|
|
13
|
+
const UNKNOWN = { types: undefined, single: undefined, ordered: undefined };
|
|
13
14
|
/** A runtime-analyzed function result no longer has exact facts about its input focus. */
|
|
14
15
|
function callResult(state, runtime) {
|
|
15
16
|
return runtime
|
|
16
17
|
? {
|
|
17
18
|
types: state.types,
|
|
18
19
|
single: state.single,
|
|
20
|
+
ordered: state.ordered,
|
|
19
21
|
...(state.targets !== undefined && { targets: state.targets }),
|
|
20
22
|
}
|
|
21
23
|
: state;
|
|
@@ -37,23 +39,48 @@ export function analyzeExpression(expression, options) {
|
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Analyzes one source site with only facts visible in that file. A declared root
|
|
40
|
-
* does not prove which variables the later call provides.
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* does not prove which variables the later call provides. A site whose call
|
|
43
|
+
* binds variables the source cannot name (`openVariables`) may resolve any
|
|
44
|
+
* `%variable` at runtime, so an unresolved one is reported as an
|
|
45
|
+
* `unchecked-variable` warning under `reportUnchecked` instead of an error.
|
|
46
|
+
* DTO sites may also receive variables and functions from base classes, other
|
|
47
|
+
* modules, or `project()`. Such sites omit unknown-variable diagnostics and
|
|
48
|
+
* report an unknown function only when it resembles a local column. A DTO
|
|
49
|
+
* without a known root receives syntax checks only. Use `analyzeDto` when the
|
|
50
|
+
* class and engine are loaded.
|
|
46
51
|
*/
|
|
47
52
|
export function analyzeSite(site, options) {
|
|
53
|
+
const sourceVariables = options?.[SOURCE_VARIABLE_DEFAULTS];
|
|
54
|
+
const variablePlan = site.variablePlan;
|
|
48
55
|
const declared = { ...site.functions, ...options?.functions };
|
|
56
|
+
const variables = sourceSiteVariables(site.variables, variablePlan, options?.variables, sourceVariables);
|
|
49
57
|
const merged = {
|
|
50
58
|
...options,
|
|
51
59
|
...(site.inputType !== undefined && { inputType: site.inputType }),
|
|
52
60
|
...(Object.keys(declared).length > 0 && { functions: declared }),
|
|
61
|
+
...(variables !== undefined && { variables }),
|
|
53
62
|
};
|
|
54
63
|
const diagnostics = analyzeExpression(site.expression, merged);
|
|
55
64
|
if (site.dto !== true) {
|
|
56
|
-
|
|
65
|
+
if (site.openVariables === true) {
|
|
66
|
+
return diagnostics.flatMap(diagnostic => {
|
|
67
|
+
if (diagnostic.code !== 'unknown-variable') {
|
|
68
|
+
return [diagnostic];
|
|
69
|
+
}
|
|
70
|
+
if (options?.reportUnchecked !== true) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
...diagnostic,
|
|
76
|
+
severity: 'warning',
|
|
77
|
+
code: 'unchecked-variable',
|
|
78
|
+
message: `Environment variable %${diagnostic.name} was not checked: the call's env/vars names are not fully visible in source`,
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return site.inputType === undefined || options?.variables !== undefined
|
|
57
84
|
? diagnostics
|
|
58
85
|
: diagnostics.filter(diagnostic => diagnostic.code !== 'unknown-variable');
|
|
59
86
|
}
|
|
@@ -71,6 +98,33 @@ export function analyzeSite(site, options) {
|
|
|
71
98
|
return diagnostic.name !== undefined && nearestName(diagnostic.name, columns) !== undefined;
|
|
72
99
|
});
|
|
73
100
|
}
|
|
101
|
+
/** Recreate strict evaluation's vars prefix without flattening away engine/default insertion order. */
|
|
102
|
+
function sourceSiteVariables(siteEnvironment, plan, engineEnvironment, defaults) {
|
|
103
|
+
if (siteEnvironment === undefined &&
|
|
104
|
+
engineEnvironment === undefined &&
|
|
105
|
+
defaults === undefined &&
|
|
106
|
+
plan === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const variables = { ...engineEnvironment, ...siteEnvironment };
|
|
110
|
+
const declarations = plan?.inheritsDeclarations === false ? plan.declarations : { ...defaults?.declarations, ...plan?.declarations };
|
|
111
|
+
const valueNames = [...(defaults?.values ?? [])];
|
|
112
|
+
for (const name of plan?.values ?? []) {
|
|
113
|
+
if (!valueNames.includes(name)) {
|
|
114
|
+
valueNames.push(name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const end = plan?.before === undefined ? valueNames.length : valueNames.indexOf(plan.before);
|
|
118
|
+
for (const name of valueNames.slice(0, end < 0 ? valueNames.length : end)) {
|
|
119
|
+
variables[name] = declarations[name] ?? {};
|
|
120
|
+
}
|
|
121
|
+
if (plan?.before === undefined) {
|
|
122
|
+
for (const [name, declaration] of Object.entries(declarations)) {
|
|
123
|
+
variables[name] ??= declaration;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return variables;
|
|
127
|
+
}
|
|
74
128
|
/** `analyzeExpression` plus the element paths the expression touches and its inferred result type. */
|
|
75
129
|
export function analyzeExpressionDetailed(expression, options) {
|
|
76
130
|
let ast;
|
|
@@ -86,7 +140,7 @@ export function analyzeExpressionDetailed(expression, options) {
|
|
|
86
140
|
return {
|
|
87
141
|
diagnostics: [{ severity: 'error', code: 'syntax', message: error.message, span: error.span }],
|
|
88
142
|
elementDependencies: [],
|
|
89
|
-
result: { types: undefined, single: undefined },
|
|
143
|
+
result: { types: undefined, single: undefined, ordered: undefined },
|
|
90
144
|
};
|
|
91
145
|
}
|
|
92
146
|
return analyzeAstDetailed(ast, options);
|
|
@@ -98,7 +152,7 @@ function analyzeAstDetailed(ast, options, root) {
|
|
|
98
152
|
return {
|
|
99
153
|
diagnostics: analyzer.diagnostics,
|
|
100
154
|
elementDependencies: [...analyzer.dependencies],
|
|
101
|
-
result: { types: state.types, single: state.single },
|
|
155
|
+
result: { types: state.types, single: state.single, ordered: state.ordered },
|
|
102
156
|
};
|
|
103
157
|
}
|
|
104
158
|
/** Analyze a parsed expression using facts from the actual evaluation focus. */
|
|
@@ -116,15 +170,19 @@ class Analyzer {
|
|
|
116
170
|
customFunctions;
|
|
117
171
|
declaredVariables;
|
|
118
172
|
activeExpressionFunctions = new Set();
|
|
173
|
+
reportUnchecked;
|
|
119
174
|
constructor(options, root) {
|
|
120
175
|
this.model = options?.model;
|
|
121
176
|
this.runtime = root !== undefined;
|
|
177
|
+
this.reportUnchecked = options?.reportUnchecked === true;
|
|
122
178
|
const inputType = options?.inputType;
|
|
179
|
+
// The root focus is a real collection, so its order is defined even when
|
|
180
|
+
// its types and cardinality are not.
|
|
123
181
|
this.root =
|
|
124
182
|
root ??
|
|
125
183
|
(inputType === undefined
|
|
126
|
-
? { types: undefined, single: undefined }
|
|
127
|
-
:
|
|
184
|
+
? { types: undefined, single: undefined, ordered: true }
|
|
185
|
+
: singleState([this.model?.resolveType(inputType) ?? inputType]));
|
|
128
186
|
this.customFunctions = new Map(Object.entries(options?.functions ?? {}).map(([name, declared]) => [
|
|
129
187
|
name,
|
|
130
188
|
('overloads' in declared ? declared.overloads : [declared]).map(declaration => resolvedDeclaration(declaration, this.model, this.runtime)),
|
|
@@ -135,6 +193,7 @@ class Analyzer {
|
|
|
135
193
|
return {
|
|
136
194
|
types: this.root.types,
|
|
137
195
|
single: this.root.single,
|
|
196
|
+
ordered: this.root.ordered,
|
|
138
197
|
rawInput: true,
|
|
139
198
|
...(this.root.exactTypes !== undefined && { exactTypes: this.root.exactTypes }),
|
|
140
199
|
};
|
|
@@ -144,24 +203,21 @@ class Analyzer {
|
|
|
144
203
|
case 'null':
|
|
145
204
|
// The empty literal `{}` is statically at most one item, so it satisfies
|
|
146
205
|
// singleton operands (`{} + 1`, `{} and true` are spec-legal).
|
|
147
|
-
return
|
|
206
|
+
return singleState([]);
|
|
148
207
|
case 'boolean':
|
|
149
|
-
return
|
|
208
|
+
return singleState(['System.Boolean']);
|
|
150
209
|
case 'string':
|
|
151
|
-
return
|
|
210
|
+
return singleState(['System.String']);
|
|
152
211
|
case 'number':
|
|
153
|
-
return
|
|
154
|
-
types: [node.isLong ? 'System.Long' : node.isDecimal ? 'System.Decimal' : 'System.Integer'],
|
|
155
|
-
single: true,
|
|
156
|
-
};
|
|
212
|
+
return singleState([node.isLong ? 'System.Long' : node.isDecimal ? 'System.Decimal' : 'System.Integer']);
|
|
157
213
|
case 'date':
|
|
158
|
-
return
|
|
214
|
+
return singleState(['System.Date']);
|
|
159
215
|
case 'dateTime':
|
|
160
|
-
return
|
|
216
|
+
return singleState(['System.DateTime']);
|
|
161
217
|
case 'time':
|
|
162
|
-
return
|
|
218
|
+
return singleState(['System.Time']);
|
|
163
219
|
case 'quantity':
|
|
164
|
-
return
|
|
220
|
+
return singleState(['System.Quantity']);
|
|
165
221
|
case 'external':
|
|
166
222
|
return this.walkExternal(node, scope);
|
|
167
223
|
case 'special':
|
|
@@ -175,6 +231,7 @@ class Analyzer {
|
|
|
175
231
|
case 'indexer': {
|
|
176
232
|
const target = this.walk(node.target, input, scope);
|
|
177
233
|
const index = this.walk(node.index, input, scope);
|
|
234
|
+
this.requireOrdered(target, node.target.span, 'The indexer');
|
|
178
235
|
this.requireKind(index, 'Numeric', node.index.span, 'the indexer expects a single Integer');
|
|
179
236
|
return withSingle(target, true);
|
|
180
237
|
}
|
|
@@ -210,7 +267,7 @@ class Analyzer {
|
|
|
210
267
|
}
|
|
211
268
|
const declared = this.declaredVariables.get(node.name);
|
|
212
269
|
if (declared !== undefined) {
|
|
213
|
-
return this.declaredVariableState(declared);
|
|
270
|
+
return this.declaredVariableState(declared, node.name);
|
|
214
271
|
}
|
|
215
272
|
switch (node.name) {
|
|
216
273
|
case 'context':
|
|
@@ -221,13 +278,13 @@ class Analyzer {
|
|
|
221
278
|
case 'ucum':
|
|
222
279
|
case 'sct':
|
|
223
280
|
case 'loinc':
|
|
224
|
-
return
|
|
281
|
+
return singleState(['System.String']);
|
|
225
282
|
default:
|
|
226
283
|
break;
|
|
227
284
|
}
|
|
228
285
|
// FHIR-defined families expand to HL7 urls: %`vs-[name]`, %`ext-[name]`.
|
|
229
286
|
if (node.name.startsWith('vs-') || node.name.startsWith('ext-')) {
|
|
230
|
-
return
|
|
287
|
+
return singleState(['System.String']);
|
|
231
288
|
}
|
|
232
289
|
// A dynamically-named defineVariable() earlier in the chain may have bound
|
|
233
290
|
// this name, so reporting it as undefined could be wrong — stay quiet.
|
|
@@ -240,7 +297,7 @@ class Analyzer {
|
|
|
240
297
|
if (name === 'this') {
|
|
241
298
|
return this.frames.at(-1) ?? this.rootState();
|
|
242
299
|
}
|
|
243
|
-
return name === 'index' ?
|
|
300
|
+
return name === 'index' ? singleState(['System.Integer']) : UNKNOWN;
|
|
244
301
|
}
|
|
245
302
|
/**
|
|
246
303
|
* A quantity's components (spec §4: `value` and `unit`), the one System type
|
|
@@ -255,7 +312,7 @@ class Analyzer {
|
|
|
255
312
|
walkIdentifier(node, input) {
|
|
256
313
|
// Navigating from a statically empty input yields empty — nothing to check.
|
|
257
314
|
if (input.types !== undefined && input.types.length === 0) {
|
|
258
|
-
return
|
|
315
|
+
return singleState([]);
|
|
259
316
|
}
|
|
260
317
|
const exactTypes = input.exactTypes?.filter(type => rootTypeMatches(this.model, type, node.name));
|
|
261
318
|
if (exactTypes !== undefined && exactTypes.length > 0) {
|
|
@@ -263,6 +320,7 @@ class Analyzer {
|
|
|
263
320
|
return {
|
|
264
321
|
types: types === undefined ? undefined : [...new Set(types)],
|
|
265
322
|
single: input.single,
|
|
323
|
+
ordered: singletonOrder(input.single, input.ordered),
|
|
266
324
|
exactTypes,
|
|
267
325
|
...(input.rawInput === true && { rawInput: true }),
|
|
268
326
|
};
|
|
@@ -273,14 +331,16 @@ class Analyzer {
|
|
|
273
331
|
const asType = this.model?.resolveType(node.name) ?? resolveSystemTypeName(node.name);
|
|
274
332
|
if (asType !== undefined) {
|
|
275
333
|
return {
|
|
276
|
-
|
|
277
|
-
single: true,
|
|
334
|
+
...singleState([asType]),
|
|
278
335
|
...(input.exactTypes !== undefined && {
|
|
279
336
|
exactTypes: input.exactTypes.filter(type => this.model?.isSubtypeOf(type, asType) === true),
|
|
280
337
|
}),
|
|
281
338
|
};
|
|
282
339
|
}
|
|
283
|
-
|
|
340
|
+
if (input.opaqueVariable !== undefined && this.reportUnchecked) {
|
|
341
|
+
this.report('unchecked-navigation', `Element '${node.name}' after untyped environment variable %${input.opaqueVariable} was not checked; declare the variable's type`, node.span, 'warning', node.name);
|
|
342
|
+
}
|
|
343
|
+
return { ...UNKNOWN, ordered: input.ordered };
|
|
284
344
|
}
|
|
285
345
|
// Root rule: an identifier naming the (super)type of the context is the context.
|
|
286
346
|
{
|
|
@@ -335,9 +395,14 @@ class Analyzer {
|
|
|
335
395
|
: didYouMean(node.name, this.elementNames(input.types));
|
|
336
396
|
this.report('unknown-element', `Element '${node.name}' is not defined on ${input.types.join(' | ')}${hint}`, node.span, 'error', node.name);
|
|
337
397
|
}
|
|
338
|
-
return UNKNOWN;
|
|
398
|
+
return { ...UNKNOWN, ordered: input.ordered };
|
|
339
399
|
}
|
|
340
|
-
const
|
|
400
|
+
const single = singleAnd(input.single, !isCollection);
|
|
401
|
+
const state = {
|
|
402
|
+
types: [...new Set(found)],
|
|
403
|
+
single,
|
|
404
|
+
ordered: singletonOrder(single, input.ordered),
|
|
405
|
+
};
|
|
341
406
|
if (targets !== undefined && targets.length > 0) {
|
|
342
407
|
state.targets = [...new Set(targets)];
|
|
343
408
|
}
|
|
@@ -436,7 +501,11 @@ class Analyzer {
|
|
|
436
501
|
// ofType(X) filters and as(X) casts: both narrow to the named type,
|
|
437
502
|
// intersected with the known candidates.
|
|
438
503
|
if ((node.name === 'ofType' || node.name === 'as') && typeTarget !== undefined) {
|
|
439
|
-
return {
|
|
504
|
+
return {
|
|
505
|
+
types: this.narrowTypes(input, typeTarget, node.span),
|
|
506
|
+
single: input.single,
|
|
507
|
+
ordered: singletonOrder(input.single, input.ordered),
|
|
508
|
+
};
|
|
440
509
|
}
|
|
441
510
|
const expressionResult = this.expressionFunctionResult(node.name, custom, input, scope);
|
|
442
511
|
if (expressionResult !== undefined) {
|
|
@@ -458,7 +527,7 @@ class Analyzer {
|
|
|
458
527
|
const bodyInput = this.expressionBodyInput(declaration, input);
|
|
459
528
|
if (declaration.criteria === true) {
|
|
460
529
|
this.walkExpressionFunction(name, expression, declaration.variables, bodyInput, scope);
|
|
461
|
-
return
|
|
530
|
+
return singleState(['System.Boolean']);
|
|
462
531
|
}
|
|
463
532
|
if (declaration.signature?.result === undefined) {
|
|
464
533
|
return this.walkExpressionFunction(name, expression, declaration.variables, bodyInput, scope);
|
|
@@ -483,6 +552,7 @@ class Analyzer {
|
|
|
483
552
|
return {
|
|
484
553
|
types: [...new Set(types)],
|
|
485
554
|
single: callInput.single,
|
|
555
|
+
ordered: callInput.ordered,
|
|
486
556
|
...(callInput.targets !== undefined && { targets: callInput.targets }),
|
|
487
557
|
};
|
|
488
558
|
}
|
|
@@ -505,7 +575,7 @@ class Analyzer {
|
|
|
505
575
|
const bare = bareEnvironmentName(declaredName);
|
|
506
576
|
// defineVariable() values have priority over environment overlays at runtime.
|
|
507
577
|
if (!functionScope.vars.has(bare)) {
|
|
508
|
-
functionScope.vars.set(bare, this.declaredVariableState(variable));
|
|
578
|
+
functionScope.vars.set(bare, this.declaredVariableState(variable, bare));
|
|
509
579
|
}
|
|
510
580
|
}
|
|
511
581
|
const diagnosticCount = this.diagnostics.length;
|
|
@@ -529,13 +599,15 @@ class Analyzer {
|
|
|
529
599
|
}
|
|
530
600
|
}
|
|
531
601
|
/** Convert one declared environment value into the same state used for scoped variables. */
|
|
532
|
-
declaredVariableState(declared) {
|
|
602
|
+
declaredVariableState(declared, name) {
|
|
533
603
|
const exactTypes = this.runtime && isRuntimeAnalyzerVariable(declared) ? declared.exactTypes : undefined;
|
|
534
604
|
return {
|
|
535
605
|
types: declared.types?.map(type => this.canonicalize(type)),
|
|
536
606
|
single: declared.single,
|
|
607
|
+
ordered: singletonOrder(declared.single, declared.ordered),
|
|
537
608
|
...(declared.targets !== undefined && { targets: declared.targets.map(type => this.canonicalize(type)) }),
|
|
538
609
|
...(exactTypes !== undefined && { exactTypes }),
|
|
610
|
+
...(declared.types === undefined && name !== undefined && { opaqueVariable: name }),
|
|
539
611
|
};
|
|
540
612
|
}
|
|
541
613
|
/**
|
|
@@ -574,11 +646,14 @@ class Analyzer {
|
|
|
574
646
|
this.report('wrong-arity', `Function '${node.name}' expects ${describeArity(minArity, maxArity)}, got ${node.args.length}`, node.span);
|
|
575
647
|
}
|
|
576
648
|
}
|
|
577
|
-
/** The signature's input constraints: cardinality, value kind, and declared types. */
|
|
649
|
+
/** The signature's input constraints: cardinality, ordering, value kind, and declared types. */
|
|
578
650
|
checkCallInput(node, signature, input) {
|
|
579
651
|
if (!signature.input) {
|
|
580
652
|
return;
|
|
581
653
|
}
|
|
654
|
+
if (signature.input.ordered === true) {
|
|
655
|
+
this.requireOrdered(input, node.span, `${node.name}()`);
|
|
656
|
+
}
|
|
582
657
|
// A function written for one type (a DTO's `@column`), called on a focus
|
|
583
658
|
// that can never be that type. `unsatisfiedInput` holds the same rule the
|
|
584
659
|
// engine applies, so the two halves agree on what counts as a mistake.
|
|
@@ -590,7 +665,7 @@ class Analyzer {
|
|
|
590
665
|
this.report('singleton-required', `${node.name}() expects a single item as input, but this is a collection (spec §11)${NARROW_HINT}`, node.span);
|
|
591
666
|
}
|
|
592
667
|
if (signature.input.kind) {
|
|
593
|
-
this.requireKind(
|
|
668
|
+
this.requireKind(singleState(input.types), signature.input.kind, node.span, `${node.name}() expects a ${signature.input.kind} input`);
|
|
594
669
|
}
|
|
595
670
|
}
|
|
596
671
|
/**
|
|
@@ -692,14 +767,14 @@ class Analyzer {
|
|
|
692
767
|
return undefined;
|
|
693
768
|
}
|
|
694
769
|
const types = declared.result?.types?.map(type => this.canonicalize(type));
|
|
695
|
-
const single = declared.result?.single;
|
|
696
770
|
return {
|
|
697
771
|
...(declared.input !== undefined && { input: declared.input }),
|
|
698
772
|
...(declared.args !== undefined && { args: declared.args }),
|
|
699
773
|
result: {
|
|
700
774
|
kind: 'fixed',
|
|
701
775
|
...(types !== undefined && { types }),
|
|
702
|
-
...(single !== undefined && { single }),
|
|
776
|
+
...(declared.result?.single !== undefined && { single: declared.result.single }),
|
|
777
|
+
...(declared.result?.ordered !== undefined && { ordered: declared.result.ordered }),
|
|
703
778
|
},
|
|
704
779
|
};
|
|
705
780
|
}
|
|
@@ -831,6 +906,12 @@ class Analyzer {
|
|
|
831
906
|
this.report('singleton-required', `${message}${NARROW_HINT}`, span);
|
|
832
907
|
}
|
|
833
908
|
}
|
|
909
|
+
/** Report an order-dependent operation only when the input is provably unordered. */
|
|
910
|
+
requireOrdered(state, span, subject) {
|
|
911
|
+
if (state.ordered === false && state.single !== true) {
|
|
912
|
+
this.report('order-dependent', `${subject} depends on collection order, but the input collection has no defined order`, span);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
834
915
|
walkTypeOp(node, input, scope) {
|
|
835
916
|
const operand = this.walk(node.operand, input, scope);
|
|
836
917
|
this.requireSingle(operand, node.operand.span, `'${node.operator}' expects a single item operand`);
|
|
@@ -959,26 +1040,37 @@ function mergedDeclaration(candidates) {
|
|
|
959
1040
|
}),
|
|
960
1041
|
};
|
|
961
1042
|
}
|
|
962
|
-
/**
|
|
1043
|
+
/**
|
|
1044
|
+
* Every type any declaration accepts, or no type constraint when one of them
|
|
1045
|
+
* accepts anything. An ordered input stays required when every candidate
|
|
1046
|
+
* requires it — whichever declaration the call resolves to would reject an
|
|
1047
|
+
* unordered focus, so keeping the requirement cannot flag valid code.
|
|
1048
|
+
*/
|
|
963
1049
|
function mergedInput(candidates) {
|
|
1050
|
+
const ordered = candidates.every(candidate => candidate.signature?.input?.ordered === true);
|
|
964
1051
|
const declared = candidates.map(candidate => candidate.signature?.input?.types);
|
|
965
|
-
|
|
1052
|
+
const types = declared.some(types => types === undefined)
|
|
1053
|
+
? undefined
|
|
1054
|
+
: [...new Set(declared.flatMap(types => types ?? []))];
|
|
1055
|
+
if (types === undefined && !ordered) {
|
|
966
1056
|
return undefined;
|
|
967
1057
|
}
|
|
968
|
-
return {
|
|
1058
|
+
return { ...(types !== undefined && { types }), ...(ordered && { ordered: true }) };
|
|
969
1059
|
}
|
|
970
1060
|
/** The union of the declarations' results, unknown as soon as one of them is. */
|
|
971
1061
|
function mergedResult(candidates) {
|
|
972
1062
|
const union = unionStates(candidates.map(candidate => ({
|
|
973
1063
|
types: candidate.signature?.result?.types === undefined ? undefined : [...candidate.signature.result.types],
|
|
974
1064
|
single: candidate.signature?.result?.single,
|
|
1065
|
+
ordered: singletonOrder(candidate.signature?.result?.single, candidate.signature?.result?.ordered),
|
|
975
1066
|
})));
|
|
976
|
-
if (union.types === undefined && union.single === undefined) {
|
|
1067
|
+
if (union.types === undefined && union.single === undefined && union.ordered === undefined) {
|
|
977
1068
|
return undefined;
|
|
978
1069
|
}
|
|
979
1070
|
return {
|
|
980
1071
|
...(union.types !== undefined && { types: union.types }),
|
|
981
1072
|
...(union.single !== undefined && { single: union.single }),
|
|
1073
|
+
...(union.ordered !== undefined && { ordered: union.ordered }),
|
|
982
1074
|
};
|
|
983
1075
|
}
|
|
984
1076
|
function typeSpecifierParts(node) {
|