fhirpath-ts 0.0.0-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +391 -0
- package/THIRD-PARTY-NOTICES.md +296 -0
- package/dist/analyzer/analyze-dto.d.ts +45 -0
- package/dist/analyzer/analyze-dto.d.ts.map +1 -0
- package/dist/analyzer/analyze-dto.js +147 -0
- package/dist/analyzer/analyze-dto.js.map +1 -0
- package/dist/analyzer/analyze.d.ts +98 -0
- package/dist/analyzer/analyze.d.ts.map +1 -0
- package/dist/analyzer/analyze.js +925 -0
- package/dist/analyzer/analyze.js.map +1 -0
- package/dist/analyzer/expression-policy.d.ts +210 -0
- package/dist/analyzer/expression-policy.d.ts.map +1 -0
- package/dist/analyzer/expression-policy.js +254 -0
- package/dist/analyzer/expression-policy.js.map +1 -0
- package/dist/analyzer/index.d.ts +3 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +6 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/regex-safety.d.ts +10 -0
- package/dist/analyzer/regex-safety.d.ts.map +1 -0
- package/dist/analyzer/regex-safety.js +84 -0
- package/dist/analyzer/regex-safety.js.map +1 -0
- package/dist/analyzer/signatures.d.ts +72 -0
- package/dist/analyzer/signatures.d.ts.map +1 -0
- package/dist/analyzer/signatures.js +195 -0
- package/dist/analyzer/signatures.js.map +1 -0
- package/dist/api/bundle.d.ts +29 -0
- package/dist/api/bundle.d.ts.map +1 -0
- package/dist/api/bundle.js +108 -0
- package/dist/api/bundle.js.map +1 -0
- package/dist/api/cache.d.ts +10 -0
- package/dist/api/cache.d.ts.map +1 -0
- package/dist/api/cache.js +38 -0
- package/dist/api/cache.js.map +1 -0
- package/dist/api/column-signature.d.ts +53 -0
- package/dist/api/column-signature.d.ts.map +1 -0
- package/dist/api/column-signature.js +43 -0
- package/dist/api/column-signature.js.map +1 -0
- package/dist/api/compile.d.ts +120 -0
- package/dist/api/compile.d.ts.map +1 -0
- package/dist/api/compile.js +167 -0
- package/dist/api/compile.js.map +1 -0
- package/dist/api/constraints.d.ts +48 -0
- package/dist/api/constraints.d.ts.map +1 -0
- package/dist/api/constraints.js +52 -0
- package/dist/api/constraints.js.map +1 -0
- package/dist/api/dto.d.ts +134 -0
- package/dist/api/dto.d.ts.map +1 -0
- package/dist/api/dto.js +295 -0
- package/dist/api/dto.js.map +1 -0
- package/dist/api/engine.d.ts +136 -0
- package/dist/api/engine.d.ts.map +1 -0
- package/dist/api/engine.js +183 -0
- package/dist/api/engine.js.map +1 -0
- package/dist/api/evaluate.d.ts +7 -0
- package/dist/api/evaluate.d.ts.map +1 -0
- package/dist/api/evaluate.js +10 -0
- package/dist/api/evaluate.js.map +1 -0
- package/dist/api/project.d.ts +89 -0
- package/dist/api/project.d.ts.map +1 -0
- package/dist/api/project.js +150 -0
- package/dist/api/project.js.map +1 -0
- package/dist/api/tagged.d.ts +14 -0
- package/dist/api/tagged.d.ts.map +1 -0
- package/dist/api/tagged.js +13 -0
- package/dist/api/tagged.js.map +1 -0
- package/dist/cli/dto-check.d.ts +25 -0
- package/dist/cli/dto-check.d.ts.map +1 -0
- package/dist/cli/dto-check.js +77 -0
- package/dist/cli/dto-check.js.map +1 -0
- package/dist/cli/fhirpath-check.d.ts +3 -0
- package/dist/cli/fhirpath-check.d.ts.map +1 -0
- package/dist/cli/fhirpath-check.js +149 -0
- package/dist/cli/fhirpath-check.js.map +1 -0
- package/dist/cli/ts-loader.d.mts +3 -0
- package/dist/cli/ts-loader.d.mts.map +1 -0
- package/dist/cli/ts-loader.mjs +48 -0
- package/dist/cli/ts-loader.mjs.map +1 -0
- package/dist/engine/context.d.ts +173 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +118 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/evaluator.d.ts +7 -0
- package/dist/engine/evaluator.d.ts.map +1 -0
- package/dist/engine/evaluator.js +196 -0
- package/dist/engine/evaluator.js.map +1 -0
- package/dist/engine/navigation.d.ts +11 -0
- package/dist/engine/navigation.d.ts.map +1 -0
- package/dist/engine/navigation.js +102 -0
- package/dist/engine/navigation.js.map +1 -0
- package/dist/engine/operators/collections.d.ts +12 -0
- package/dist/engine/operators/collections.d.ts.map +1 -0
- package/dist/engine/operators/collections.js +31 -0
- package/dist/engine/operators/collections.js.map +1 -0
- package/dist/engine/operators/comparison.d.ts +13 -0
- package/dist/engine/operators/comparison.d.ts.map +1 -0
- package/dist/engine/operators/comparison.js +77 -0
- package/dist/engine/operators/comparison.js.map +1 -0
- package/dist/engine/operators/equality.d.ts +19 -0
- package/dist/engine/operators/equality.d.ts.map +1 -0
- package/dist/engine/operators/equality.js +203 -0
- package/dist/engine/operators/equality.js.map +1 -0
- package/dist/engine/operators/index.d.ts +18 -0
- package/dist/engine/operators/index.d.ts.map +1 -0
- package/dist/engine/operators/index.js +30 -0
- package/dist/engine/operators/index.js.map +1 -0
- package/dist/engine/operators/logic.d.ts +8 -0
- package/dist/engine/operators/logic.d.ts.map +1 -0
- package/dist/engine/operators/logic.js +38 -0
- package/dist/engine/operators/logic.js.map +1 -0
- package/dist/engine/operators/math.d.ts +13 -0
- package/dist/engine/operators/math.d.ts.map +1 -0
- package/dist/engine/operators/math.js +198 -0
- package/dist/engine/operators/math.js.map +1 -0
- package/dist/engine/operators/strings.d.ts +8 -0
- package/dist/engine/operators/strings.d.ts.map +1 -0
- package/dist/engine/operators/strings.js +22 -0
- package/dist/engine/operators/strings.js.map +1 -0
- package/dist/engine/operators/types.d.ts +3 -0
- package/dist/engine/operators/types.d.ts.map +1 -0
- package/dist/engine/operators/types.js +15 -0
- package/dist/engine/operators/types.js.map +1 -0
- package/dist/engine/type-matching.d.ts +20 -0
- package/dist/engine/type-matching.d.ts.map +1 -0
- package/dist/engine/type-matching.js +122 -0
- package/dist/engine/type-matching.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint/index.d.ts +12 -0
- package/dist/eslint/index.d.ts.map +1 -0
- package/dist/eslint/index.js +316 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/fhir/html-checks.d.ts +7 -0
- package/dist/fhir/html-checks.d.ts.map +1 -0
- package/dist/fhir/html-checks.js +256 -0
- package/dist/fhir/html-checks.js.map +1 -0
- package/dist/fhir/model-navigation.d.ts +11 -0
- package/dist/fhir/model-navigation.d.ts.map +1 -0
- package/dist/fhir/model-navigation.js +142 -0
- package/dist/fhir/model-navigation.js.map +1 -0
- package/dist/functions/aggregate.d.ts +2 -0
- package/dist/functions/aggregate.d.ts.map +1 -0
- package/dist/functions/aggregate.js +127 -0
- package/dist/functions/aggregate.js.map +1 -0
- package/dist/functions/boundary.d.ts +2 -0
- package/dist/functions/boundary.d.ts.map +1 -0
- package/dist/functions/boundary.js +189 -0
- package/dist/functions/boundary.js.map +1 -0
- package/dist/functions/combining.d.ts +2 -0
- package/dist/functions/combining.d.ts.map +1 -0
- package/dist/functions/combining.js +13 -0
- package/dist/functions/combining.js.map +1 -0
- package/dist/functions/control.d.ts +2 -0
- package/dist/functions/control.d.ts.map +1 -0
- package/dist/functions/control.js +25 -0
- package/dist/functions/control.js.map +1 -0
- package/dist/functions/conversion.d.ts +3 -0
- package/dist/functions/conversion.d.ts.map +1 -0
- package/dist/functions/conversion.js +316 -0
- package/dist/functions/conversion.js.map +1 -0
- package/dist/functions/date-components.d.ts +2 -0
- package/dist/functions/date-components.d.ts.map +1 -0
- package/dist/functions/date-components.js +81 -0
- package/dist/functions/date-components.js.map +1 -0
- package/dist/functions/existence.d.ts +4 -0
- package/dist/functions/existence.d.ts.map +1 -0
- package/dist/functions/existence.js +97 -0
- package/dist/functions/existence.js.map +1 -0
- package/dist/functions/fhir.d.ts +2 -0
- package/dist/functions/fhir.d.ts.map +1 -0
- package/dist/functions/fhir.js +222 -0
- package/dist/functions/fhir.js.map +1 -0
- package/dist/functions/filtering.d.ts +2 -0
- package/dist/functions/filtering.d.ts.map +1 -0
- package/dist/functions/filtering.js +112 -0
- package/dist/functions/filtering.js.map +1 -0
- package/dist/functions/install.d.ts +18 -0
- package/dist/functions/install.d.ts.map +1 -0
- package/dist/functions/install.js +20 -0
- package/dist/functions/install.js.map +1 -0
- package/dist/functions/iteration.d.ts +7 -0
- package/dist/functions/iteration.d.ts.map +1 -0
- package/dist/functions/iteration.js +9 -0
- package/dist/functions/iteration.js.map +1 -0
- package/dist/functions/math.d.ts +2 -0
- package/dist/functions/math.d.ts.map +1 -0
- package/dist/functions/math.js +152 -0
- package/dist/functions/math.js.map +1 -0
- package/dist/functions/reflection.d.ts +2 -0
- package/dist/functions/reflection.d.ts.map +1 -0
- package/dist/functions/reflection.js +23 -0
- package/dist/functions/reflection.js.map +1 -0
- package/dist/functions/registry.d.ts +28 -0
- package/dist/functions/registry.d.ts.map +1 -0
- package/dist/functions/registry.js +46 -0
- package/dist/functions/registry.js.map +1 -0
- package/dist/functions/sort.d.ts +2 -0
- package/dist/functions/sort.d.ts.map +1 -0
- package/dist/functions/sort.js +48 -0
- package/dist/functions/sort.js.map +1 -0
- package/dist/functions/string.d.ts +2 -0
- package/dist/functions/string.d.ts.map +1 -0
- package/dist/functions/string.js +353 -0
- package/dist/functions/string.js.map +1 -0
- package/dist/functions/subsetting.d.ts +2 -0
- package/dist/functions/subsetting.d.ts.map +1 -0
- package/dist/functions/subsetting.js +72 -0
- package/dist/functions/subsetting.js.map +1 -0
- package/dist/functions/tree.d.ts +8 -0
- package/dist/functions/tree.d.ts.map +1 -0
- package/dist/functions/tree.js +109 -0
- package/dist/functions/tree.js.map +1 -0
- package/dist/functions/type-specifier.d.ts +8 -0
- package/dist/functions/type-specifier.d.ts.map +1 -0
- package/dist/functions/type-specifier.js +27 -0
- package/dist/functions/type-specifier.js.map +1 -0
- package/dist/functions/utility.d.ts +2 -0
- package/dist/functions/utility.d.ts.map +1 -0
- package/dist/functions/utility.js +76 -0
- package/dist/functions/utility.js.map +1 -0
- package/dist/functions/variables.d.ts +2 -0
- package/dist/functions/variables.d.ts.map +1 -0
- package/dist/functions/variables.js +35 -0
- package/dist/functions/variables.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts +4 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js +318 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/tokens.d.ts +23 -0
- package/dist/lexer/tokens.d.ts.map +1 -0
- package/dist/lexer/tokens.js +40 -0
- package/dist/lexer/tokens.js.map +1 -0
- package/dist/model/provider.d.ts +36 -0
- package/dist/model/provider.d.ts.map +1 -0
- package/dist/model/provider.js +2 -0
- package/dist/model/provider.js.map +1 -0
- package/dist/parser/ast.d.ts +104 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +2 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/parser.d.ts +4 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +299 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/precedence.d.ts +24 -0
- package/dist/parser/precedence.d.ts.map +1 -0
- package/dist/parser/precedence.js +52 -0
- package/dist/parser/precedence.js.map +1 -0
- package/dist/parser/printer.d.ts +4 -0
- package/dist/parser/printer.d.ts.map +1 -0
- package/dist/parser/printer.js +112 -0
- package/dist/parser/printer.js.map +1 -0
- package/dist/r4/generated/resources-data.d.ts +3 -0
- package/dist/r4/generated/resources-data.d.ts.map +1 -0
- package/dist/r4/generated/resources-data.js +28628 -0
- package/dist/r4/generated/resources-data.js.map +1 -0
- package/dist/r4/generated/type-maps.d.ts +43153 -0
- package/dist/r4/generated/type-maps.d.ts.map +1 -0
- package/dist/r4/generated/type-maps.js +4 -0
- package/dist/r4/generated/type-maps.js.map +1 -0
- package/dist/r4/generated/types-data.d.ts +3 -0
- package/dist/r4/generated/types-data.d.ts.map +1 -0
- package/dist/r4/generated/types-data.js +2205 -0
- package/dist/r4/generated/types-data.js.map +1 -0
- package/dist/r4/index.d.ts +9 -0
- package/dist/r4/index.d.ts.map +1 -0
- package/dist/r4/index.js +94 -0
- package/dist/r4/index.js.map +1 -0
- package/dist/r4/model-data.d.ts +17 -0
- package/dist/r4/model-data.d.ts.map +1 -0
- package/dist/r4/model-data.js +2 -0
- package/dist/r4/model-data.js.map +1 -0
- package/dist/sites/index.d.ts +31 -0
- package/dist/sites/index.d.ts.map +1 -0
- package/dist/sites/index.js +205 -0
- package/dist/sites/index.js.map +1 -0
- package/dist/typed/infer.d.ts +234 -0
- package/dist/typed/infer.d.ts.map +1 -0
- package/dist/typed/infer.js +85 -0
- package/dist/typed/infer.js.map +1 -0
- package/dist/typed/perf-fixture.types.d.ts +136 -0
- package/dist/typed/perf-fixture.types.d.ts.map +1 -0
- package/dist/typed/perf-fixture.types.js +2 -0
- package/dist/typed/perf-fixture.types.js.map +1 -0
- package/dist/values/collection.d.ts +24 -0
- package/dist/values/collection.d.ts.map +1 -0
- package/dist/values/collection.js +51 -0
- package/dist/values/collection.js.map +1 -0
- package/dist/values/datetime.d.ts +54 -0
- package/dist/values/datetime.d.ts.map +1 -0
- package/dist/values/datetime.js +226 -0
- package/dist/values/datetime.js.map +1 -0
- package/dist/values/decimal.d.ts +48 -0
- package/dist/values/decimal.d.ts.map +1 -0
- package/dist/values/decimal.js +188 -0
- package/dist/values/decimal.js.map +1 -0
- package/dist/values/numeric.d.ts +24 -0
- package/dist/values/numeric.d.ts.map +1 -0
- package/dist/values/numeric.js +53 -0
- package/dist/values/numeric.js.map +1 -0
- package/dist/values/quantity.d.ts +49 -0
- package/dist/values/quantity.d.ts.map +1 -0
- package/dist/values/quantity.js +297 -0
- package/dist/values/quantity.js.map +1 -0
- package/dist/values/temporal-arithmetic.d.ts +17 -0
- package/dist/values/temporal-arithmetic.d.ts.map +1 -0
- package/dist/values/temporal-arithmetic.js +183 -0
- package/dist/values/temporal-arithmetic.js.map +1 -0
- package/dist/values/temporal-compare.d.ts +9 -0
- package/dist/values/temporal-compare.d.ts.map +1 -0
- package/dist/values/temporal-compare.js +59 -0
- package/dist/values/temporal-compare.js.map +1 -0
- package/dist/values/type-compat.d.ts +47 -0
- package/dist/values/type-compat.d.ts.map +1 -0
- package/dist/values/type-compat.js +100 -0
- package/dist/values/type-compat.js.map +1 -0
- package/dist/values/typed-value.d.ts +48 -0
- package/dist/values/typed-value.d.ts.map +1 -0
- package/dist/values/typed-value.js +117 -0
- package/dist/values/typed-value.js.map +1 -0
- package/dist/values/ucum.d.ts +14 -0
- package/dist/values/ucum.d.ts.map +1 -0
- package/dist/values/ucum.js +146 -0
- package/dist/values/ucum.js.map +1 -0
- package/package.json +137 -0
- package/src/analyzer/analyze-dto.ts +195 -0
- package/src/analyzer/analyze.ts +1181 -0
- package/src/analyzer/expression-policy.ts +434 -0
- package/src/analyzer/index.ts +23 -0
- package/src/analyzer/regex-safety.ts +85 -0
- package/src/analyzer/signatures.ts +260 -0
- package/src/api/bundle.ts +132 -0
- package/src/api/cache.ts +40 -0
- package/src/api/column-signature.ts +71 -0
- package/src/api/compile.ts +308 -0
- package/src/api/constraints.ts +104 -0
- package/src/api/dto.ts +445 -0
- package/src/api/engine.ts +328 -0
- package/src/api/evaluate.ts +11 -0
- package/src/api/project.ts +255 -0
- package/src/api/tagged.ts +35 -0
- package/src/cli/dto-check.ts +94 -0
- package/src/cli/fhirpath-check.ts +168 -0
- package/src/cli/ts-loader.mjs +49 -0
- package/src/engine/context.ts +282 -0
- package/src/engine/evaluator.ts +246 -0
- package/src/engine/navigation.ts +106 -0
- package/src/engine/operators/collections.ts +34 -0
- package/src/engine/operators/comparison.ts +82 -0
- package/src/engine/operators/equality.ts +232 -0
- package/src/engine/operators/index.ts +65 -0
- package/src/engine/operators/logic.ts +53 -0
- package/src/engine/operators/math.ts +225 -0
- package/src/engine/operators/strings.ts +25 -0
- package/src/engine/operators/types.ts +16 -0
- package/src/engine/type-matching.ts +148 -0
- package/src/errors.ts +33 -0
- package/src/eslint/index.ts +388 -0
- package/src/fhir/html-checks.ts +266 -0
- package/src/fhir/model-navigation.ts +148 -0
- package/src/functions/aggregate.ts +136 -0
- package/src/functions/boundary.ts +209 -0
- package/src/functions/combining.ts +15 -0
- package/src/functions/control.ts +25 -0
- package/src/functions/conversion.ts +349 -0
- package/src/functions/date-components.ts +88 -0
- package/src/functions/existence.ts +107 -0
- package/src/functions/fhir.ts +247 -0
- package/src/functions/filtering.ts +120 -0
- package/src/functions/install.ts +19 -0
- package/src/functions/iteration.ts +21 -0
- package/src/functions/math.ts +167 -0
- package/src/functions/reflection.ts +24 -0
- package/src/functions/registry.ts +72 -0
- package/src/functions/sort.ts +68 -0
- package/src/functions/string.ts +413 -0
- package/src/functions/subsetting.ts +89 -0
- package/src/functions/tree.ts +112 -0
- package/src/functions/type-specifier.ts +29 -0
- package/src/functions/utility.ts +82 -0
- package/src/functions/variables.ts +36 -0
- package/src/index.ts +30 -0
- package/src/lexer/lexer.ts +341 -0
- package/src/lexer/tokens.ts +69 -0
- package/src/model/provider.ts +36 -0
- package/src/parser/ast.ts +164 -0
- package/src/parser/parser.ts +321 -0
- package/src/parser/precedence.ts +52 -0
- package/src/parser/printer.ts +114 -0
- package/src/r4/generated/resources-data.ts +28631 -0
- package/src/r4/generated/type-maps.ts +22145 -0
- package/src/r4/generated/types-data.ts +2208 -0
- package/src/r4/index.ts +107 -0
- package/src/r4/model-data.ts +17 -0
- package/src/sites/index.ts +285 -0
- package/src/typed/infer.ts +433 -0
- package/src/typed/perf-fixture.types.ts +143 -0
- package/src/values/collection.ts +57 -0
- package/src/values/datetime.ts +285 -0
- package/src/values/decimal.ts +213 -0
- package/src/values/numeric.ts +63 -0
- package/src/values/quantity.ts +358 -0
- package/src/values/temporal-arithmetic.ts +231 -0
- package/src/values/temporal-compare.ts +65 -0
- package/src/values/type-compat.ts +133 -0
- package/src/values/typed-value.ts +151 -0
- package/src/values/ucum.ts +181 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared expression-site rules for the TypeScript and ESTree walkers. This file
|
|
3
|
+
* owns call names, argument shapes, receiver checks, and site context. Walkers
|
|
4
|
+
* provide AST access only. It must not import TypeScript or ESLint at runtime.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
type ColumnFunctionSignature,
|
|
8
|
+
columnSignature,
|
|
9
|
+
type ColumnTypeClaim,
|
|
10
|
+
criteriaSignature,
|
|
11
|
+
} from '../api/column-signature.ts'
|
|
12
|
+
|
|
13
|
+
/** The supported expression argument shapes: one string, columns, constraints, or DTO variables. */
|
|
14
|
+
export type CallSiteShape = 'expression' | 'columns' | 'constraints' | 'dto-vars'
|
|
15
|
+
|
|
16
|
+
export interface CallSitePolicy {
|
|
17
|
+
/** Which argument holds the expression(s): 0 for expression-first calls, 1 for subject-first helpers. */
|
|
18
|
+
argIndex: number
|
|
19
|
+
shape: CallSiteShape
|
|
20
|
+
/**
|
|
21
|
+
* Required receiver evidence. `any` accepts bare or member calls unless a
|
|
22
|
+
* foreign import owns the name. `engine` requires a package import or local
|
|
23
|
+
* `FhirPathEngine`. `import` requires the called name to come from this package.
|
|
24
|
+
*/
|
|
25
|
+
receiver: 'any' | 'engine' | 'import'
|
|
26
|
+
/**
|
|
27
|
+
* The argument that names the type the expression runs against, when the call
|
|
28
|
+
* takes one: `fhirpath('status', 'MedicationRequest')` declares it at index 1,
|
|
29
|
+
* `defineDto('Condition', { vars })` at index 0. A site with a declared root is
|
|
30
|
+
* analyzed against it, which is the only way a walker can check a relative
|
|
31
|
+
* expression — see `analyzeSite`.
|
|
32
|
+
*/
|
|
33
|
+
rootArg?: number
|
|
34
|
+
/**
|
|
35
|
+
* The root comes from the enclosing class's `extends defineDto('Condition')`
|
|
36
|
+
* clause: a `@column`/`@criteria` field. A class extending anything else (a
|
|
37
|
+
* base class, a root-generic factory) has no statically-known fhirType, and
|
|
38
|
+
* the expression is analyzed without an input type.
|
|
39
|
+
*/
|
|
40
|
+
rootFromClass?: true
|
|
41
|
+
/**
|
|
42
|
+
* A DTO member site. Its `%variables` are never judged — they come from the
|
|
43
|
+
* DTO's own `vars`/`env`, from a base class, or from the projecting call, none
|
|
44
|
+
* of which a source walker can see in full — and an unresolved function is
|
|
45
|
+
* reported only when it misspells a column of the same file. `analyzeDto`
|
|
46
|
+
* checks all of it properly.
|
|
47
|
+
*/
|
|
48
|
+
dto?: true
|
|
49
|
+
/** Declares a zero-argument function named after the decorated field. */
|
|
50
|
+
declaresField?: 'column' | 'criteria'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Call names that take FHIRPath expressions, and where/how/on-what they take them. */
|
|
54
|
+
export const CALL_SITES: ReadonlyMap<string, CallSitePolicy> = new Map([
|
|
55
|
+
// Expression-first: the low-level API and the evaluate family of FhirPathEngine.
|
|
56
|
+
// `fhirpath`/`compile` take the type the expression runs against as their
|
|
57
|
+
// second argument, which is what makes an expression held in a `const` — and
|
|
58
|
+
// evaluated somewhere else entirely — checkable.
|
|
59
|
+
['fhirpath', { argIndex: 0, shape: 'expression', receiver: 'any', rootArg: 1 }],
|
|
60
|
+
['compile', { argIndex: 0, shape: 'expression', receiver: 'any', rootArg: 1 }],
|
|
61
|
+
['evaluate', { argIndex: 0, shape: 'expression', receiver: 'any' }],
|
|
62
|
+
['evaluateTyped', { argIndex: 0, shape: 'expression', receiver: 'any' }],
|
|
63
|
+
['first', { argIndex: 0, shape: 'expression', receiver: 'engine' }],
|
|
64
|
+
['analyzeExpression', { argIndex: 0, shape: 'expression', receiver: 'any' }],
|
|
65
|
+
// Subject-first FhirPathEngine helpers: the expression(s) come second.
|
|
66
|
+
['test', { argIndex: 1, shape: 'expression', receiver: 'engine' }],
|
|
67
|
+
['filter', { argIndex: 1, shape: 'expression', receiver: 'engine' }],
|
|
68
|
+
['project', { argIndex: 1, shape: 'columns', receiver: 'engine' }],
|
|
69
|
+
['checkConstraints', { argIndex: 1, shape: 'constraints', receiver: 'any' }],
|
|
70
|
+
// DTO declarations: the column/criteria expressions of a `@column` field, and
|
|
71
|
+
// the `vars` a DTO binds per row.
|
|
72
|
+
[
|
|
73
|
+
'column',
|
|
74
|
+
{ argIndex: 0, shape: 'expression', receiver: 'import', rootFromClass: true, dto: true, declaresField: 'column' },
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'criteria',
|
|
78
|
+
{ argIndex: 0, shape: 'expression', receiver: 'import', rootFromClass: true, dto: true, declaresField: 'criteria' },
|
|
79
|
+
],
|
|
80
|
+
['defineDto', { argIndex: 1, shape: 'dto-vars', receiver: 'import', rootArg: 0, dto: true }],
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
/** The `defineDto` call whose first argument fixes a DTO's fhirType. */
|
|
84
|
+
export const DTO_BASE_NAME = 'defineDto'
|
|
85
|
+
|
|
86
|
+
/** The tag name whose no-substitution template holds a FHIRPath expression. */
|
|
87
|
+
export const TAG_NAME = 'fhirpath'
|
|
88
|
+
|
|
89
|
+
/** Imports from this package (any subpath) are the real FHIRPath API, never foreign. */
|
|
90
|
+
export const PACKAGE_PREFIX = 'fhirpath-ts'
|
|
91
|
+
|
|
92
|
+
/** The engine class name: `new FhirPathEngine(...)` locals are trusted bindings. */
|
|
93
|
+
export const ENGINE_CLASS_NAME = 'FhirPathEngine'
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Name bindings a walker collects from a source file before extracting sites.
|
|
97
|
+
* Files without imports (scripts, snippets) have all sets empty: `receiver: 'any'`
|
|
98
|
+
* names are then always checked, `receiver: 'engine'` names only via a
|
|
99
|
+
* `new FhirPathEngine(...)` local.
|
|
100
|
+
*/
|
|
101
|
+
export interface SourceBindings {
|
|
102
|
+
/**
|
|
103
|
+
* Local names bound by imports from modules other than this package
|
|
104
|
+
* (`compile` from handlebars is not a FHIRPath entry point).
|
|
105
|
+
*/
|
|
106
|
+
foreign: ReadonlySet<string>
|
|
107
|
+
/** Package imports and locals created with `new FhirPathEngine()`. */
|
|
108
|
+
trusted: ReadonlySet<string>
|
|
109
|
+
/**
|
|
110
|
+
* Trusted names also declared for another purpose. Trust is file-wide, so a
|
|
111
|
+
* rebound name is skipped to prevent false positives in another scope.
|
|
112
|
+
*/
|
|
113
|
+
rebound: ReadonlySet<string>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether a call site should be checked, given its policy and the file's
|
|
118
|
+
* bindings. `receiverRoot` is the leftmost identifier of a member-expression
|
|
119
|
+
* callee (`Handlebars` in `Handlebars.compile(...)`, `db` in
|
|
120
|
+
* `db.clients.first(...)`), or undefined for a bare-identifier callee or a
|
|
121
|
+
* non-identifier root (`this`, a call result).
|
|
122
|
+
*/
|
|
123
|
+
export function isCheckedCall(
|
|
124
|
+
policy: CallSitePolicy,
|
|
125
|
+
calleeName: string,
|
|
126
|
+
receiverRoot: string | undefined,
|
|
127
|
+
bindings: SourceBindings
|
|
128
|
+
): boolean {
|
|
129
|
+
if (policy.receiver === 'engine') {
|
|
130
|
+
return receiverRoot !== undefined && bindings.trusted.has(receiverRoot) && !bindings.rebound.has(receiverRoot)
|
|
131
|
+
}
|
|
132
|
+
if (policy.receiver === 'import') {
|
|
133
|
+
return receiverRoot === undefined && bindings.trusted.has(calleeName) && !bindings.rebound.has(calleeName)
|
|
134
|
+
}
|
|
135
|
+
return !bindings.foreign.has(receiverRoot ?? calleeName)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Checks a `fhirpath` tag unless its bare name or namespace comes from another package. */
|
|
139
|
+
export function isCheckedTag(receiverRoot: string | undefined, bindings: SourceBindings): boolean {
|
|
140
|
+
return !bindings.foreign.has(receiverRoot ?? TAG_NAME)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Whether `new className(...)` yields a trusted binding: any class imported from
|
|
145
|
+
* this package counts, and the bare `FhirPathEngine` name also counts in files
|
|
146
|
+
* whose imports don't claim it — so import-less snippets stay checkable.
|
|
147
|
+
*/
|
|
148
|
+
export function constructsEngine(className: string, bindings: SourceBindings): boolean {
|
|
149
|
+
return bindings.trusted.has(className) || (className === ENGINE_CLASS_NAME && !bindings.foreign.has(className))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Which import sources count as the real FHIRPath API rather than a foreign module. */
|
|
153
|
+
export interface LocalModuleOptions {
|
|
154
|
+
/** Import-source prefixes that are the FHIRPath API. Defaults to `['fhirpath-ts']`. */
|
|
155
|
+
packages?: readonly string[]
|
|
156
|
+
/**
|
|
157
|
+
* Also treat relative imports (`./`, `../`) as the FHIRPath API. Off by default
|
|
158
|
+
* (a consumer's relative `compile` is not FHIRPath); turn it on so the package
|
|
159
|
+
* can dogfood the rule on its own source, which imports its API relatively.
|
|
160
|
+
*/
|
|
161
|
+
localImports?: boolean
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether an import specifier's module is foreign (not the FHIRPath API). By
|
|
166
|
+
* default only `fhirpath-ts` (any subpath) is local; `options` widens that to
|
|
167
|
+
* extra package prefixes and/or relative imports.
|
|
168
|
+
*/
|
|
169
|
+
export function isForeignModule(moduleSpecifier: string, options: LocalModuleOptions = {}): boolean {
|
|
170
|
+
const packages = options.packages ?? [PACKAGE_PREFIX]
|
|
171
|
+
if (packages.some(prefix => moduleSpecifier.startsWith(prefix))) {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
if (options.localImports === true && moduleSpecifier.startsWith('.')) {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
return true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** An extracted expression literal: the AST node (for reporting) and its text. */
|
|
181
|
+
export interface ExpressionEntry<N> {
|
|
182
|
+
node: N
|
|
183
|
+
expression: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* What a site tells `analyzeSite` about itself, beyond the expression text: the
|
|
188
|
+
* type the expression runs against, and whether it is a DTO member. Both walkers
|
|
189
|
+
* produce this shape so `analyzeSite` cannot be handed two different vocabularies.
|
|
190
|
+
*/
|
|
191
|
+
export interface SiteContext {
|
|
192
|
+
/** The type the expression is analyzed against, when the site fixes one. */
|
|
193
|
+
inputType?: string
|
|
194
|
+
/** A DTO member site, whose findings are weighed differently (see `CallSitePolicy.dto`). */
|
|
195
|
+
dto?: true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** One class of a file, as a walker reads its heritage clause. */
|
|
199
|
+
export interface ClassHeritage {
|
|
200
|
+
/** The class's own name, when it has one. */
|
|
201
|
+
name: string | undefined
|
|
202
|
+
/** The fhirType its own `extends defineDto('X')` clause fixes, when it has one. */
|
|
203
|
+
ownRoot: string | undefined
|
|
204
|
+
/** The name of the class it extends, when that clause is a plain identifier. */
|
|
205
|
+
baseName: string | undefined
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolves DTO roots through base classes declared in the same file. Duplicate
|
|
210
|
+
* names, cycles, factories, and imported bases remain unresolved because the
|
|
211
|
+
* source cannot prove their root.
|
|
212
|
+
*/
|
|
213
|
+
export function dtoRootsOf(classes: readonly ClassHeritage[]): ReadonlyMap<string, string> {
|
|
214
|
+
const byName = new Map<string, ClassHeritage>()
|
|
215
|
+
const ambiguous = new Set<string>()
|
|
216
|
+
for (const cls of classes) {
|
|
217
|
+
if (cls.name === undefined) {
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
if (byName.has(cls.name)) {
|
|
221
|
+
ambiguous.add(cls.name)
|
|
222
|
+
}
|
|
223
|
+
byName.set(cls.name, cls)
|
|
224
|
+
}
|
|
225
|
+
const rootOf = (cls: ClassHeritage, seen: Set<string>): string | undefined => {
|
|
226
|
+
if (cls.ownRoot !== undefined) {
|
|
227
|
+
return cls.ownRoot
|
|
228
|
+
}
|
|
229
|
+
const base = cls.baseName
|
|
230
|
+
if (base === undefined || ambiguous.has(base) || seen.has(base)) {
|
|
231
|
+
return undefined
|
|
232
|
+
}
|
|
233
|
+
const declaration = byName.get(base)
|
|
234
|
+
if (declaration === undefined) {
|
|
235
|
+
return undefined
|
|
236
|
+
}
|
|
237
|
+
seen.add(base)
|
|
238
|
+
return rootOf(declaration, seen)
|
|
239
|
+
}
|
|
240
|
+
const roots = new Map<string, string>()
|
|
241
|
+
for (const [name, cls] of byName) {
|
|
242
|
+
if (ambiguous.has(name)) {
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
const root = rootOf(cls, new Set([name]))
|
|
246
|
+
if (root !== undefined) {
|
|
247
|
+
roots.set(name, root)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return roots
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The DTO root a class's `@column` fields analyze against: its own
|
|
255
|
+
* `extends defineDto('X')` clause, else whatever its `extends` chain settled on in
|
|
256
|
+
* `dtoRootsOf`. Its own clause wins outright, so a class whose *name* the file
|
|
257
|
+
* declares twice — dropped from the chain, since a wrong root would report valid
|
|
258
|
+
* code — still checks its own columns.
|
|
259
|
+
*/
|
|
260
|
+
export function rootOf(heritage: ClassHeritage | undefined, dtoRoots: ReadonlyMap<string, string>): string | undefined {
|
|
261
|
+
if (heritage === undefined || heritage.ownRoot !== undefined) {
|
|
262
|
+
return heritage?.ownRoot
|
|
263
|
+
}
|
|
264
|
+
return heritage.name === undefined ? undefined : dtoRoots.get(heritage.name)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The context a call site's expressions carry, per its policy. The root is named
|
|
269
|
+
* either by one of the call's own arguments (`fhirpath(expr, 'Patient')`,
|
|
270
|
+
* `defineDto('Condition', …)`) or by the enclosing class's `extends defineDto(…)`
|
|
271
|
+
* clause, which the walker resolves and passes as `classRoot`. Mapping a policy
|
|
272
|
+
* to a context is a decision, so it happens here rather than once per walker —
|
|
273
|
+
* the two drifted while each had its own copy.
|
|
274
|
+
*/
|
|
275
|
+
export function siteContext<N>(
|
|
276
|
+
policy: CallSitePolicy,
|
|
277
|
+
argumentAt: (index: number) => N | undefined,
|
|
278
|
+
classRoot: string | undefined,
|
|
279
|
+
ast: ExpressionAst<N>
|
|
280
|
+
): SiteContext {
|
|
281
|
+
const rootArgument = policy.rootArg === undefined ? undefined : argumentAt(policy.rootArg)
|
|
282
|
+
const inputType =
|
|
283
|
+
rootArgument !== undefined
|
|
284
|
+
? ast.string(rootArgument)?.expression
|
|
285
|
+
: policy.rootFromClass === true
|
|
286
|
+
? classRoot
|
|
287
|
+
: undefined
|
|
288
|
+
return {
|
|
289
|
+
...(policy.dto === true && { dto: true as const }),
|
|
290
|
+
...(inputType !== undefined && { inputType }),
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The AST accessors a walker provides so shape extraction can be written once.
|
|
296
|
+
* Each returns undefined when the node is not of the asked-for kind, which the
|
|
297
|
+
* extractor treats as "dynamic, skip" — so spreads, shorthands, computed values,
|
|
298
|
+
* and variables all fall out of the same rule in both walkers.
|
|
299
|
+
*/
|
|
300
|
+
export interface ExpressionAst<N> {
|
|
301
|
+
/** The node as a string literal entry, or undefined when it is not one. */
|
|
302
|
+
string(node: N): ExpressionEntry<N> | undefined
|
|
303
|
+
/** The node's value as a `true`/`false` literal, or undefined when it is not one. */
|
|
304
|
+
boolean(node: N): boolean | undefined
|
|
305
|
+
/**
|
|
306
|
+
* The plain (non-spread, non-shorthand) properties of an object literal, or
|
|
307
|
+
* undefined when the node is not an object literal. `name` is the property's
|
|
308
|
+
* statically-known key — undefined for computed keys.
|
|
309
|
+
*/
|
|
310
|
+
properties(node: N): { name: string | undefined; value: N }[] | undefined
|
|
311
|
+
/** The elements of an array literal, or undefined when the node is not one. */
|
|
312
|
+
elements(node: N): N[] | undefined
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Extract the expression literal(s) an argument holds, per the call site's shape.
|
|
317
|
+
* Column keys are output names, not expressions, so a computed key does not stop
|
|
318
|
+
* its value from being checked; `{ path }` / `{ expression }` lookups match
|
|
319
|
+
* identifier and string-literal keys only.
|
|
320
|
+
*/
|
|
321
|
+
export function expressionEntries<N>(argument: N, shape: CallSiteShape, ast: ExpressionAst<N>): ExpressionEntry<N>[] {
|
|
322
|
+
if (shape === 'expression') {
|
|
323
|
+
const entry = ast.string(argument)
|
|
324
|
+
return entry ? [entry] : []
|
|
325
|
+
}
|
|
326
|
+
if (shape === 'columns') {
|
|
327
|
+
// project() columns: { name: 'expr' }, { name: { path: 'expr', ... } }, or
|
|
328
|
+
// the boolean-criteria form { name: { test: 'expr' } }.
|
|
329
|
+
return (ast.properties(argument) ?? []).flatMap(({ value }) => {
|
|
330
|
+
const entry = ast.string(value)
|
|
331
|
+
return entry ? [entry] : [...namedStringEntries(value, 'path', ast), ...namedStringEntries(value, 'test', ast)]
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
if (shape === 'dto-vars') {
|
|
335
|
+
// defineDto() options: { vars: { name: 'expr' }, callerEnv: [...] }. Only
|
|
336
|
+
// vars hold expressions.
|
|
337
|
+
const vars = (ast.properties(argument) ?? []).filter(({ name }) => name === 'vars')
|
|
338
|
+
return vars.flatMap(({ value }) =>
|
|
339
|
+
(ast.properties(value) ?? []).flatMap(({ value: expression }) => {
|
|
340
|
+
const entry = ast.string(expression)
|
|
341
|
+
return entry ? [entry] : []
|
|
342
|
+
})
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
// checkConstraints() constraints: [{ key, expression: 'expr', ... }].
|
|
346
|
+
return (ast.elements(argument) ?? []).flatMap(element => namedStringEntries(element, 'expression', ast))
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** The string-literal values of an object literal's `name` properties. */
|
|
350
|
+
function namedStringEntries<N>(object: N, name: string, ast: ExpressionAst<N>): ExpressionEntry<N>[] {
|
|
351
|
+
return (ast.properties(object) ?? []).flatMap(({ name: key, value }) => {
|
|
352
|
+
if (key !== name) {
|
|
353
|
+
return []
|
|
354
|
+
}
|
|
355
|
+
const entry = ast.string(value)
|
|
356
|
+
return entry ? [entry] : []
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* What a DTO column declares as a function: every `@column` field of a
|
|
362
|
+
* registered DTO becomes a zero-argument expression function named by the field
|
|
363
|
+
* (see `withDtos`), so a walker that reads a file's columns can resolve the
|
|
364
|
+
* calls between them. Shaped to be assignable to the analyzer's
|
|
365
|
+
* `DeclaredFunction`.
|
|
366
|
+
*/
|
|
367
|
+
export interface DeclaredColumnFunction {
|
|
368
|
+
minArity: 0
|
|
369
|
+
maxArity: 0
|
|
370
|
+
signature?: ColumnFunctionSignature
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** One field name of a file, with every class that declares it (see `declaredColumnOverloads`). */
|
|
374
|
+
export type FileColumnFunction = DeclaredColumnFunction | { overloads: readonly DeclaredColumnFunction[] }
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Builds a DTO column's function declaration from literal source options and a
|
|
378
|
+
* visible class root. Unknown roots omit the input claim. Dynamic options omit
|
|
379
|
+
* the result claim.
|
|
380
|
+
*/
|
|
381
|
+
export function columnFunctionDeclaration<N>(
|
|
382
|
+
kind: 'column' | 'criteria',
|
|
383
|
+
options: N | undefined,
|
|
384
|
+
ast: ExpressionAst<N>,
|
|
385
|
+
hostType: string | undefined
|
|
386
|
+
): DeclaredColumnFunction {
|
|
387
|
+
const declaration: DeclaredColumnFunction = { minArity: 0, maxArity: 0 }
|
|
388
|
+
if (kind === 'criteria') {
|
|
389
|
+
// A criteria takes no options object. Its result is a single Boolean
|
|
390
|
+
// whatever the expression returns, because the rule lives on the function.
|
|
391
|
+
return { ...declaration, signature: criteriaSignature(hostType) }
|
|
392
|
+
}
|
|
393
|
+
const signature = columnSignature(columnClaim(options, ast), hostType)
|
|
394
|
+
return signature === undefined ? declaration : { ...declaration, signature }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Keeps every same-name column declaration as an overload for focus-based analysis. */
|
|
398
|
+
export function declaredColumnOverloads(
|
|
399
|
+
seen: FileColumnFunction | undefined,
|
|
400
|
+
next: DeclaredColumnFunction
|
|
401
|
+
): FileColumnFunction {
|
|
402
|
+
if (seen === undefined) {
|
|
403
|
+
return next
|
|
404
|
+
}
|
|
405
|
+
return { overloads: [...('overloads' in seen ? seen.overloads : [seen]), next] }
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Reads the type claim a column's options object makes. The answer is an empty
|
|
410
|
+
* claim whenever the source cannot say: no options at all, options that are not
|
|
411
|
+
* an object literal, or a `collection` set from a variable, where a guessed
|
|
412
|
+
* cardinality would be worse than none. All three produce no result claim,
|
|
413
|
+
* which leaves the input type as the only thing the declaration carries.
|
|
414
|
+
*/
|
|
415
|
+
function columnClaim<N>(options: N | undefined, ast: ExpressionAst<N>): ColumnTypeClaim & { collection?: boolean } {
|
|
416
|
+
const properties = options === undefined ? undefined : ast.properties(options)
|
|
417
|
+
if (properties === undefined) {
|
|
418
|
+
return {}
|
|
419
|
+
}
|
|
420
|
+
const named = (name: string): N | undefined => properties.find(property => property.name === name)?.value
|
|
421
|
+
const collection = named('collection')
|
|
422
|
+
const isCollection = collection === undefined ? false : ast.boolean(collection)
|
|
423
|
+
if (isCollection === undefined) {
|
|
424
|
+
return {}
|
|
425
|
+
}
|
|
426
|
+
const declaredType = named('type')
|
|
427
|
+
return {
|
|
428
|
+
...(declaredType !== undefined && { type: ast.string(declaredType)?.expression }),
|
|
429
|
+
...(named('enum') !== undefined && { enum: true }),
|
|
430
|
+
...(named('as') !== undefined && { as: true }),
|
|
431
|
+
...(named('choices') !== undefined && { choices: true }),
|
|
432
|
+
collection: isCollection,
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The `fhirpath-ts/analyzer` entry point. Listed name by name rather than
|
|
2
|
+
// Re-export the public analyzer API from one entry point. Its contents should
|
|
3
|
+
// be a decision, not a side effect of adding an export to analyze.ts.
|
|
4
|
+
export {
|
|
5
|
+
type AnalysisDetails,
|
|
6
|
+
analyzeExpression,
|
|
7
|
+
analyzeExpressionDetailed,
|
|
8
|
+
type AnalyzeOptions,
|
|
9
|
+
type AnalyzerDiagnostic,
|
|
10
|
+
analyzeSite,
|
|
11
|
+
type DeclaredFunction,
|
|
12
|
+
type DeclaredVariable,
|
|
13
|
+
type OverloadedDeclaredFunction,
|
|
14
|
+
type SingleDeclaredFunction,
|
|
15
|
+
} from './analyze.ts'
|
|
16
|
+
export {
|
|
17
|
+
type AnalyzedContext,
|
|
18
|
+
type AnalyzedEngine,
|
|
19
|
+
analyzeDto,
|
|
20
|
+
type AnalyzeDtoOptions,
|
|
21
|
+
analyzeEngineDtos,
|
|
22
|
+
type DtoDiagnostic,
|
|
23
|
+
} from './analyze-dto.ts'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static ReDoS heuristic for literal regex arguments of the matches() family.
|
|
3
|
+
* A synchronous JS engine cannot time out a running RegExp, so catastrophic
|
|
4
|
+
* backtracking is flagged before it runs instead: the classic exponential
|
|
5
|
+
* shape is an unbounded quantifier applied to a group that itself contains an
|
|
6
|
+
* unbounded quantifier — `(a+)+`, `(\d*)*`, `(x|y+)*`. That is what this
|
|
7
|
+
* detects; overlapping-alternation cases like `(a|ab)+` are out of scope.
|
|
8
|
+
*/
|
|
9
|
+
export function hasNestedUnboundedQuantifier(pattern: string): boolean {
|
|
10
|
+
// Each open group tracks whether an unbounded quantifier appeared inside it.
|
|
11
|
+
const groups: boolean[] = []
|
|
12
|
+
let currentHasUnbounded = false
|
|
13
|
+
let index = 0
|
|
14
|
+
while (index < pattern.length) {
|
|
15
|
+
const char = pattern[index] as string
|
|
16
|
+
if (char === '\\') {
|
|
17
|
+
index += 2
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
if (char === '[') {
|
|
21
|
+
// Character classes repeat single characters; quantifiers inside are literals.
|
|
22
|
+
index += 1
|
|
23
|
+
while (index < pattern.length && pattern[index] !== ']') {
|
|
24
|
+
index += pattern[index] === '\\' ? 2 : 1
|
|
25
|
+
}
|
|
26
|
+
index += 1
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
if (char === '(') {
|
|
30
|
+
groups.push(currentHasUnbounded)
|
|
31
|
+
currentHasUnbounded = false
|
|
32
|
+
index += 1
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
if (char === ')') {
|
|
36
|
+
const groupHadUnbounded = currentHasUnbounded
|
|
37
|
+
currentHasUnbounded = groups.pop() ?? false
|
|
38
|
+
index += 1
|
|
39
|
+
const quantifier = readQuantifier(pattern, index)
|
|
40
|
+
if (quantifier !== undefined) {
|
|
41
|
+
if (groupHadUnbounded && quantifier.unbounded) {
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
currentHasUnbounded ||= quantifier.unbounded
|
|
45
|
+
index = quantifier.end
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
currentHasUnbounded ||= groupHadUnbounded
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
const quantifier = readQuantifier(pattern, index)
|
|
52
|
+
if (quantifier !== undefined && index > 0) {
|
|
53
|
+
currentHasUnbounded ||= quantifier.unbounded
|
|
54
|
+
index = quantifier.end
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
index += 1
|
|
58
|
+
}
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The quantifier at `index` (`*`, `+`, `{m,}`, `{m,n}`, with lazy `?`), or undefined. */
|
|
63
|
+
function readQuantifier(pattern: string, index: number): { unbounded: boolean; end: number } | undefined {
|
|
64
|
+
const char = pattern[index]
|
|
65
|
+
if (char === '*' || char === '+') {
|
|
66
|
+
return { unbounded: true, end: skipLazy(pattern, index + 1) }
|
|
67
|
+
}
|
|
68
|
+
if (char === '?') {
|
|
69
|
+
return { unbounded: false, end: skipLazy(pattern, index + 1) }
|
|
70
|
+
}
|
|
71
|
+
if (char === '{') {
|
|
72
|
+
const match = /^\{(\d+)(,(\d*)?)?\}/.exec(pattern.slice(index))
|
|
73
|
+
if (match === null) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
// {m} and {m,n} are bounded; {m,} is unbounded.
|
|
77
|
+
const unbounded = match[2] !== undefined && (match[3] === undefined || match[3] === '')
|
|
78
|
+
return { unbounded, end: skipLazy(pattern, index + match[0].length) }
|
|
79
|
+
}
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function skipLazy(pattern: string, index: number): number {
|
|
84
|
+
return pattern[index] === '?' ? index + 1 : index
|
|
85
|
+
}
|