@jesscss/core 2.0.0-alpha.4 → 2.0.0-alpha.6
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/lib/index.cjs +20159 -0
- package/lib/index.d.cts +5993 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.ts +5992 -21
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +19926 -22
- package/lib/index.js.map +1 -1
- package/package.json +15 -14
- package/src/__tests__/define-function-record.test.ts +58 -0
- package/src/__tests__/define-function-simple.test.ts +55 -0
- package/src/__tests__/define-function-split-sequence.test.ts +547 -0
- package/src/__tests__/define-function-type-parity.test.ts +9 -0
- package/src/__tests__/define-function.test.ts +763 -0
- package/src/__tests__/num-operations.test.ts +91 -0
- package/src/__tests__/safe-parse.test.ts +374 -0
- package/src/context.ts +896 -0
- package/src/conversions.ts +282 -0
- package/src/debug-log.ts +29 -0
- package/src/define-function.ts +1006 -0
- package/src/deprecation.ts +67 -0
- package/src/globals.d.ts +26 -0
- package/src/index.ts +31 -0
- package/src/jess-error.ts +773 -0
- package/src/logger/deprecation-processing.ts +109 -0
- package/src/logger.ts +31 -0
- package/src/plugin.ts +292 -0
- package/src/tree/LOOKUP_CHAINS.md +35 -0
- package/src/tree/README.md +18 -0
- package/src/tree/__tests__/__snapshots__/extend-eval-integration.test.ts.snap +1455 -0
- package/src/tree/__tests__/ampersand.test.ts +382 -0
- package/src/tree/__tests__/at-rule.test.ts +2047 -0
- package/src/tree/__tests__/basic-render.test.ts +212 -0
- package/src/tree/__tests__/block.test.ts +40 -0
- package/src/tree/__tests__/call.test.ts +346 -0
- package/src/tree/__tests__/color.test.ts +537 -0
- package/src/tree/__tests__/condition.test.ts +186 -0
- package/src/tree/__tests__/control.test.ts +564 -0
- package/src/tree/__tests__/declaration.test.ts +253 -0
- package/src/tree/__tests__/dependency-graph.test.ts +177 -0
- package/src/tree/__tests__/detached-rulesets.test.ts +213 -0
- package/src/tree/__tests__/dimension.test.ts +236 -0
- package/src/tree/__tests__/expression.test.ts +73 -0
- package/src/tree/__tests__/ext-node.test.ts +31 -0
- package/src/tree/__tests__/extend-eval-integration.test.ts +1033 -0
- package/src/tree/__tests__/extend-import-style.test.ts +929 -0
- package/src/tree/__tests__/extend-less-fixtures.test.ts +851 -0
- package/src/tree/__tests__/extend-list.test.ts +31 -0
- package/src/tree/__tests__/extend-roots.test.ts +1045 -0
- package/src/tree/__tests__/extend-rules.test.ts +740 -0
- package/src/tree/__tests__/func.test.ts +171 -0
- package/src/tree/__tests__/import-js.test.ts +33 -0
- package/src/tree/__tests__/import-style-test-helpers.ts +56 -0
- package/src/tree/__tests__/import-style.test.ts +1967 -0
- package/src/tree/__tests__/interpolated-reference.test.ts +44 -0
- package/src/tree/__tests__/interpolated.test.ts +41 -0
- package/src/tree/__tests__/list.test.ts +177 -0
- package/src/tree/__tests__/log.test.ts +83 -0
- package/src/tree/__tests__/mixin-recursion.test.ts +639 -0
- package/src/tree/__tests__/mixin.test.ts +2171 -0
- package/src/tree/__tests__/negative.test.ts +45 -0
- package/src/tree/__tests__/nesting-collapse.test.ts +519 -0
- package/src/tree/__tests__/node-flags-perf.test.ts +195 -0
- package/src/tree/__tests__/node-flags.test.ts +410 -0
- package/src/tree/__tests__/node-graph.test.ts +598 -0
- package/src/tree/__tests__/node-mutation.test.ts +182 -0
- package/src/tree/__tests__/operation.test.ts +18 -0
- package/src/tree/__tests__/paren.test.ts +90 -0
- package/src/tree/__tests__/preserve-mode-output.test.ts +50 -0
- package/src/tree/__tests__/quoted.test.ts +72 -0
- package/src/tree/__tests__/range.test.ts +59 -0
- package/src/tree/__tests__/reference.test.ts +743 -0
- package/src/tree/__tests__/rest.test.ts +29 -0
- package/src/tree/__tests__/rules-raw.test.ts +14 -0
- package/src/tree/__tests__/rules.test.ts +1271 -0
- package/src/tree/__tests__/ruleset.test.ts +597 -0
- package/src/tree/__tests__/selector-attr.test.ts +50 -0
- package/src/tree/__tests__/selector-basic.test.ts +44 -0
- package/src/tree/__tests__/selector-capture.test.ts +22 -0
- package/src/tree/__tests__/selector-complex.test.ts +120 -0
- package/src/tree/__tests__/selector-compound.test.ts +74 -0
- package/src/tree/__tests__/selector-interpolated.test.ts +50 -0
- package/src/tree/__tests__/selector-list.test.ts +59 -0
- package/src/tree/__tests__/selector-pseudo.test.ts +23 -0
- package/src/tree/__tests__/selector.test.ts +182 -0
- package/src/tree/__tests__/sequence.test.ts +226 -0
- package/src/tree/__tests__/serialize-types.test.ts +529 -0
- package/src/tree/__tests__/spaced.test.ts +8 -0
- package/src/tree/__tests__/url.test.ts +72 -0
- package/src/tree/__tests__/var-declaration.test.ts +90 -0
- package/src/tree/ampersand.ts +538 -0
- package/src/tree/any.ts +169 -0
- package/src/tree/at-rule.ts +760 -0
- package/src/tree/block.ts +72 -0
- package/src/tree/bool.ts +46 -0
- package/src/tree/call.ts +593 -0
- package/src/tree/collection.ts +52 -0
- package/src/tree/color.ts +629 -0
- package/src/tree/combinator.ts +30 -0
- package/src/tree/comment.ts +36 -0
- package/src/tree/condition.ts +194 -0
- package/src/tree/control.ts +452 -0
- package/src/tree/declaration-custom.ts +56 -0
- package/src/tree/declaration-var.ts +87 -0
- package/src/tree/declaration.ts +742 -0
- package/src/tree/default-guard.ts +35 -0
- package/src/tree/dimension.ts +392 -0
- package/src/tree/expression.ts +97 -0
- package/src/tree/extend-list.ts +51 -0
- package/src/tree/extend.ts +391 -0
- package/src/tree/function.ts +254 -0
- package/src/tree/import-js.ts +130 -0
- package/src/tree/import-style.ts +875 -0
- package/{lib/tree/index.js → src/tree/index.ts} +49 -22
- package/src/tree/interpolated.ts +346 -0
- package/src/tree/js-array.ts +21 -0
- package/src/tree/js-expr.ts +50 -0
- package/src/tree/js-function.ts +31 -0
- package/src/tree/js-object.ts +22 -0
- package/src/tree/list.ts +415 -0
- package/src/tree/log.ts +89 -0
- package/src/tree/mixin.ts +331 -0
- package/src/tree/negative.ts +58 -0
- package/src/tree/nil.ts +57 -0
- package/src/tree/node-base.ts +1716 -0
- package/src/tree/node-type.ts +122 -0
- package/src/tree/node.ts +118 -0
- package/src/tree/number.ts +54 -0
- package/src/tree/operation.ts +187 -0
- package/src/tree/paren.ts +132 -0
- package/src/tree/query-condition.ts +47 -0
- package/src/tree/quoted.ts +119 -0
- package/src/tree/range.ts +101 -0
- package/src/tree/reference.ts +1099 -0
- package/src/tree/rest.ts +55 -0
- package/src/tree/rules-raw.ts +52 -0
- package/src/tree/rules.ts +2896 -0
- package/src/tree/ruleset.ts +1217 -0
- package/src/tree/selector-attr.ts +172 -0
- package/src/tree/selector-basic.ts +75 -0
- package/src/tree/selector-capture.ts +85 -0
- package/src/tree/selector-complex.ts +189 -0
- package/src/tree/selector-compound.ts +205 -0
- package/src/tree/selector-interpolated.ts +95 -0
- package/src/tree/selector-list.ts +245 -0
- package/src/tree/selector-pseudo.ts +173 -0
- package/src/tree/selector-simple.ts +10 -0
- package/src/tree/selector.ts +152 -0
- package/src/tree/sequence.ts +463 -0
- package/src/tree/tree.ts +130 -0
- package/src/tree/url.ts +95 -0
- package/src/tree/util/EXTEND_ARCHITECTURE_ANALYSIS.md +215 -0
- package/src/tree/util/EXTEND_AUDIT.md +233 -0
- package/src/tree/util/EXTEND_BASELINE.md +64 -0
- package/src/tree/util/EXTEND_CALL_GRAPH_ANALYSIS.md +244 -0
- package/src/tree/util/EXTEND_DOCS.md +24 -0
- package/src/tree/util/EXTEND_FINAL_SUMMARY.md +95 -0
- package/src/tree/util/EXTEND_FUNCTION_AUDIT.md +1433 -0
- package/src/tree/util/EXTEND_OPTIMIZATION_PLAN.md +114 -0
- package/src/tree/util/EXTEND_REFACTORING_SUMMARY.md +152 -0
- package/src/tree/util/EXTEND_RULES.md +74 -0
- package/src/tree/util/EXTEND_UNUSED_FUNCTIONS.md +127 -0
- package/src/tree/util/EXTEND_UNUSED_FUNCTIONS_ANALYSIS.md +227 -0
- package/src/tree/util/NODE_COPY_REDUCTION_PLAN.md +12 -0
- package/src/tree/util/__tests__/EXTEND_TEST_INDEX.md +59 -0
- package/src/tree/util/__tests__/OPTIMIZATION-ANALYSIS.md +130 -0
- package/src/tree/util/__tests__/WALK_AND_CONSUME_DESIGN.md +138 -0
- package/src/tree/util/__tests__/_archive/2026-02-09__OPTIMIZATION-ANALYSIS.md +9 -0
- package/src/tree/util/__tests__/_archive/README.md +4 -0
- package/src/tree/util/__tests__/bitset.test.ts +142 -0
- package/src/tree/util/__tests__/debug-log.ts +50 -0
- package/src/tree/util/__tests__/extend-comment-handling.test.ts +187 -0
- package/src/tree/util/__tests__/extend-core-unit.test.ts +941 -0
- package/src/tree/util/__tests__/extend-pipeline-bench.test.ts +154 -0
- package/src/tree/util/__tests__/extend-pipeline-bench.ts +190 -0
- package/src/tree/util/__tests__/fast-reject.test.ts +377 -0
- package/src/tree/util/__tests__/is-node.test.ts +63 -0
- package/src/tree/util/__tests__/list-like.test.ts +63 -0
- package/src/tree/util/__tests__/outputwriter.test.ts +523 -0
- package/src/tree/util/__tests__/print.test.ts +183 -0
- package/src/tree/util/__tests__/process-extends.test.ts +226 -0
- package/src/tree/util/__tests__/process-leading-is.test.ts +205 -0
- package/src/tree/util/__tests__/recursion-helper.test.ts +184 -0
- package/src/tree/util/__tests__/selector-match-unit.test.ts +1427 -0
- package/src/tree/util/__tests__/sourcemap.test.ts +117 -0
- package/src/tree/util/ampersand-template.ts +9 -0
- package/src/tree/util/bitset.ts +194 -0
- package/src/tree/util/calculate.ts +11 -0
- package/src/tree/util/cast.ts +89 -0
- package/src/tree/util/cloning.ts +8 -0
- package/src/tree/util/collections.ts +299 -0
- package/src/tree/util/compare.ts +90 -0
- package/src/tree/util/cursor.ts +171 -0
- package/src/tree/util/extend-core.ts +2139 -0
- package/src/tree/util/extend-roots.ts +1108 -0
- package/src/tree/util/field-helpers.ts +354 -0
- package/src/tree/util/is-node.ts +43 -0
- package/src/tree/util/list-like.ts +93 -0
- package/src/tree/util/mixin-instance-primitives.ts +2020 -0
- package/src/tree/util/print.ts +303 -0
- package/src/tree/util/process-leading-is.ts +421 -0
- package/src/tree/util/recursion-helper.ts +54 -0
- package/src/tree/util/regex.ts +2 -0
- package/src/tree/util/registry-utils.ts +1953 -0
- package/src/tree/util/ruleset-trace.ts +17 -0
- package/src/tree/util/scoped-body-eval.ts +320 -0
- package/src/tree/util/selector-match-core.ts +2005 -0
- package/src/tree/util/selector-utils.ts +757 -0
- package/src/tree/util/serialize-helper.ts +535 -0
- package/src/tree/util/serialize-types.ts +318 -0
- package/src/tree/util/should-operate.ts +78 -0
- package/src/tree/util/sourcemap.ts +37 -0
- package/src/types/config.ts +247 -0
- package/src/types/index.ts +12 -0
- package/{lib/types/modes.d.ts → src/types/modes.ts} +2 -1
- package/src/types.d.ts +9 -0
- package/src/types.ts +68 -0
- package/src/use-webpack-resolver.ts +56 -0
- package/src/visitor/__tests__/visitor.test.ts +136 -0
- package/src/visitor/index.ts +263 -0
- package/{lib/visitor/less-visitor.js → src/visitor/less-visitor.ts} +3 -2
- package/lib/context.d.ts +0 -352
- package/lib/context.d.ts.map +0 -1
- package/lib/context.js +0 -636
- package/lib/context.js.map +0 -1
- package/lib/conversions.d.ts +0 -73
- package/lib/conversions.d.ts.map +0 -1
- package/lib/conversions.js +0 -253
- package/lib/conversions.js.map +0 -1
- package/lib/debug-log.d.ts +0 -2
- package/lib/debug-log.d.ts.map +0 -1
- package/lib/debug-log.js +0 -27
- package/lib/debug-log.js.map +0 -1
- package/lib/define-function.d.ts +0 -587
- package/lib/define-function.d.ts.map +0 -1
- package/lib/define-function.js +0 -726
- package/lib/define-function.js.map +0 -1
- package/lib/deprecation.d.ts +0 -34
- package/lib/deprecation.d.ts.map +0 -1
- package/lib/deprecation.js +0 -57
- package/lib/deprecation.js.map +0 -1
- package/lib/jess-error.d.ts +0 -343
- package/lib/jess-error.d.ts.map +0 -1
- package/lib/jess-error.js +0 -508
- package/lib/jess-error.js.map +0 -1
- package/lib/logger/deprecation-processing.d.ts +0 -41
- package/lib/logger/deprecation-processing.d.ts.map +0 -1
- package/lib/logger/deprecation-processing.js +0 -81
- package/lib/logger/deprecation-processing.js.map +0 -1
- package/lib/logger.d.ts +0 -10
- package/lib/logger.d.ts.map +0 -1
- package/lib/logger.js +0 -20
- package/lib/logger.js.map +0 -1
- package/lib/plugin.d.ts +0 -94
- package/lib/plugin.d.ts.map +0 -1
- package/lib/plugin.js +0 -174
- package/lib/plugin.js.map +0 -1
- package/lib/tree/ampersand.d.ts +0 -94
- package/lib/tree/ampersand.d.ts.map +0 -1
- package/lib/tree/ampersand.js +0 -269
- package/lib/tree/ampersand.js.map +0 -1
- package/lib/tree/any.d.ts +0 -58
- package/lib/tree/any.d.ts.map +0 -1
- package/lib/tree/any.js +0 -104
- package/lib/tree/any.js.map +0 -1
- package/lib/tree/at-rule.d.ts +0 -53
- package/lib/tree/at-rule.d.ts.map +0 -1
- package/lib/tree/at-rule.js +0 -503
- package/lib/tree/at-rule.js.map +0 -1
- package/lib/tree/block.d.ts +0 -22
- package/lib/tree/block.d.ts.map +0 -1
- package/lib/tree/block.js +0 -24
- package/lib/tree/block.js.map +0 -1
- package/lib/tree/bool.d.ts +0 -18
- package/lib/tree/bool.d.ts.map +0 -1
- package/lib/tree/bool.js +0 -28
- package/lib/tree/bool.js.map +0 -1
- package/lib/tree/call.d.ts +0 -66
- package/lib/tree/call.d.ts.map +0 -1
- package/lib/tree/call.js +0 -306
- package/lib/tree/call.js.map +0 -1
- package/lib/tree/collection.d.ts +0 -30
- package/lib/tree/collection.d.ts.map +0 -1
- package/lib/tree/collection.js +0 -37
- package/lib/tree/collection.js.map +0 -1
- package/lib/tree/color.d.ts +0 -101
- package/lib/tree/color.d.ts.map +0 -1
- package/lib/tree/color.js +0 -513
- package/lib/tree/color.js.map +0 -1
- package/lib/tree/combinator.d.ts +0 -13
- package/lib/tree/combinator.d.ts.map +0 -1
- package/lib/tree/combinator.js +0 -12
- package/lib/tree/combinator.js.map +0 -1
- package/lib/tree/comment.d.ts +0 -20
- package/lib/tree/comment.d.ts.map +0 -1
- package/lib/tree/comment.js +0 -19
- package/lib/tree/comment.js.map +0 -1
- package/lib/tree/condition.d.ts +0 -31
- package/lib/tree/condition.d.ts.map +0 -1
- package/lib/tree/condition.js +0 -103
- package/lib/tree/condition.js.map +0 -1
- package/lib/tree/control.d.ts +0 -104
- package/lib/tree/control.d.ts.map +0 -1
- package/lib/tree/control.js +0 -430
- package/lib/tree/control.js.map +0 -1
- package/lib/tree/declaration-custom.d.ts +0 -18
- package/lib/tree/declaration-custom.d.ts.map +0 -1
- package/lib/tree/declaration-custom.js +0 -24
- package/lib/tree/declaration-custom.js.map +0 -1
- package/lib/tree/declaration-var.d.ts +0 -35
- package/lib/tree/declaration-var.d.ts.map +0 -1
- package/lib/tree/declaration-var.js +0 -63
- package/lib/tree/declaration-var.js.map +0 -1
- package/lib/tree/declaration.d.ts +0 -78
- package/lib/tree/declaration.d.ts.map +0 -1
- package/lib/tree/declaration.js +0 -286
- package/lib/tree/declaration.js.map +0 -1
- package/lib/tree/default-guard.d.ts +0 -15
- package/lib/tree/default-guard.d.ts.map +0 -1
- package/lib/tree/default-guard.js +0 -19
- package/lib/tree/default-guard.js.map +0 -1
- package/lib/tree/dimension.d.ts +0 -34
- package/lib/tree/dimension.d.ts.map +0 -1
- package/lib/tree/dimension.js +0 -294
- package/lib/tree/dimension.js.map +0 -1
- package/lib/tree/expression.d.ts +0 -25
- package/lib/tree/expression.d.ts.map +0 -1
- package/lib/tree/expression.js +0 -32
- package/lib/tree/expression.js.map +0 -1
- package/lib/tree/extend-list.d.ts +0 -23
- package/lib/tree/extend-list.d.ts.map +0 -1
- package/lib/tree/extend-list.js +0 -23
- package/lib/tree/extend-list.js.map +0 -1
- package/lib/tree/extend.d.ts +0 -47
- package/lib/tree/extend.d.ts.map +0 -1
- package/lib/tree/extend.js +0 -296
- package/lib/tree/extend.js.map +0 -1
- package/lib/tree/function.d.ts +0 -48
- package/lib/tree/function.d.ts.map +0 -1
- package/lib/tree/function.js +0 -74
- package/lib/tree/function.js.map +0 -1
- package/lib/tree/import-js.d.ts +0 -35
- package/lib/tree/import-js.d.ts.map +0 -1
- package/lib/tree/import-js.js +0 -45
- package/lib/tree/import-js.js.map +0 -1
- package/lib/tree/import-style.d.ts +0 -156
- package/lib/tree/import-style.d.ts.map +0 -1
- package/lib/tree/import-style.js +0 -566
- package/lib/tree/import-style.js.map +0 -1
- package/lib/tree/index.d.ts +0 -71
- package/lib/tree/index.d.ts.map +0 -1
- package/lib/tree/index.js.map +0 -1
- package/lib/tree/interpolated-reference.d.ts +0 -24
- package/lib/tree/interpolated-reference.d.ts.map +0 -1
- package/lib/tree/interpolated-reference.js +0 -37
- package/lib/tree/interpolated-reference.js.map +0 -1
- package/lib/tree/interpolated.d.ts +0 -62
- package/lib/tree/interpolated.d.ts.map +0 -1
- package/lib/tree/interpolated.js +0 -204
- package/lib/tree/interpolated.js.map +0 -1
- package/lib/tree/js-array.d.ts +0 -10
- package/lib/tree/js-array.d.ts.map +0 -1
- package/lib/tree/js-array.js +0 -10
- package/lib/tree/js-array.js.map +0 -1
- package/lib/tree/js-expr.d.ts +0 -23
- package/lib/tree/js-expr.d.ts.map +0 -1
- package/lib/tree/js-expr.js +0 -28
- package/lib/tree/js-expr.js.map +0 -1
- package/lib/tree/js-function.d.ts +0 -20
- package/lib/tree/js-function.d.ts.map +0 -1
- package/lib/tree/js-function.js +0 -16
- package/lib/tree/js-function.js.map +0 -1
- package/lib/tree/js-object.d.ts +0 -10
- package/lib/tree/js-object.d.ts.map +0 -1
- package/lib/tree/js-object.js +0 -10
- package/lib/tree/js-object.js.map +0 -1
- package/lib/tree/list.d.ts +0 -38
- package/lib/tree/list.d.ts.map +0 -1
- package/lib/tree/list.js +0 -83
- package/lib/tree/list.js.map +0 -1
- package/lib/tree/log.d.ts +0 -29
- package/lib/tree/log.d.ts.map +0 -1
- package/lib/tree/log.js +0 -56
- package/lib/tree/log.js.map +0 -1
- package/lib/tree/mixin.d.ts +0 -87
- package/lib/tree/mixin.d.ts.map +0 -1
- package/lib/tree/mixin.js +0 -112
- package/lib/tree/mixin.js.map +0 -1
- package/lib/tree/negative.d.ts +0 -17
- package/lib/tree/negative.d.ts.map +0 -1
- package/lib/tree/negative.js +0 -22
- package/lib/tree/negative.js.map +0 -1
- package/lib/tree/nil.d.ts +0 -30
- package/lib/tree/nil.d.ts.map +0 -1
- package/lib/tree/nil.js +0 -35
- package/lib/tree/nil.js.map +0 -1
- package/lib/tree/node-base.d.ts +0 -361
- package/lib/tree/node-base.d.ts.map +0 -1
- package/lib/tree/node-base.js +0 -930
- package/lib/tree/node-base.js.map +0 -1
- package/lib/tree/node.d.ts +0 -10
- package/lib/tree/node.d.ts.map +0 -1
- package/lib/tree/node.js +0 -45
- package/lib/tree/node.js.map +0 -1
- package/lib/tree/number.d.ts +0 -21
- package/lib/tree/number.d.ts.map +0 -1
- package/lib/tree/number.js +0 -27
- package/lib/tree/number.js.map +0 -1
- package/lib/tree/operation.d.ts +0 -26
- package/lib/tree/operation.d.ts.map +0 -1
- package/lib/tree/operation.js +0 -103
- package/lib/tree/operation.js.map +0 -1
- package/lib/tree/paren.d.ts +0 -19
- package/lib/tree/paren.d.ts.map +0 -1
- package/lib/tree/paren.js +0 -92
- package/lib/tree/paren.js.map +0 -1
- package/lib/tree/query-condition.d.ts +0 -17
- package/lib/tree/query-condition.d.ts.map +0 -1
- package/lib/tree/query-condition.js +0 -39
- package/lib/tree/query-condition.js.map +0 -1
- package/lib/tree/quoted.d.ts +0 -28
- package/lib/tree/quoted.d.ts.map +0 -1
- package/lib/tree/quoted.js +0 -75
- package/lib/tree/quoted.js.map +0 -1
- package/lib/tree/range.d.ts +0 -33
- package/lib/tree/range.d.ts.map +0 -1
- package/lib/tree/range.js +0 -47
- package/lib/tree/range.js.map +0 -1
- package/lib/tree/reference.d.ts +0 -76
- package/lib/tree/reference.d.ts.map +0 -1
- package/lib/tree/reference.js +0 -521
- package/lib/tree/reference.js.map +0 -1
- package/lib/tree/rest.d.ts +0 -15
- package/lib/tree/rest.d.ts.map +0 -1
- package/lib/tree/rest.js +0 -32
- package/lib/tree/rest.js.map +0 -1
- package/lib/tree/rules-raw.d.ts +0 -17
- package/lib/tree/rules-raw.d.ts.map +0 -1
- package/lib/tree/rules-raw.js +0 -37
- package/lib/tree/rules-raw.js.map +0 -1
- package/lib/tree/rules.d.ts +0 -262
- package/lib/tree/rules.d.ts.map +0 -1
- package/lib/tree/rules.js +0 -2359
- package/lib/tree/rules.js.map +0 -1
- package/lib/tree/ruleset.d.ts +0 -92
- package/lib/tree/ruleset.d.ts.map +0 -1
- package/lib/tree/ruleset.js +0 -528
- package/lib/tree/ruleset.js.map +0 -1
- package/lib/tree/selector-attr.d.ts +0 -31
- package/lib/tree/selector-attr.d.ts.map +0 -1
- package/lib/tree/selector-attr.js +0 -99
- package/lib/tree/selector-attr.js.map +0 -1
- package/lib/tree/selector-basic.d.ts +0 -24
- package/lib/tree/selector-basic.d.ts.map +0 -1
- package/lib/tree/selector-basic.js +0 -38
- package/lib/tree/selector-basic.js.map +0 -1
- package/lib/tree/selector-capture.d.ts +0 -23
- package/lib/tree/selector-capture.d.ts.map +0 -1
- package/lib/tree/selector-capture.js +0 -34
- package/lib/tree/selector-capture.js.map +0 -1
- package/lib/tree/selector-complex.d.ts +0 -40
- package/lib/tree/selector-complex.d.ts.map +0 -1
- package/lib/tree/selector-complex.js +0 -143
- package/lib/tree/selector-complex.js.map +0 -1
- package/lib/tree/selector-compound.d.ts +0 -16
- package/lib/tree/selector-compound.d.ts.map +0 -1
- package/lib/tree/selector-compound.js +0 -114
- package/lib/tree/selector-compound.js.map +0 -1
- package/lib/tree/selector-interpolated.d.ts +0 -23
- package/lib/tree/selector-interpolated.d.ts.map +0 -1
- package/lib/tree/selector-interpolated.js +0 -27
- package/lib/tree/selector-interpolated.js.map +0 -1
- package/lib/tree/selector-list.d.ts +0 -17
- package/lib/tree/selector-list.d.ts.map +0 -1
- package/lib/tree/selector-list.js +0 -174
- package/lib/tree/selector-list.js.map +0 -1
- package/lib/tree/selector-pseudo.d.ts +0 -42
- package/lib/tree/selector-pseudo.d.ts.map +0 -1
- package/lib/tree/selector-pseudo.js +0 -204
- package/lib/tree/selector-pseudo.js.map +0 -1
- package/lib/tree/selector-simple.d.ts +0 -5
- package/lib/tree/selector-simple.d.ts.map +0 -1
- package/lib/tree/selector-simple.js +0 -6
- package/lib/tree/selector-simple.js.map +0 -1
- package/lib/tree/selector.d.ts +0 -43
- package/lib/tree/selector.d.ts.map +0 -1
- package/lib/tree/selector.js +0 -56
- package/lib/tree/selector.js.map +0 -1
- package/lib/tree/sequence.d.ts +0 -43
- package/lib/tree/sequence.d.ts.map +0 -1
- package/lib/tree/sequence.js +0 -151
- package/lib/tree/sequence.js.map +0 -1
- package/lib/tree/tree.d.ts +0 -87
- package/lib/tree/tree.d.ts.map +0 -1
- package/lib/tree/tree.js +0 -2
- package/lib/tree/tree.js.map +0 -1
- package/lib/tree/url.d.ts +0 -18
- package/lib/tree/url.d.ts.map +0 -1
- package/lib/tree/url.js +0 -35
- package/lib/tree/url.js.map +0 -1
- package/lib/tree/util/__tests__/debug-log.d.ts +0 -1
- package/lib/tree/util/__tests__/debug-log.d.ts.map +0 -1
- package/lib/tree/util/__tests__/debug-log.js +0 -36
- package/lib/tree/util/__tests__/debug-log.js.map +0 -1
- package/lib/tree/util/calculate.d.ts +0 -3
- package/lib/tree/util/calculate.d.ts.map +0 -1
- package/lib/tree/util/calculate.js +0 -10
- package/lib/tree/util/calculate.js.map +0 -1
- package/lib/tree/util/cast.d.ts +0 -10
- package/lib/tree/util/cast.d.ts.map +0 -1
- package/lib/tree/util/cast.js +0 -87
- package/lib/tree/util/cast.js.map +0 -1
- package/lib/tree/util/cloning.d.ts +0 -4
- package/lib/tree/util/cloning.d.ts.map +0 -1
- package/lib/tree/util/cloning.js +0 -8
- package/lib/tree/util/cloning.js.map +0 -1
- package/lib/tree/util/collections.d.ts +0 -57
- package/lib/tree/util/collections.d.ts.map +0 -1
- package/lib/tree/util/collections.js +0 -136
- package/lib/tree/util/collections.js.map +0 -1
- package/lib/tree/util/compare.d.ts +0 -11
- package/lib/tree/util/compare.d.ts.map +0 -1
- package/lib/tree/util/compare.js +0 -89
- package/lib/tree/util/compare.js.map +0 -1
- package/lib/tree/util/extend-helpers.d.ts +0 -2
- package/lib/tree/util/extend-helpers.d.ts.map +0 -1
- package/lib/tree/util/extend-helpers.js +0 -2
- package/lib/tree/util/extend-helpers.js.map +0 -1
- package/lib/tree/util/extend-roots.d.ts +0 -37
- package/lib/tree/util/extend-roots.d.ts.map +0 -1
- package/lib/tree/util/extend-roots.js +0 -700
- package/lib/tree/util/extend-roots.js.map +0 -1
- package/lib/tree/util/extend-roots.old.d.ts +0 -132
- package/lib/tree/util/extend-roots.old.d.ts.map +0 -1
- package/lib/tree/util/extend-roots.old.js +0 -2272
- package/lib/tree/util/extend-roots.old.js.map +0 -1
- package/lib/tree/util/extend-trace-debug.d.ts +0 -13
- package/lib/tree/util/extend-trace-debug.d.ts.map +0 -1
- package/lib/tree/util/extend-trace-debug.js +0 -34
- package/lib/tree/util/extend-trace-debug.js.map +0 -1
- package/lib/tree/util/extend-walk.d.ts +0 -53
- package/lib/tree/util/extend-walk.d.ts.map +0 -1
- package/lib/tree/util/extend-walk.js +0 -881
- package/lib/tree/util/extend-walk.js.map +0 -1
- package/lib/tree/util/extend.d.ts +0 -218
- package/lib/tree/util/extend.d.ts.map +0 -1
- package/lib/tree/util/extend.js +0 -3182
- package/lib/tree/util/extend.js.map +0 -1
- package/lib/tree/util/find-extendable-locations.d.ts +0 -2
- package/lib/tree/util/find-extendable-locations.d.ts.map +0 -1
- package/lib/tree/util/find-extendable-locations.js +0 -2
- package/lib/tree/util/find-extendable-locations.js.map +0 -1
- package/lib/tree/util/format.d.ts +0 -20
- package/lib/tree/util/format.d.ts.map +0 -1
- package/lib/tree/util/format.js +0 -67
- package/lib/tree/util/format.js.map +0 -1
- package/lib/tree/util/is-node.d.ts +0 -13
- package/lib/tree/util/is-node.d.ts.map +0 -1
- package/lib/tree/util/is-node.js +0 -43
- package/lib/tree/util/is-node.js.map +0 -1
- package/lib/tree/util/print.d.ts +0 -80
- package/lib/tree/util/print.d.ts.map +0 -1
- package/lib/tree/util/print.js +0 -205
- package/lib/tree/util/print.js.map +0 -1
- package/lib/tree/util/process-leading-is.d.ts +0 -25
- package/lib/tree/util/process-leading-is.d.ts.map +0 -1
- package/lib/tree/util/process-leading-is.js +0 -364
- package/lib/tree/util/process-leading-is.js.map +0 -1
- package/lib/tree/util/recursion-helper.d.ts +0 -15
- package/lib/tree/util/recursion-helper.d.ts.map +0 -1
- package/lib/tree/util/recursion-helper.js +0 -43
- package/lib/tree/util/recursion-helper.js.map +0 -1
- package/lib/tree/util/regex.d.ts +0 -4
- package/lib/tree/util/regex.d.ts.map +0 -1
- package/lib/tree/util/regex.js +0 -4
- package/lib/tree/util/regex.js.map +0 -1
- package/lib/tree/util/registry-utils.d.ts +0 -192
- package/lib/tree/util/registry-utils.d.ts.map +0 -1
- package/lib/tree/util/registry-utils.js +0 -1214
- package/lib/tree/util/registry-utils.js.map +0 -1
- package/lib/tree/util/ruleset-trace.d.ts +0 -4
- package/lib/tree/util/ruleset-trace.d.ts.map +0 -1
- package/lib/tree/util/ruleset-trace.js +0 -14
- package/lib/tree/util/ruleset-trace.js.map +0 -1
- package/lib/tree/util/selector-compare.d.ts +0 -2
- package/lib/tree/util/selector-compare.d.ts.map +0 -1
- package/lib/tree/util/selector-compare.js +0 -2
- package/lib/tree/util/selector-compare.js.map +0 -1
- package/lib/tree/util/selector-match-core.d.ts +0 -184
- package/lib/tree/util/selector-match-core.d.ts.map +0 -1
- package/lib/tree/util/selector-match-core.js +0 -1603
- package/lib/tree/util/selector-match-core.js.map +0 -1
- package/lib/tree/util/selector-utils.d.ts +0 -30
- package/lib/tree/util/selector-utils.d.ts.map +0 -1
- package/lib/tree/util/selector-utils.js +0 -100
- package/lib/tree/util/selector-utils.js.map +0 -1
- package/lib/tree/util/serialize-helper.d.ts +0 -13
- package/lib/tree/util/serialize-helper.d.ts.map +0 -1
- package/lib/tree/util/serialize-helper.js +0 -387
- package/lib/tree/util/serialize-helper.js.map +0 -1
- package/lib/tree/util/serialize-types.d.ts +0 -9
- package/lib/tree/util/serialize-types.d.ts.map +0 -1
- package/lib/tree/util/serialize-types.js +0 -216
- package/lib/tree/util/serialize-types.js.map +0 -1
- package/lib/tree/util/should-operate.d.ts +0 -23
- package/lib/tree/util/should-operate.d.ts.map +0 -1
- package/lib/tree/util/should-operate.js +0 -46
- package/lib/tree/util/should-operate.js.map +0 -1
- package/lib/tree/util/sourcemap.d.ts +0 -7
- package/lib/tree/util/sourcemap.d.ts.map +0 -1
- package/lib/tree/util/sourcemap.js +0 -25
- package/lib/tree/util/sourcemap.js.map +0 -1
- package/lib/types/config.d.ts +0 -205
- package/lib/types/config.d.ts.map +0 -1
- package/lib/types/config.js +0 -2
- package/lib/types/config.js.map +0 -1
- package/lib/types/index.d.ts +0 -15
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/index.js +0 -3
- package/lib/types/index.js.map +0 -1
- package/lib/types/modes.d.ts.map +0 -1
- package/lib/types/modes.js +0 -2
- package/lib/types/modes.js.map +0 -1
- package/lib/types.d.ts +0 -61
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/lib/use-webpack-resolver.d.ts +0 -9
- package/lib/use-webpack-resolver.d.ts.map +0 -1
- package/lib/use-webpack-resolver.js +0 -41
- package/lib/use-webpack-resolver.js.map +0 -1
- package/lib/visitor/index.d.ts +0 -136
- package/lib/visitor/index.d.ts.map +0 -1
- package/lib/visitor/index.js +0 -135
- package/lib/visitor/index.js.map +0 -1
- package/lib/visitor/less-visitor.d.ts +0 -7
- package/lib/visitor/less-visitor.d.ts.map +0 -1
- package/lib/visitor/less-visitor.js.map +0 -1
|
@@ -0,0 +1,2020 @@
|
|
|
1
|
+
import { Context } from '../../context.js';
|
|
2
|
+
import { EVAL, Node } from '../node-base.js';
|
|
3
|
+
|
|
4
|
+
import { Bool } from '../bool.js';
|
|
5
|
+
import type { Condition } from '../condition.js';
|
|
6
|
+
import { Nil } from '../nil.js';
|
|
7
|
+
import { Rules } from '../rules.js';
|
|
8
|
+
import type { Ruleset } from '../ruleset.js';
|
|
9
|
+
import type { AtRule } from '../at-rule.js';
|
|
10
|
+
import type { VarDeclaration } from '../declaration-var.js';
|
|
11
|
+
import { VarDeclaration as VarDeclarationCtor } from '../declaration-var.js';
|
|
12
|
+
import { list, type List } from '../list.js';
|
|
13
|
+
import { Sequence } from '../sequence.js';
|
|
14
|
+
import { Any } from '../any.js';
|
|
15
|
+
import { N } from '../node-type.js';
|
|
16
|
+
import { CALLER, CANONICAL, F_VISIBLE } from '../node.js';
|
|
17
|
+
import { isNode } from './is-node.js';
|
|
18
|
+
import { comparePosition } from './compare.js';
|
|
19
|
+
import { getParent, getSourceParent, setChildren, setParent, setSourceParent } from './field-helpers.js';
|
|
20
|
+
import { addParentEdge } from './cursor.js';
|
|
21
|
+
import type { Mixin } from '../mixin.js';
|
|
22
|
+
import { isThenable, type MaybePromise } from '@jesscss/awaitable-pipe';
|
|
23
|
+
import { cast } from './cast.js';
|
|
24
|
+
import { isPlainObject } from './collections.js';
|
|
25
|
+
import type { MixinEntry } from '../rules.js';
|
|
26
|
+
import type { RenderKey } from '../node-base.js';
|
|
27
|
+
import { getCurrentParentNode } from './selector-utils.js';
|
|
28
|
+
|
|
29
|
+
export const enum MixinDefaultGroup {
|
|
30
|
+
FalseEither = -1,
|
|
31
|
+
None = 0,
|
|
32
|
+
True = 1,
|
|
33
|
+
False = 2
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PreparedMixinCandidateInvocation = {
|
|
37
|
+
rules: Rules;
|
|
38
|
+
params: List<Node> | undefined;
|
|
39
|
+
outerRules: Rules | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type EvaluatedMixinGuard = {
|
|
43
|
+
passes: boolean;
|
|
44
|
+
outerRules: Rules | undefined;
|
|
45
|
+
defaultGroup?: MixinDefaultGroup;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type PendingMixinDefaultCandidate<TCandidate = unknown> = {
|
|
49
|
+
candidate: TCandidate;
|
|
50
|
+
rules: Rules;
|
|
51
|
+
outerRules?: Rules;
|
|
52
|
+
params?: List<Node>;
|
|
53
|
+
group: MixinDefaultGroup;
|
|
54
|
+
lookupScope?: Rules;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ProcessPreparedMixinCandidateOptions<TCandidate> = {
|
|
58
|
+
candidate: TCandidate;
|
|
59
|
+
rules: Rules;
|
|
60
|
+
params?: List<Node>;
|
|
61
|
+
outerRules?: Rules;
|
|
62
|
+
guard?: Condition | Bool;
|
|
63
|
+
parent: Node | undefined;
|
|
64
|
+
guardScopeChildren?: readonly Node[];
|
|
65
|
+
hasAnyDefault: boolean;
|
|
66
|
+
candidateHasDefault: boolean;
|
|
67
|
+
context: Context;
|
|
68
|
+
evaluateCandidateOutput: (
|
|
69
|
+
candidate: TCandidate,
|
|
70
|
+
rules: Rules,
|
|
71
|
+
outerRules: Rules | undefined,
|
|
72
|
+
params: List<Node> | undefined,
|
|
73
|
+
) => MaybePromise<void>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const bindableParamTemplates = new WeakMap<Node, VarDeclaration>();
|
|
77
|
+
const restParamTemplates = new WeakMap<Node, VarDeclaration>();
|
|
78
|
+
function getCurrentRulesetGuard(
|
|
79
|
+
ruleset: Ruleset,
|
|
80
|
+
context: Context
|
|
81
|
+
): Node | undefined {
|
|
82
|
+
return ruleset.get('guard', context) as Node | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getCurrentMixinParams(
|
|
86
|
+
mixin: Mixin,
|
|
87
|
+
context: Context | RenderKey | undefined
|
|
88
|
+
): List<Node> | undefined {
|
|
89
|
+
return mixin.get('params', context) as List<Node> | undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getCanonicalSourceParent(
|
|
93
|
+
node: Node | undefined
|
|
94
|
+
): Node | undefined {
|
|
95
|
+
return node?.sourceParent;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getBindableParamTemplate(
|
|
99
|
+
param: Node,
|
|
100
|
+
context: Context
|
|
101
|
+
): VarDeclaration {
|
|
102
|
+
const cached = bindableParamTemplates.get(param);
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
const name = String(param.valueOf());
|
|
107
|
+
const template = new VarDeclarationCtor({
|
|
108
|
+
name: new Any(name, { role: 'property' }),
|
|
109
|
+
value: new Nil()
|
|
110
|
+
}, { paramVar: true }, param.location, context.treeContext);
|
|
111
|
+
bindableParamTemplates.set(param, template);
|
|
112
|
+
return template;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getRestParamTemplate(
|
|
116
|
+
param: Node,
|
|
117
|
+
restName: string,
|
|
118
|
+
context: Context
|
|
119
|
+
): VarDeclaration {
|
|
120
|
+
const cached = restParamTemplates.get(param);
|
|
121
|
+
if (cached) {
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
const template = new VarDeclarationCtor({
|
|
125
|
+
name: new Any(restName, { role: 'property' }),
|
|
126
|
+
value: new Nil()
|
|
127
|
+
}, { paramVar: true }, param.location, context.treeContext);
|
|
128
|
+
restParamTemplates.set(param, template);
|
|
129
|
+
return template;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Follow a Rules node back to its canonical source root. Mixin/ruleset
|
|
134
|
+
* candidate setup wants this shared notion of "the source rules subtree" so
|
|
135
|
+
* that eval state subtrees are always created against the canonical backing body.
|
|
136
|
+
*/
|
|
137
|
+
export function getRootSourceRules(rules: Rules): Rules {
|
|
138
|
+
let current = rules;
|
|
139
|
+
const seen = new Set<Rules>();
|
|
140
|
+
while (current.sourceNode && isNode(current.sourceNode, N.Rules)) {
|
|
141
|
+
const next = current.sourceNode as Rules;
|
|
142
|
+
if (next === current || seen.has(next)) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
seen.add(current);
|
|
146
|
+
current = next;
|
|
147
|
+
}
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the canonical source rules for a mixin-like candidate and create a
|
|
153
|
+
* per-call eval state subtree when a session is active.
|
|
154
|
+
*/
|
|
155
|
+
export function createMixinCandidateInstanceRoot(
|
|
156
|
+
_candidate: MixinEntry,
|
|
157
|
+
_context: Context
|
|
158
|
+
): undefined {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Apply the final return policy for mixin invocation output.
|
|
164
|
+
*
|
|
165
|
+
* - Context receivers get a live `Rules` result (or `Nil` if empty), with
|
|
166
|
+
* `ruleCounter` assigned on first return.
|
|
167
|
+
* - Non-Context receivers get a plain object view, preserving legacy
|
|
168
|
+
* `getFunctionFromMixins()` semantics.
|
|
169
|
+
*/
|
|
170
|
+
export function finalizeMixinInvocationReturn(
|
|
171
|
+
output: Rules,
|
|
172
|
+
receiver: Context | Node
|
|
173
|
+
): Rules | Nil | ReturnType<Rules['toObject']> {
|
|
174
|
+
if (receiver instanceof Context) {
|
|
175
|
+
output.index ??= receiver.ruleCounter++;
|
|
176
|
+
if (output.value.length === 0) {
|
|
177
|
+
return new Nil();
|
|
178
|
+
}
|
|
179
|
+
return output;
|
|
180
|
+
}
|
|
181
|
+
return output.toObject();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Bind one mixin param through the active eval state subtree instead of mutating the
|
|
186
|
+
* canonical VarDeclaration. This is the smallest useful primitive behind direct
|
|
187
|
+
* mixin invocation.
|
|
188
|
+
*/
|
|
189
|
+
export function bindMixinParamValue(
|
|
190
|
+
param: VarDeclaration,
|
|
191
|
+
value: Node,
|
|
192
|
+
context: Context
|
|
193
|
+
): void {
|
|
194
|
+
param.value = value;
|
|
195
|
+
param.adopt(value, context);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create the transient scope that holds bound mixin parameters. This is the
|
|
200
|
+
* direct replacement for the inlined outerRules construction in
|
|
201
|
+
* getFunctionFromMixins().
|
|
202
|
+
*/
|
|
203
|
+
export function createMixinParamScope(
|
|
204
|
+
index: number,
|
|
205
|
+
renderKey: RenderKey
|
|
206
|
+
): Rules {
|
|
207
|
+
const scope = Rules.create([], {
|
|
208
|
+
rulesVisibility: {
|
|
209
|
+
Ruleset: 'public',
|
|
210
|
+
Declaration: 'public',
|
|
211
|
+
VarDeclaration: 'public',
|
|
212
|
+
Mixin: 'public'
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
scope.index = index;
|
|
216
|
+
scope.renderKey = renderKey;
|
|
217
|
+
return scope;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function createRenderOwnedSequence(
|
|
221
|
+
items: readonly Node[],
|
|
222
|
+
renderKey: RenderKey,
|
|
223
|
+
context: Context
|
|
224
|
+
): Sequence {
|
|
225
|
+
const sequence = new Sequence([], { forceSpacing: true }, undefined, context.treeContext);
|
|
226
|
+
sequence.renderKey = renderKey;
|
|
227
|
+
(sequence as unknown as { value: Node[] }).value = [...items];
|
|
228
|
+
const edgeContext = { ...context, renderKey };
|
|
229
|
+
for (const item of items) {
|
|
230
|
+
setParent(item, sequence, edgeContext);
|
|
231
|
+
}
|
|
232
|
+
return sequence;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Register already-bound parameter declarations into the transient mixin scope.
|
|
237
|
+
* Matching/rest conversion still happens outside this helper; this primitive is
|
|
238
|
+
* only responsible for making those params visible to lookup.
|
|
239
|
+
*/
|
|
240
|
+
export function populateMixinParamScope(
|
|
241
|
+
scope: Rules,
|
|
242
|
+
params: List<Node>,
|
|
243
|
+
context: Context
|
|
244
|
+
): void {
|
|
245
|
+
const paramItems = params.get('value');
|
|
246
|
+
for (let i = 0; i < paramItems.length; i++) {
|
|
247
|
+
const param = paramItems[i]!;
|
|
248
|
+
if (!isNode(param, N.VarDeclaration)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (param.index === undefined) {
|
|
252
|
+
param.index = -(i + 1);
|
|
253
|
+
}
|
|
254
|
+
param.options ??= {};
|
|
255
|
+
param.options.paramVar = true;
|
|
256
|
+
param.removeFlag(F_VISIBLE);
|
|
257
|
+
const name = String(param.get('name', scope.renderKey).valueOf());
|
|
258
|
+
setParent(param, scope, context);
|
|
259
|
+
scope.setInvocationBinding(name, { declaration: param });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Define the Less-style @arguments variable inside the transient mixin scope.
|
|
265
|
+
* This stays a separate primitive so direct mixin invocation can reuse it
|
|
266
|
+
* without dragging along the rest of getFunctionFromMixins().
|
|
267
|
+
*/
|
|
268
|
+
export function defineMixinArgumentsInScope(
|
|
269
|
+
scope: Rules,
|
|
270
|
+
params: List<Node> | undefined,
|
|
271
|
+
nodeArgs: readonly Node[],
|
|
272
|
+
context: Context
|
|
273
|
+
): void {
|
|
274
|
+
scope.setInvocationBinding('arguments', {
|
|
275
|
+
factory: (rules, bindingContext) => {
|
|
276
|
+
const nextRenderKey = rules.renderKey;
|
|
277
|
+
const paramValues = params?.get('value', nextRenderKey)
|
|
278
|
+
.filter((p): p is VarDeclaration => isNode(p, N.VarDeclaration))
|
|
279
|
+
.map(p => p.get('value', nextRenderKey))
|
|
280
|
+
.filter((value): value is Node => value instanceof Node);
|
|
281
|
+
const argumentNodes = (paramValues && paramValues.length > 0) ? paramValues : nodeArgs;
|
|
282
|
+
const argumentsArgs: Node[] = [];
|
|
283
|
+
for (const argNode of argumentNodes) {
|
|
284
|
+
if (isNode(argNode, N.Sequence) && (argNode as Sequence).get('value', nextRenderKey).length > 1) {
|
|
285
|
+
argumentsArgs.push(...(argNode as Sequence).get('value', nextRenderKey));
|
|
286
|
+
} else {
|
|
287
|
+
argumentsArgs.push(argNode);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const argumentsDecl = new VarDeclarationCtor({
|
|
291
|
+
name: new Any('arguments', { role: 'property' }),
|
|
292
|
+
value: createRenderOwnedSequence(argumentsArgs, nextRenderKey, bindingContext ?? context)
|
|
293
|
+
}, { readonly: true, paramVar: true });
|
|
294
|
+
argumentsDecl.removeFlag(F_VISIBLE);
|
|
295
|
+
argumentsDecl.renderKey = nextRenderKey;
|
|
296
|
+
argumentsDecl.preEvaluated = true;
|
|
297
|
+
argumentsDecl.evaluated = true;
|
|
298
|
+
return argumentsDecl;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Seed a fresh reset-eval guard scope from the active param scope without
|
|
305
|
+
* touching canonical parentage. The returned scope is safe to reuse for a
|
|
306
|
+
* single guard probe.
|
|
307
|
+
*/
|
|
308
|
+
export function seedMixinGuardScope(
|
|
309
|
+
scope: Rules | undefined,
|
|
310
|
+
guardParent: Node | undefined,
|
|
311
|
+
guardNode: Node | undefined,
|
|
312
|
+
context: Context,
|
|
313
|
+
scopeChildren?: readonly Node[]
|
|
314
|
+
): Rules {
|
|
315
|
+
const nextScope = scope ?? Rules.create([]);
|
|
316
|
+
setParent(nextScope, guardParent, context);
|
|
317
|
+
const activeChildren = scopeChildren ?? nextScope.getRegistryChildren(context);
|
|
318
|
+
if (scopeChildren) {
|
|
319
|
+
setChildren(nextScope, activeChildren, context, { markDirty: false });
|
|
320
|
+
} else {
|
|
321
|
+
for (const child of activeChildren) {
|
|
322
|
+
setParent(child, nextScope, context);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (guardNode) {
|
|
326
|
+
nextScope.adopt(guardNode, context);
|
|
327
|
+
}
|
|
328
|
+
return nextScope;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function captureMixinScopeSnapshot(
|
|
332
|
+
scope: Rules | undefined,
|
|
333
|
+
scopeChildren: readonly Node[] | undefined,
|
|
334
|
+
context: Context
|
|
335
|
+
): Rules | undefined {
|
|
336
|
+
if (!scope) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const capturedChildren = scopeChildren ?? scope.getRegistryChildren(context);
|
|
340
|
+
const captured = scope.createPlacementWrapperWithChildren(
|
|
341
|
+
capturedChildren,
|
|
342
|
+
scope.renderKey
|
|
343
|
+
);
|
|
344
|
+
captured.parent = getParent(scope, context);
|
|
345
|
+
captured.sourceParent = scope.sourceParent;
|
|
346
|
+
return captured;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Prepare the transient scope used by a single mixin invocation. This is the
|
|
351
|
+
* smallest complete lookup-ready scope primitive for direct canonical-body eval:
|
|
352
|
+
* the caller gets a param scope with registered params / @arguments and the
|
|
353
|
+
* canonical body attached through state parent shadow only.
|
|
354
|
+
*/
|
|
355
|
+
export function prepareMixinInvocationScope(
|
|
356
|
+
definitionParent: Node | undefined,
|
|
357
|
+
placementParent: Node | undefined,
|
|
358
|
+
sourceParent: Node | undefined,
|
|
359
|
+
index: number,
|
|
360
|
+
renderKey: RenderKey,
|
|
361
|
+
params: List<Node> | undefined,
|
|
362
|
+
nodeArgs: readonly Node[],
|
|
363
|
+
context: Context
|
|
364
|
+
): Rules | undefined {
|
|
365
|
+
if (!params) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
const scope = createMixinParamScope(index, renderKey);
|
|
369
|
+
populateMixinParamScope(scope, params, context);
|
|
370
|
+
defineMixinArgumentsInScope(scope, params, nodeArgs, context);
|
|
371
|
+
scope.parent = placementParent ?? definitionParent;
|
|
372
|
+
scope.sourceParent = sourceParent;
|
|
373
|
+
return scope;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function createMixinInvocationRules(
|
|
377
|
+
body: Rules,
|
|
378
|
+
lookupParent: Node | undefined,
|
|
379
|
+
lexicalSourceParent: Node | undefined,
|
|
380
|
+
sourceParent: Node | undefined,
|
|
381
|
+
index: number,
|
|
382
|
+
context: Context,
|
|
383
|
+
renderKey: RenderKey
|
|
384
|
+
): Rules {
|
|
385
|
+
const wrapper = body.createShallowBodyWrapper(undefined, renderKey);
|
|
386
|
+
wrapper.index = index;
|
|
387
|
+
wrapper.parent = lookupParent;
|
|
388
|
+
wrapper.sourceParent = sourceParent ?? lexicalSourceParent;
|
|
389
|
+
|
|
390
|
+
wrapper.options = {
|
|
391
|
+
...wrapper.options,
|
|
392
|
+
rulesVisibility: {
|
|
393
|
+
...(wrapper.options.rulesVisibility ?? {}),
|
|
394
|
+
VarDeclaration: 'public'
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
return wrapper;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function bindStructuralSourceParent(
|
|
401
|
+
node: Node,
|
|
402
|
+
sourceParent: Node,
|
|
403
|
+
context: Context,
|
|
404
|
+
seen: Set<Node> = new Set()
|
|
405
|
+
): void {
|
|
406
|
+
if (seen.has(node)) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
seen.add(node);
|
|
410
|
+
node.sourceParent = sourceParent;
|
|
411
|
+
setSourceParent(node, sourceParent, context);
|
|
412
|
+
if (
|
|
413
|
+
node.renderKey !== undefined
|
|
414
|
+
&& node.renderKey !== CANONICAL
|
|
415
|
+
&& node.renderKey !== context.renderKey
|
|
416
|
+
) {
|
|
417
|
+
setSourceParent(node, sourceParent, {
|
|
418
|
+
...context,
|
|
419
|
+
renderKey: node.renderKey
|
|
420
|
+
} as Context);
|
|
421
|
+
}
|
|
422
|
+
const childKeys = (node.constructor as typeof Node).childKeys;
|
|
423
|
+
if (!childKeys) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
for (const key of childKeys) {
|
|
427
|
+
const value = (node as unknown as Record<string, unknown>)[key];
|
|
428
|
+
if (Array.isArray(value)) {
|
|
429
|
+
for (const item of value) {
|
|
430
|
+
if (item instanceof Node) {
|
|
431
|
+
bindStructuralSourceParent(item, sourceParent, context, seen);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (value instanceof Node) {
|
|
437
|
+
bindStructuralSourceParent(value, sourceParent, context, seen);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function bindStructuralParentTree(
|
|
443
|
+
node: Node,
|
|
444
|
+
parent: Node,
|
|
445
|
+
context: Context,
|
|
446
|
+
seen: Set<Node> = new Set()
|
|
447
|
+
): void {
|
|
448
|
+
if (seen.has(node)) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
seen.add(node);
|
|
452
|
+
setParent(node, parent, context);
|
|
453
|
+
const childKeys = (node.constructor as typeof Node).childKeys;
|
|
454
|
+
if (!childKeys) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
for (const key of childKeys) {
|
|
458
|
+
const value = (node as unknown as Record<string, unknown>)[key];
|
|
459
|
+
if (Array.isArray(value)) {
|
|
460
|
+
for (const item of value) {
|
|
461
|
+
if (item instanceof Node) {
|
|
462
|
+
bindStructuralParentTree(item, node, context, seen);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (value instanceof Node) {
|
|
468
|
+
bindStructuralParentTree(value, node, context, seen);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function projectParentChainForRenderKey(
|
|
474
|
+
node: Node | undefined,
|
|
475
|
+
sourceContext: Context,
|
|
476
|
+
targetContext: Context,
|
|
477
|
+
seen: Set<Node> = new Set()
|
|
478
|
+
): void {
|
|
479
|
+
if (!node || seen.has(node)) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
seen.add(node);
|
|
483
|
+
const nodeSourceContext = {
|
|
484
|
+
...sourceContext,
|
|
485
|
+
renderKey: node.renderKey ?? sourceContext.renderKey
|
|
486
|
+
} as Context;
|
|
487
|
+
const parent = getParent(node, nodeSourceContext);
|
|
488
|
+
if (!parent) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (getParent(node, targetContext) !== parent) {
|
|
492
|
+
setParent(node, parent, targetContext);
|
|
493
|
+
}
|
|
494
|
+
projectParentChainForRenderKey(parent, sourceContext, targetContext, seen);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function anchorCallSiteValue(
|
|
498
|
+
value: Node,
|
|
499
|
+
sourceParent: Node,
|
|
500
|
+
context: Context
|
|
501
|
+
): void {
|
|
502
|
+
if (
|
|
503
|
+
getSourceParent(value, context) === undefined
|
|
504
|
+
&& isNode(value, N.Reference | N.Mixin | N.Sequence | N.List | N.Rules | N.Ruleset | N.AtRule)
|
|
505
|
+
) {
|
|
506
|
+
setSourceParent(value, sourceParent, context);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function getUsableInvocationSourceParent(
|
|
511
|
+
sourceParent: Node | undefined,
|
|
512
|
+
fallback: Node | undefined
|
|
513
|
+
): Node | undefined {
|
|
514
|
+
if (sourceParent && !isNode(sourceParent, N.Reference | N.Call)) {
|
|
515
|
+
return sourceParent;
|
|
516
|
+
}
|
|
517
|
+
return fallback;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Normalize mixin params for invocation-time lookup registration.
|
|
522
|
+
*
|
|
523
|
+
* Rest params must become VarDeclarations before they can participate in the
|
|
524
|
+
* transient param scope. Keep that conversion here instead of inline inside the
|
|
525
|
+
* candidate loop.
|
|
526
|
+
*/
|
|
527
|
+
export function normalizeMixinInvocationParams(
|
|
528
|
+
params: List<Node> | undefined,
|
|
529
|
+
context: Context
|
|
530
|
+
): List<Node> | undefined {
|
|
531
|
+
if (!params) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let unnamedRestCount = 0;
|
|
536
|
+
const paramItems = params.get('value');
|
|
537
|
+
for (let i = 0; i < paramItems.length; i++) {
|
|
538
|
+
const param = paramItems[i]!;
|
|
539
|
+
if (param.type !== 'Rest') {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let restName: string;
|
|
544
|
+
if (typeof (param as any).value === 'string') {
|
|
545
|
+
restName = (param as any).value;
|
|
546
|
+
} else {
|
|
547
|
+
restName = unnamedRestCount === 0 ? 'rest' : `rest${unnamedRestCount + 1}`;
|
|
548
|
+
unnamedRestCount++;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const restValue = isNode((param as any).value)
|
|
552
|
+
? (param as any).value as Node
|
|
553
|
+
: (
|
|
554
|
+
context.treeContext?.file
|
|
555
|
+
? new Sequence([])
|
|
556
|
+
: new Any(restName, { role: 'property' })
|
|
557
|
+
);
|
|
558
|
+
const restVarDecl = new VarDeclarationCtor({
|
|
559
|
+
name: new Any(restName, { role: 'property' }),
|
|
560
|
+
value: restValue
|
|
561
|
+
}, { paramVar: true });
|
|
562
|
+
|
|
563
|
+
params.value[i] = restVarDecl;
|
|
564
|
+
params.adopt(restVarDecl, context);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return params;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildBoundMixinParams(
|
|
571
|
+
params: List<Node> | undefined,
|
|
572
|
+
nodeArgs: readonly Node[],
|
|
573
|
+
bindingSourceParent: Node | undefined,
|
|
574
|
+
renderKey: RenderKey,
|
|
575
|
+
context: Context
|
|
576
|
+
): List<Node> | undefined {
|
|
577
|
+
if (!params) {
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const namedArgs = new Map<string, Node>();
|
|
582
|
+
const positionalArgs: Node[] = [];
|
|
583
|
+
for (const arg of nodeArgs) {
|
|
584
|
+
if (isNode(arg, N.VarDeclaration)) {
|
|
585
|
+
const argName = String((arg as VarDeclaration).get('name').valueOf());
|
|
586
|
+
const argValue = (arg as VarDeclaration).get('value') as Node;
|
|
587
|
+
namedArgs.set(argName, argValue);
|
|
588
|
+
} else {
|
|
589
|
+
positionalArgs.push(arg);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const bindingContext = { ...context, renderKey };
|
|
594
|
+
const boundParams = list([], params.options ? { ...params.options } : undefined);
|
|
595
|
+
let positionalIndex = 0;
|
|
596
|
+
const cloneDefaultParamValue = (value: Node): Node => {
|
|
597
|
+
return value.clone(false, undefined, bindingContext);
|
|
598
|
+
};
|
|
599
|
+
const cloneBoundParamTemplate = (
|
|
600
|
+
param: VarDeclaration,
|
|
601
|
+
boundValue: Node | undefined
|
|
602
|
+
): VarDeclaration => {
|
|
603
|
+
const boundParam = param.clone(false, undefined, bindingContext) as VarDeclaration;
|
|
604
|
+
boundParam.renderKey = renderKey;
|
|
605
|
+
boundParam.options = { ...(boundParam.options ?? {}), paramVar: true };
|
|
606
|
+
boundParam.preEvaluated = true;
|
|
607
|
+
boundParam.evaluated = true;
|
|
608
|
+
if (boundValue) {
|
|
609
|
+
boundParam.setCurrentValue(boundValue, bindingContext);
|
|
610
|
+
}
|
|
611
|
+
return boundParam;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
for (let index = 0; index < params.get('value').length; index++) {
|
|
615
|
+
const param = params.get('value')[index]!;
|
|
616
|
+
|
|
617
|
+
if (isNode(param, N.VarDeclaration)) {
|
|
618
|
+
const paramDecl = param as VarDeclaration;
|
|
619
|
+
const name = String(paramDecl.get('name').valueOf());
|
|
620
|
+
|
|
621
|
+
const hasNamedArg = namedArgs.has(name);
|
|
622
|
+
const hasPositionalArg = positionalIndex < positionalArgs.length;
|
|
623
|
+
const boundValue = hasNamedArg
|
|
624
|
+
? namedArgs.get(name)!
|
|
625
|
+
: hasPositionalArg
|
|
626
|
+
? positionalArgs[positionalIndex++]!
|
|
627
|
+
: cloneDefaultParamValue(paramDecl.get('value'));
|
|
628
|
+
if (
|
|
629
|
+
bindingSourceParent
|
|
630
|
+
&& isNode(boundValue)
|
|
631
|
+
) {
|
|
632
|
+
anchorCallSiteValue(boundValue, bindingSourceParent, bindingContext);
|
|
633
|
+
}
|
|
634
|
+
const boundParam = cloneBoundParamTemplate(paramDecl, boundValue);
|
|
635
|
+
boundParam.index = paramDecl.index ?? -(index + 1);
|
|
636
|
+
boundParams.push(boundParam);
|
|
637
|
+
namedArgs.delete(name);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (isNode(param, N.Any) && param.role === 'property') {
|
|
642
|
+
const name = String(param.valueOf());
|
|
643
|
+
const hasNamedArg = namedArgs.has(name);
|
|
644
|
+
const hasPositionalArg = positionalIndex < positionalArgs.length;
|
|
645
|
+
const boundValue = hasNamedArg
|
|
646
|
+
? namedArgs.get(name)!
|
|
647
|
+
: hasPositionalArg
|
|
648
|
+
? positionalArgs[positionalIndex++]!
|
|
649
|
+
: undefined;
|
|
650
|
+
if (
|
|
651
|
+
boundValue
|
|
652
|
+
&& (hasNamedArg || hasPositionalArg)
|
|
653
|
+
&& bindingSourceParent
|
|
654
|
+
&& isNode(boundValue)
|
|
655
|
+
) {
|
|
656
|
+
anchorCallSiteValue(boundValue, bindingSourceParent, bindingContext);
|
|
657
|
+
}
|
|
658
|
+
const template = getBindableParamTemplate(param, context);
|
|
659
|
+
const boundParam = cloneBoundParamTemplate(template, boundValue);
|
|
660
|
+
boundParam.index = -(index + 1);
|
|
661
|
+
boundParams.push(boundParam);
|
|
662
|
+
namedArgs.delete(name);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (param.type === 'Rest') {
|
|
667
|
+
const restName = typeof (param as unknown as { value?: string }).value === 'string'
|
|
668
|
+
? String((param as unknown as { value: string }).value)
|
|
669
|
+
: 'rest';
|
|
670
|
+
const restValues = positionalArgs.slice(positionalIndex);
|
|
671
|
+
positionalIndex = positionalArgs.length;
|
|
672
|
+
const restValue = restValues.length > 0
|
|
673
|
+
? createRenderOwnedSequence(restValues, renderKey, context)
|
|
674
|
+
: (
|
|
675
|
+
context.treeContext?.file
|
|
676
|
+
? createRenderOwnedSequence([], renderKey, context)
|
|
677
|
+
: new Any(restName, { role: 'property' })
|
|
678
|
+
);
|
|
679
|
+
const restTemplate = getRestParamTemplate(param, restName, context);
|
|
680
|
+
const restVarDecl = cloneBoundParamTemplate(restTemplate, restValue);
|
|
681
|
+
restVarDecl.index = -(index + 1);
|
|
682
|
+
boundParams.push(restVarDecl);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Non-binding pattern params still consume a positional argument slot
|
|
687
|
+
// when matched, so later bindable params line up with the correct arg.
|
|
688
|
+
if (positionalIndex < positionalArgs.length) {
|
|
689
|
+
positionalIndex++;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return boundParams;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Prepare the normal mixin-candidate body for direct invocation.
|
|
698
|
+
*
|
|
699
|
+
* This is the slice of the old candidate loop that wires per-call identity,
|
|
700
|
+
* visibility, parent/source provenance, param normalization, and lookup scope
|
|
701
|
+
* construction before guard evaluation or output shaping runs.
|
|
702
|
+
*/
|
|
703
|
+
export function prepareMixinCandidateInvocation(
|
|
704
|
+
rules: Rules,
|
|
705
|
+
params: List<Node> | undefined,
|
|
706
|
+
parent: Node | undefined,
|
|
707
|
+
sourceParent: Node | undefined,
|
|
708
|
+
index: number,
|
|
709
|
+
nodeArgs: readonly Node[],
|
|
710
|
+
context: Context
|
|
711
|
+
): PreparedMixinCandidateInvocation {
|
|
712
|
+
const renderKey = context.nextRenderKey();
|
|
713
|
+
const bindingSourceParent = sourceParent ?? context.caller;
|
|
714
|
+
const boundParams = buildBoundMixinParams(
|
|
715
|
+
params,
|
|
716
|
+
nodeArgs,
|
|
717
|
+
bindingSourceParent,
|
|
718
|
+
renderKey,
|
|
719
|
+
context
|
|
720
|
+
);
|
|
721
|
+
const outerRules = boundParams
|
|
722
|
+
? prepareMixinInvocationScope(
|
|
723
|
+
parent,
|
|
724
|
+
parent,
|
|
725
|
+
sourceParent,
|
|
726
|
+
index,
|
|
727
|
+
renderKey,
|
|
728
|
+
boundParams,
|
|
729
|
+
nodeArgs,
|
|
730
|
+
context
|
|
731
|
+
)
|
|
732
|
+
: undefined;
|
|
733
|
+
const lookupParent = outerRules ?? parent;
|
|
734
|
+
const sourceRules = getRootSourceRules(rules);
|
|
735
|
+
const invocationRules = createMixinInvocationRules(
|
|
736
|
+
sourceRules,
|
|
737
|
+
lookupParent,
|
|
738
|
+
parent,
|
|
739
|
+
sourceParent,
|
|
740
|
+
index,
|
|
741
|
+
context,
|
|
742
|
+
renderKey
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
return {
|
|
746
|
+
rules: invocationRules,
|
|
747
|
+
params: boundParams,
|
|
748
|
+
outerRules
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Run an evaluation step with the mixin invocation scope as the active lookup
|
|
754
|
+
* scope, then restore the caller's prior rulesContext.
|
|
755
|
+
*/
|
|
756
|
+
export function withMixinLookupScope<T>(
|
|
757
|
+
scope: Rules | undefined,
|
|
758
|
+
lookupScope: Rules | undefined,
|
|
759
|
+
context: Context,
|
|
760
|
+
fn: () => MaybePromise<T>
|
|
761
|
+
): MaybePromise<T> {
|
|
762
|
+
const previousRulesContext = context.rulesContext;
|
|
763
|
+
const previousRenderKey = context.renderKey;
|
|
764
|
+
const previousLookupScope = context.lookupScope;
|
|
765
|
+
if (scope) {
|
|
766
|
+
context.rulesContext = scope;
|
|
767
|
+
}
|
|
768
|
+
context.lookupScope = lookupScope;
|
|
769
|
+
context.renderKey = scope?.renderKey ?? previousRenderKey;
|
|
770
|
+
try {
|
|
771
|
+
const out = fn();
|
|
772
|
+
if (isThenable(out)) {
|
|
773
|
+
return (out as Promise<T>).finally(() => {
|
|
774
|
+
context.rulesContext = previousRulesContext;
|
|
775
|
+
context.renderKey = previousRenderKey;
|
|
776
|
+
context.lookupScope = previousLookupScope;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
context.rulesContext = previousRulesContext;
|
|
780
|
+
context.renderKey = previousRenderKey;
|
|
781
|
+
context.lookupScope = previousLookupScope;
|
|
782
|
+
return out;
|
|
783
|
+
} catch (error) {
|
|
784
|
+
context.rulesContext = previousRulesContext;
|
|
785
|
+
context.renderKey = previousRenderKey;
|
|
786
|
+
context.lookupScope = previousLookupScope;
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Evaluate one mixin guard candidate against the prepared invocation scope.
|
|
793
|
+
*
|
|
794
|
+
* This centralizes the reset-session guard probe behavior so the caller loop
|
|
795
|
+
* only has to deal with the result (`passes`, optional default group, evolved
|
|
796
|
+
* scope) instead of the probing mechanics.
|
|
797
|
+
*/
|
|
798
|
+
export async function evaluateMixinGuardCandidate(
|
|
799
|
+
guardNode: Condition | Bool | undefined,
|
|
800
|
+
outerRules: Rules | undefined,
|
|
801
|
+
guardParent: Node | undefined,
|
|
802
|
+
context: Context,
|
|
803
|
+
scopeChildren: readonly Node[] | undefined,
|
|
804
|
+
hasDefault: boolean
|
|
805
|
+
): Promise<EvaluatedMixinGuard> {
|
|
806
|
+
if (!guardNode) {
|
|
807
|
+
return { passes: true, outerRules };
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const evaluateWithDefault = async (
|
|
811
|
+
isDefaultValue: boolean
|
|
812
|
+
): Promise<{ passes: boolean; outerRules: Rules | undefined }> => {
|
|
813
|
+
const prevIsDefault = context.isDefault;
|
|
814
|
+
const previousRulesContext = context.rulesContext;
|
|
815
|
+
const previousRenderKey = context.renderKey;
|
|
816
|
+
const previousLookupScope = context.lookupScope;
|
|
817
|
+
try {
|
|
818
|
+
const nextScope = seedMixinGuardScope(
|
|
819
|
+
outerRules,
|
|
820
|
+
guardParent,
|
|
821
|
+
guardNode,
|
|
822
|
+
context,
|
|
823
|
+
scopeChildren
|
|
824
|
+
);
|
|
825
|
+
context.isDefault = isDefaultValue;
|
|
826
|
+
context.rulesContext = nextScope;
|
|
827
|
+
context.lookupScope = nextScope;
|
|
828
|
+
context.renderKey = nextScope.renderKey ?? previousRenderKey;
|
|
829
|
+
const probeResult = await guardNode.eval(context);
|
|
830
|
+
return {
|
|
831
|
+
passes: probeResult instanceof Bool && probeResult.value === true,
|
|
832
|
+
outerRules: nextScope
|
|
833
|
+
};
|
|
834
|
+
} finally {
|
|
835
|
+
context.rulesContext = previousRulesContext;
|
|
836
|
+
context.renderKey = previousRenderKey;
|
|
837
|
+
context.lookupScope = previousLookupScope;
|
|
838
|
+
context.isDefault = prevIsDefault;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
if (hasDefault) {
|
|
843
|
+
const passWhenDefaultFalse = await evaluateWithDefault(false);
|
|
844
|
+
const passWhenDefaultTrue = await evaluateWithDefault(true);
|
|
845
|
+
const defaultGroup = classifyMixinDefaultGroup(
|
|
846
|
+
passWhenDefaultFalse.passes,
|
|
847
|
+
passWhenDefaultTrue.passes
|
|
848
|
+
);
|
|
849
|
+
return {
|
|
850
|
+
passes: defaultGroup !== undefined,
|
|
851
|
+
outerRules: passWhenDefaultTrue.outerRules ?? passWhenDefaultFalse.outerRules ?? outerRules,
|
|
852
|
+
defaultGroup
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const result = await evaluateWithDefault(false);
|
|
857
|
+
return {
|
|
858
|
+
passes: result.passes,
|
|
859
|
+
outerRules: result.outerRules
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Classify a default() guard probe pair into Less-style default groups.
|
|
865
|
+
*/
|
|
866
|
+
export function classifyMixinDefaultGroup(
|
|
867
|
+
passWhenDefaultFalse: boolean,
|
|
868
|
+
passWhenDefaultTrue: boolean
|
|
869
|
+
): MixinDefaultGroup | undefined {
|
|
870
|
+
if (!passWhenDefaultFalse && !passWhenDefaultTrue) {
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
873
|
+
if (passWhenDefaultFalse && passWhenDefaultTrue) {
|
|
874
|
+
return MixinDefaultGroup.None;
|
|
875
|
+
}
|
|
876
|
+
return passWhenDefaultTrue
|
|
877
|
+
? MixinDefaultGroup.True
|
|
878
|
+
: MixinDefaultGroup.False;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Resolve which default() candidate groups should win for the current call.
|
|
883
|
+
*/
|
|
884
|
+
export function resolveWinningMixinDefaultGroups(
|
|
885
|
+
groups: readonly MixinDefaultGroup[]
|
|
886
|
+
): Set<MixinDefaultGroup> {
|
|
887
|
+
let hasDefNoneCandidate = false;
|
|
888
|
+
let hasDefTrueCandidate = false;
|
|
889
|
+
let hasDefFalseCandidate = false;
|
|
890
|
+
|
|
891
|
+
for (const group of groups) {
|
|
892
|
+
if (group === MixinDefaultGroup.True) {
|
|
893
|
+
hasDefTrueCandidate = true;
|
|
894
|
+
} else if (group === MixinDefaultGroup.False) {
|
|
895
|
+
hasDefFalseCandidate = true;
|
|
896
|
+
} else if (group === MixinDefaultGroup.None) {
|
|
897
|
+
hasDefNoneCandidate = true;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (!hasDefNoneCandidate && hasDefTrueCandidate && hasDefFalseCandidate) {
|
|
902
|
+
throw new ReferenceError('Ambiguous use of default() while matching mixins.');
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (hasDefNoneCandidate) {
|
|
906
|
+
return new Set([MixinDefaultGroup.None, MixinDefaultGroup.False]);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return new Set([MixinDefaultGroup.True]);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Replay only the winning pending default() candidates with the correct lookup
|
|
914
|
+
* scope active for each candidate.
|
|
915
|
+
*/
|
|
916
|
+
export async function replayWinningMixinDefaultCandidates<TCandidate>(
|
|
917
|
+
pendingCandidates: readonly PendingMixinDefaultCandidate<TCandidate>[],
|
|
918
|
+
context: Context,
|
|
919
|
+
evaluateCandidateOutput: (
|
|
920
|
+
pending: PendingMixinDefaultCandidate<TCandidate>
|
|
921
|
+
) => MaybePromise<void>
|
|
922
|
+
): Promise<void> {
|
|
923
|
+
if (pendingCandidates.length === 0) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const winningGroups = resolveWinningMixinDefaultGroups(
|
|
928
|
+
pendingCandidates.map(pending => pending.group)
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
for (const pending of pendingCandidates) {
|
|
932
|
+
if (!winningGroups.has(pending.group)) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
await withMixinLookupScope(
|
|
936
|
+
pending.outerRules ?? pending.rules,
|
|
937
|
+
pending.outerRules ?? pending.lookupScope ?? findRulesAncestor(pending.rules, context),
|
|
938
|
+
context,
|
|
939
|
+
() => evaluateCandidateOutput(pending)
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Assemble the final mixin output `Rules` from already-evaluated candidate
|
|
946
|
+
* results, preserving source order and mixin-output visibility semantics.
|
|
947
|
+
*/
|
|
948
|
+
export function assembleMixinInvocationOutput(
|
|
949
|
+
outputRules: Rules[],
|
|
950
|
+
restrictMixinOutputLookup: boolean,
|
|
951
|
+
context: Context
|
|
952
|
+
): Rules {
|
|
953
|
+
outputRules.sort(comparePosition);
|
|
954
|
+
|
|
955
|
+
if (outputRules.length === 0) {
|
|
956
|
+
const output = Rules.create([], {
|
|
957
|
+
rulesVisibility: {
|
|
958
|
+
Ruleset: 'public',
|
|
959
|
+
Declaration: 'public',
|
|
960
|
+
VarDeclaration: 'public',
|
|
961
|
+
Mixin: 'public'
|
|
962
|
+
},
|
|
963
|
+
isMixinOutput: restrictMixinOutputLookup
|
|
964
|
+
});
|
|
965
|
+
output.renderKey = context.renderKey ?? output.renderKey;
|
|
966
|
+
return output;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (outputRules.length === 1) {
|
|
970
|
+
const output = outputRules[0]!;
|
|
971
|
+
output.options.isMixinOutput ??= restrictMixinOutputLookup;
|
|
972
|
+
return output;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
for (let i = 0; i < outputRules.length; i++) {
|
|
976
|
+
const candidateOutput = outputRules[i]!;
|
|
977
|
+
candidateOutput.frozen = true;
|
|
978
|
+
candidateOutput.index = i;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const firstOutput = outputRules[0]!;
|
|
982
|
+
const nextRenderKey = context.renderKey ?? EVAL;
|
|
983
|
+
const output = new Rules(
|
|
984
|
+
[],
|
|
985
|
+
{
|
|
986
|
+
rulesVisibility: {
|
|
987
|
+
Ruleset: 'public',
|
|
988
|
+
Declaration: 'public',
|
|
989
|
+
VarDeclaration: 'public',
|
|
990
|
+
Mixin: 'public'
|
|
991
|
+
},
|
|
992
|
+
isMixinOutput: restrictMixinOutputLookup
|
|
993
|
+
},
|
|
994
|
+
firstOutput.location,
|
|
995
|
+
firstOutput.treeContext
|
|
996
|
+
);
|
|
997
|
+
output.renderKey = nextRenderKey;
|
|
998
|
+
output.sourceParent = firstOutput.sourceParent;
|
|
999
|
+
output._setValueArray([...outputRules]);
|
|
1000
|
+
const outputContext = {
|
|
1001
|
+
...context,
|
|
1002
|
+
renderKey: output.renderKey,
|
|
1003
|
+
rulesContext: output
|
|
1004
|
+
} as Context;
|
|
1005
|
+
for (const child of outputRules) {
|
|
1006
|
+
if (isNode(child, N.Rules)) {
|
|
1007
|
+
const childContext = {
|
|
1008
|
+
...outputContext,
|
|
1009
|
+
renderKey: (child as Rules).renderKey,
|
|
1010
|
+
rulesContext: child as Rules
|
|
1011
|
+
} as Context;
|
|
1012
|
+
setParent(child, output, childContext);
|
|
1013
|
+
output.registerNode(child, undefined, outputContext);
|
|
1014
|
+
projectCurrentRenderParents(
|
|
1015
|
+
child as Rules,
|
|
1016
|
+
childContext
|
|
1017
|
+
);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
setParent(child, output, outputContext);
|
|
1021
|
+
output.registerNode(child, undefined, outputContext);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return output;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Evaluate a ruleset candidate whose guard already passed during Ruleset
|
|
1029
|
+
* evaluation, preserving mixin-output semantics and eval-state-subtree association.
|
|
1030
|
+
*/
|
|
1031
|
+
export async function evaluateRulesetMixinCandidateOutput(
|
|
1032
|
+
sourceRules: Rules,
|
|
1033
|
+
definitionParent: Node | undefined,
|
|
1034
|
+
placementParent: Node | undefined,
|
|
1035
|
+
candidateIndex: number,
|
|
1036
|
+
restrictMixinOutputLookup: boolean,
|
|
1037
|
+
context: Context
|
|
1038
|
+
): Promise<Rules> {
|
|
1039
|
+
const renderKey = context.nextRenderKey();
|
|
1040
|
+
let rules = sourceRules.createShallowBodyWrapper(undefined, renderKey);
|
|
1041
|
+
const placementContext = {
|
|
1042
|
+
...context,
|
|
1043
|
+
renderKey,
|
|
1044
|
+
rulesContext: rules
|
|
1045
|
+
} as Context;
|
|
1046
|
+
setParent(rules, placementParent, placementContext);
|
|
1047
|
+
setSourceParent(rules, definitionParent, placementContext);
|
|
1048
|
+
const previousRulesContext = context.rulesContext;
|
|
1049
|
+
const previousRenderKey = context.renderKey;
|
|
1050
|
+
const previousFrames = context.frames;
|
|
1051
|
+
const previousRulesetFrames = context.rulesetFrames;
|
|
1052
|
+
const seededFrames: Array<Ruleset | AtRule> = [];
|
|
1053
|
+
let frameCursor = placementParent;
|
|
1054
|
+
while (frameCursor) {
|
|
1055
|
+
if (isNode(frameCursor, N.Ruleset | N.AtRule)) {
|
|
1056
|
+
seededFrames.push(frameCursor as Ruleset | AtRule);
|
|
1057
|
+
}
|
|
1058
|
+
frameCursor = getParent(frameCursor, context);
|
|
1059
|
+
}
|
|
1060
|
+
seededFrames.reverse();
|
|
1061
|
+
context.rulesContext = rules;
|
|
1062
|
+
context.renderKey = rules.renderKey;
|
|
1063
|
+
context.frames = seededFrames;
|
|
1064
|
+
context.rulesetFrames = seededFrames.filter((frame): frame is Ruleset => isNode(frame, N.Ruleset));
|
|
1065
|
+
try {
|
|
1066
|
+
rules = await rules.preEval(context) as Rules;
|
|
1067
|
+
rules = await rules.eval(context);
|
|
1068
|
+
} finally {
|
|
1069
|
+
context.rulesContext = previousRulesContext;
|
|
1070
|
+
context.renderKey = previousRenderKey;
|
|
1071
|
+
context.frames = previousFrames;
|
|
1072
|
+
context.rulesetFrames = previousRulesetFrames;
|
|
1073
|
+
}
|
|
1074
|
+
setSourceParent(rules, definitionParent, placementContext);
|
|
1075
|
+
setParent(rules, placementParent, placementContext);
|
|
1076
|
+
finalizeInvocationOutputRules(rules, context);
|
|
1077
|
+
rules.index = candidateIndex;
|
|
1078
|
+
rules.options.isMixinOutput = restrictMixinOutputLookup;
|
|
1079
|
+
return rules;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export function finalizeInvocationOutputRules(
|
|
1083
|
+
rules: Rules,
|
|
1084
|
+
context: Context
|
|
1085
|
+
): void {
|
|
1086
|
+
const scopedContext = {
|
|
1087
|
+
...context,
|
|
1088
|
+
renderKey: rules.renderKey,
|
|
1089
|
+
rulesContext: rules
|
|
1090
|
+
} as Context;
|
|
1091
|
+
|
|
1092
|
+
const children = rules.get('value', scopedContext);
|
|
1093
|
+
for (let index = 0; index < children.length; index++) {
|
|
1094
|
+
let child = children[index]!;
|
|
1095
|
+
if (isNode(child, N.Rules)) {
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (isNode(child, N.Ruleset | N.AtRule) && getCurrentParentNode(child, scopedContext) !== rules) {
|
|
1099
|
+
child = child.clone(false, undefined, scopedContext);
|
|
1100
|
+
rules._setChildAt(index, child, scopedContext, false);
|
|
1101
|
+
rules.adopt(child, scopedContext);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (isNode(child, N.Ruleset)) {
|
|
1105
|
+
const childRuleset = child as Ruleset;
|
|
1106
|
+
if (
|
|
1107
|
+
childRuleset.getOwnSelector()
|
|
1108
|
+
&& childRuleset.getExtendedSelector(rules.renderKey)
|
|
1109
|
+
&& !childRuleset.getSelectorBeforeExtend(rules.renderKey)
|
|
1110
|
+
) {
|
|
1111
|
+
childRuleset.setExtendedSelector(
|
|
1112
|
+
childRuleset.getSelector(rules.renderKey),
|
|
1113
|
+
scopedContext
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
finalizeInvocationOutputRules(
|
|
1117
|
+
childRuleset.enterRules(scopedContext),
|
|
1118
|
+
scopedContext
|
|
1119
|
+
);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (isNode(child, N.AtRule)) {
|
|
1124
|
+
const childRules = (child as AtRule).enterRules(scopedContext);
|
|
1125
|
+
if (childRules) {
|
|
1126
|
+
finalizeInvocationOutputRules(childRules, scopedContext);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function projectCurrentRenderParents(
|
|
1133
|
+
rules: Rules,
|
|
1134
|
+
context: Context
|
|
1135
|
+
): void {
|
|
1136
|
+
const scopedContext = {
|
|
1137
|
+
...context,
|
|
1138
|
+
renderKey: rules.renderKey,
|
|
1139
|
+
rulesContext: rules
|
|
1140
|
+
} as Context;
|
|
1141
|
+
const children = rules.get('value', scopedContext);
|
|
1142
|
+
for (const child of children) {
|
|
1143
|
+
setParent(child, rules, scopedContext);
|
|
1144
|
+
if (isNode(child, N.Rules)) {
|
|
1145
|
+
if ((child as Rules).renderKey !== rules.renderKey) {
|
|
1146
|
+
(child as Rules).renderKey = rules.renderKey;
|
|
1147
|
+
}
|
|
1148
|
+
projectCurrentRenderParents(child as Rules, {
|
|
1149
|
+
...scopedContext,
|
|
1150
|
+
renderKey: (child as Rules).renderKey,
|
|
1151
|
+
rulesContext: child as Rules
|
|
1152
|
+
} as Context);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (isNode(child, N.Ruleset | N.AtRule)) {
|
|
1156
|
+
const nestedRules = child.enterRules(scopedContext);
|
|
1157
|
+
if (nestedRules) {
|
|
1158
|
+
projectCurrentRenderParents(nestedRules, {
|
|
1159
|
+
...scopedContext,
|
|
1160
|
+
renderKey: nestedRules.renderKey,
|
|
1161
|
+
rulesContext: nestedRules
|
|
1162
|
+
} as Context);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Create the unlocked output for a detached ruleset call without flattening
|
|
1170
|
+
* the source body eagerly.
|
|
1171
|
+
*/
|
|
1172
|
+
export function unlockDetachedRulesetMixinCandidateOutput(
|
|
1173
|
+
sourceRules: Rules,
|
|
1174
|
+
placementParent: Node | undefined,
|
|
1175
|
+
definitionParent: Node | undefined,
|
|
1176
|
+
candidateIndex: number,
|
|
1177
|
+
context: Context
|
|
1178
|
+
): Rules {
|
|
1179
|
+
const unlocked = sourceRules.cloneDetachedUnlockWrapper(context);
|
|
1180
|
+
const placementContext = {
|
|
1181
|
+
...context,
|
|
1182
|
+
renderKey: unlocked.renderKey,
|
|
1183
|
+
rulesContext: unlocked
|
|
1184
|
+
} as Context;
|
|
1185
|
+
setParent(unlocked, placementParent, placementContext);
|
|
1186
|
+
setSourceParent(unlocked, definitionParent, placementContext);
|
|
1187
|
+
finalizeInvocationOutputRules(unlocked, placementContext);
|
|
1188
|
+
unlocked.options.isMixinOutput = false;
|
|
1189
|
+
unlocked.index = candidateIndex;
|
|
1190
|
+
return unlocked;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Process one prepared normal mixin candidate. This owns the remaining guard /
|
|
1195
|
+
* default orchestration for the standard candidate path: either dispatch the
|
|
1196
|
+
* candidate output immediately or return a pending default() replay record.
|
|
1197
|
+
*/
|
|
1198
|
+
export async function processPreparedMixinCandidate<TCandidate>(
|
|
1199
|
+
options: ProcessPreparedMixinCandidateOptions<TCandidate>
|
|
1200
|
+
): Promise<PendingMixinDefaultCandidate<TCandidate> | undefined> {
|
|
1201
|
+
const {
|
|
1202
|
+
candidate,
|
|
1203
|
+
rules,
|
|
1204
|
+
params,
|
|
1205
|
+
outerRules,
|
|
1206
|
+
guard,
|
|
1207
|
+
parent,
|
|
1208
|
+
guardScopeChildren,
|
|
1209
|
+
hasAnyDefault,
|
|
1210
|
+
candidateHasDefault,
|
|
1211
|
+
context,
|
|
1212
|
+
evaluateCandidateOutput
|
|
1213
|
+
} = options;
|
|
1214
|
+
|
|
1215
|
+
let nextOuterRules = outerRules;
|
|
1216
|
+
const stableGuardScopeChildren = guard
|
|
1217
|
+
? (guardScopeChildren ?? (nextOuterRules ? [...nextOuterRules.value] : undefined))
|
|
1218
|
+
: guardScopeChildren;
|
|
1219
|
+
const getCapturedOuterRules = () => captureMixinScopeSnapshot(
|
|
1220
|
+
nextOuterRules,
|
|
1221
|
+
stableGuardScopeChildren,
|
|
1222
|
+
context
|
|
1223
|
+
);
|
|
1224
|
+
const pendingLookupScope = () => getCapturedOuterRules() ?? getMixinCandidateLookupScope(parent, rules, context);
|
|
1225
|
+
if (guard) {
|
|
1226
|
+
const evaluatedGuard = await evaluateMixinGuardCandidate(
|
|
1227
|
+
guard,
|
|
1228
|
+
nextOuterRules,
|
|
1229
|
+
parent,
|
|
1230
|
+
context,
|
|
1231
|
+
stableGuardScopeChildren,
|
|
1232
|
+
candidateHasDefault
|
|
1233
|
+
);
|
|
1234
|
+
nextOuterRules = evaluatedGuard.outerRules;
|
|
1235
|
+
if (!evaluatedGuard.passes) {
|
|
1236
|
+
return undefined;
|
|
1237
|
+
}
|
|
1238
|
+
if (hasAnyDefault) {
|
|
1239
|
+
const capturedOuterRules = getCapturedOuterRules();
|
|
1240
|
+
const lookupScope = capturedOuterRules ?? getMixinCandidateLookupScope(parent, rules, context);
|
|
1241
|
+
return {
|
|
1242
|
+
candidate,
|
|
1243
|
+
rules,
|
|
1244
|
+
outerRules: capturedOuterRules,
|
|
1245
|
+
params,
|
|
1246
|
+
group: candidateHasDefault ? evaluatedGuard.defaultGroup! : MixinDefaultGroup.None,
|
|
1247
|
+
lookupScope
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (hasAnyDefault) {
|
|
1253
|
+
const capturedOuterRules = getCapturedOuterRules();
|
|
1254
|
+
const lookupScope = capturedOuterRules ?? getMixinCandidateLookupScope(parent, rules, context);
|
|
1255
|
+
return {
|
|
1256
|
+
candidate,
|
|
1257
|
+
rules,
|
|
1258
|
+
outerRules: capturedOuterRules,
|
|
1259
|
+
params,
|
|
1260
|
+
group: MixinDefaultGroup.None,
|
|
1261
|
+
lookupScope
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
await withMixinLookupScope(
|
|
1266
|
+
nextOuterRules ?? rules,
|
|
1267
|
+
pendingLookupScope(),
|
|
1268
|
+
context,
|
|
1269
|
+
() => evaluateCandidateOutput(candidate, rules, nextOuterRules, params)
|
|
1270
|
+
);
|
|
1271
|
+
return undefined;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// -- Scope ancestry helpers --
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Walk the parent chain (via eval state helpers) to find the nearest Rules ancestor.
|
|
1278
|
+
*/
|
|
1279
|
+
export function findRulesAncestor(node: Node | undefined, context: Context): Rules | undefined {
|
|
1280
|
+
let current = node ? getParent(node, context) : undefined;
|
|
1281
|
+
while (current && current.type !== 'Rules') {
|
|
1282
|
+
current = getParent(current, context);
|
|
1283
|
+
}
|
|
1284
|
+
return current as Rules | undefined;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Walk sourceParent chain then parent chain to find the nearest source Rules ancestor.
|
|
1289
|
+
*/
|
|
1290
|
+
export function findSourceRulesAncestor(node: Node | undefined, context: Context): Rules | undefined {
|
|
1291
|
+
let current = node;
|
|
1292
|
+
let sp = current ? getSourceParent(current, context) : undefined;
|
|
1293
|
+
while (current && !sp) {
|
|
1294
|
+
current = getParent(current, context);
|
|
1295
|
+
sp = current ? getSourceParent(current, context) : undefined;
|
|
1296
|
+
}
|
|
1297
|
+
return sp ? findRulesAncestor(sp, context) : undefined;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function getMixinCandidateLookupScope(
|
|
1301
|
+
parent: Node | undefined,
|
|
1302
|
+
rules: Rules,
|
|
1303
|
+
context: Context
|
|
1304
|
+
): Rules | undefined {
|
|
1305
|
+
if (isNode(parent, N.Rules)) {
|
|
1306
|
+
return parent as Rules;
|
|
1307
|
+
}
|
|
1308
|
+
return findRulesAncestor(parent, context) ?? findRulesAncestor(rules, context);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Resolve the candidate's parent, throwing if absent.
|
|
1313
|
+
*
|
|
1314
|
+
* Accepts `Node<any, any>` so callers don't need `as unknown as Node` casts
|
|
1315
|
+
* for typed subclasses like Mixin or Ruleset.
|
|
1316
|
+
*/
|
|
1317
|
+
export function getCandidateParent(node: Node<any, any>, context: Context): Node {
|
|
1318
|
+
const parent = getParent(node as Node, context);
|
|
1319
|
+
if (!parent) {
|
|
1320
|
+
throw new ReferenceError(`${node.type} candidate must have a parent during mixin evaluation`);
|
|
1321
|
+
}
|
|
1322
|
+
return parent;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// -- Arg evaluation --
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Evaluate raw call args into a flat array of current Node values.
|
|
1329
|
+
*/
|
|
1330
|
+
export async function evaluateMixinArgs(
|
|
1331
|
+
args: any[],
|
|
1332
|
+
caller: Node | undefined,
|
|
1333
|
+
context: Context
|
|
1334
|
+
): Promise<Node[]> {
|
|
1335
|
+
const nodeArgs: Node[] = [];
|
|
1336
|
+
const callerSourceNode = (caller as any)?.name instanceof Node
|
|
1337
|
+
? (caller as any).name
|
|
1338
|
+
: caller;
|
|
1339
|
+
const callerSourceParent = callerSourceNode
|
|
1340
|
+
? getSourceParent(callerSourceNode, context)
|
|
1341
|
+
: undefined;
|
|
1342
|
+
const savedRulesContext = context.rulesContext;
|
|
1343
|
+
const savedLookupScope = context.lookupScope;
|
|
1344
|
+
const savedRenderKey = context.renderKey;
|
|
1345
|
+
const argEvalRulesContext = context.lookupScope
|
|
1346
|
+
?? context.rulesContext
|
|
1347
|
+
?? findRulesAncestor(caller, context)
|
|
1348
|
+
?? findSourceRulesAncestor(callerSourceNode, context)
|
|
1349
|
+
?? findRulesAncestor(callerSourceParent, context);
|
|
1350
|
+
context.rulesContext = argEvalRulesContext;
|
|
1351
|
+
context.lookupScope = argEvalRulesContext;
|
|
1352
|
+
context.renderKey = argEvalRulesContext?.renderKey ?? savedRenderKey;
|
|
1353
|
+
try {
|
|
1354
|
+
for (const arg of args) {
|
|
1355
|
+
if (isNode(arg)) {
|
|
1356
|
+
if (isNode(arg, N.VarDeclaration)) {
|
|
1357
|
+
// Evaluate the value, keep the VarDeclaration structure
|
|
1358
|
+
const value = (arg as VarDeclaration).get('value');
|
|
1359
|
+
if (value instanceof Node) {
|
|
1360
|
+
if (callerSourceParent && (isNode(value, N.Reference) || isNode(value, N.Mixin))) {
|
|
1361
|
+
setSourceParent(value, callerSourceParent, context);
|
|
1362
|
+
}
|
|
1363
|
+
const evaldValue = await value.eval(context);
|
|
1364
|
+
const argName = String((arg as VarDeclaration).get('name').valueOf());
|
|
1365
|
+
const bound = new VarDeclarationCtor({
|
|
1366
|
+
name: new Any(argName, { role: 'property' }),
|
|
1367
|
+
value: new Nil()
|
|
1368
|
+
}, { ...((arg as VarDeclaration).options ?? {}) }, arg.location, context.treeContext);
|
|
1369
|
+
bound.renderKey = context.renderKey ?? bound.renderKey;
|
|
1370
|
+
(bound as VarDeclaration).value = evaldValue;
|
|
1371
|
+
bound.adopt(evaldValue, context);
|
|
1372
|
+
nodeArgs.push(bound);
|
|
1373
|
+
} else {
|
|
1374
|
+
nodeArgs.push(arg);
|
|
1375
|
+
}
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
if (callerSourceParent && (isNode(arg, N.Reference) || isNode(arg, N.Mixin))) {
|
|
1379
|
+
setSourceParent(arg, callerSourceParent, context);
|
|
1380
|
+
}
|
|
1381
|
+
const evald = await arg.eval(context);
|
|
1382
|
+
if (evald.type === 'Rest') {
|
|
1383
|
+
let restValue = (evald as unknown as { value: unknown }).value;
|
|
1384
|
+
if (isNode(restValue as Node) && !isNode(restValue as Node, N.Sequence | N.List)) {
|
|
1385
|
+
restValue = await (restValue as Node).eval(context);
|
|
1386
|
+
}
|
|
1387
|
+
if (isNode(restValue, N.Sequence) || isNode(restValue, N.List)) {
|
|
1388
|
+
for (const restArg of (restValue as unknown as { value: Node[] }).value) {
|
|
1389
|
+
nodeArgs.push(restArg);
|
|
1390
|
+
}
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
nodeArgs.push(evald);
|
|
1395
|
+
} else {
|
|
1396
|
+
nodeArgs.push(cast(arg));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
} finally {
|
|
1400
|
+
context.rulesContext = savedRulesContext;
|
|
1401
|
+
context.lookupScope = savedLookupScope;
|
|
1402
|
+
context.renderKey = savedRenderKey;
|
|
1403
|
+
}
|
|
1404
|
+
return nodeArgs;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// -- Candidate matching --
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Evaluate a pattern-match operand in a fresh session scope.
|
|
1411
|
+
*/
|
|
1412
|
+
async function preparePatternOperand(node: Node, context: Context): Promise<Node> {
|
|
1413
|
+
try {
|
|
1414
|
+
return await node.eval(context);
|
|
1415
|
+
} finally {
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Match the mixin array against evaluated args. Returns the candidates whose
|
|
1421
|
+
* param signatures match (with params bound).
|
|
1422
|
+
*/
|
|
1423
|
+
export async function matchMixinCandidates(
|
|
1424
|
+
mixinArr: MixinEntry[],
|
|
1425
|
+
nodeArgs: Node[],
|
|
1426
|
+
caller: Node | undefined,
|
|
1427
|
+
sourceParent: Node | undefined,
|
|
1428
|
+
context: Context
|
|
1429
|
+
): Promise<MixinEntry[]> {
|
|
1430
|
+
if (process.env.JESS_DEBUG_LOCK === 'throw-match') {
|
|
1431
|
+
const callerName = caller && isNode(caller, N.Call)
|
|
1432
|
+
? (caller as unknown as { name?: Node }).name
|
|
1433
|
+
: undefined;
|
|
1434
|
+
const callerKey = isNode(callerName, N.Reference)
|
|
1435
|
+
? String(callerName.key?.valueOf?.() ?? '')
|
|
1436
|
+
: '';
|
|
1437
|
+
const candidateNames = mixinArr.map(mixin => {
|
|
1438
|
+
if (isNode(mixin, N.Mixin)) {
|
|
1439
|
+
return String(mixin.get('name')?.valueOf?.() ?? '');
|
|
1440
|
+
}
|
|
1441
|
+
if (isNode(mixin, N.Ruleset)) {
|
|
1442
|
+
return String(mixin.get('selector')?.valueOf?.() ?? '');
|
|
1443
|
+
}
|
|
1444
|
+
return mixin.type;
|
|
1445
|
+
});
|
|
1446
|
+
if (
|
|
1447
|
+
callerKey.includes('inner-locked-mixin')
|
|
1448
|
+
|| candidateNames.some(name => name.includes('inner-locked-mixin'))
|
|
1449
|
+
) {
|
|
1450
|
+
throw new Error(`[lock-match] ${JSON.stringify({
|
|
1451
|
+
callerKey,
|
|
1452
|
+
candidateNames,
|
|
1453
|
+
sourceParent: sourceParent?.type,
|
|
1454
|
+
rulesContext: context.rulesContext?.type,
|
|
1455
|
+
lookupScope: context.lookupScope?.type,
|
|
1456
|
+
argTypes: nodeArgs.map(arg => arg.type)
|
|
1457
|
+
})}`);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const mixinCandidates: MixinEntry[] = [];
|
|
1461
|
+
const bindingSourceParent = sourceParent ?? caller;
|
|
1462
|
+
|
|
1463
|
+
for (let i = 0; i < mixinArr.length; i++) {
|
|
1464
|
+
let mixin = mixinArr[i]!;
|
|
1465
|
+
const isPlainRule = isNode(mixin, N.Rules);
|
|
1466
|
+
const currentParams = !isPlainRule && isNode(mixin, N.Mixin)
|
|
1467
|
+
? getCurrentMixinParams(mixin as Mixin, mixin.renderKey ?? context)
|
|
1468
|
+
: undefined;
|
|
1469
|
+
const paramLength = isPlainRule ? 0 : (currentParams?.length ?? 0);
|
|
1470
|
+
|
|
1471
|
+
if (!paramLength) {
|
|
1472
|
+
if (nodeArgs.length) {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
mixinCandidates.push(mixin);
|
|
1476
|
+
} else {
|
|
1477
|
+
const params = currentParams!;
|
|
1478
|
+
const hasRestParamOriginal = params.get('value').some(
|
|
1479
|
+
(p: Node) => p.type === 'Rest'
|
|
1480
|
+
);
|
|
1481
|
+
const maxPositionalArgs = hasRestParamOriginal ? Number.POSITIVE_INFINITY : params.length;
|
|
1482
|
+
const positions = params.length;
|
|
1483
|
+
let requiredPositions = 0;
|
|
1484
|
+
for (const param of params.get('value')) {
|
|
1485
|
+
if (isNode(param, N.VarDeclaration)) {
|
|
1486
|
+
if ((param as VarDeclaration).get('value') instanceof Nil) {
|
|
1487
|
+
requiredPositions++;
|
|
1488
|
+
}
|
|
1489
|
+
} else if (isNode(param, N.Any) && param.role === 'property') {
|
|
1490
|
+
requiredPositions++;
|
|
1491
|
+
} else if (param.type !== 'Rest') {
|
|
1492
|
+
requiredPositions++;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
let argPos = 0;
|
|
1497
|
+
let match = true;
|
|
1498
|
+
for (let pi = 0; pi < positions; pi++) {
|
|
1499
|
+
const arg = nodeArgs[argPos];
|
|
1500
|
+
if (!arg) {
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
let param: Node | undefined;
|
|
1504
|
+
let argValue: Node;
|
|
1505
|
+
|
|
1506
|
+
if (isNode(arg, N.VarDeclaration)) {
|
|
1507
|
+
param = params.get('value').find((p: Node) => {
|
|
1508
|
+
if (isNode(p, N.VarDeclaration)) {
|
|
1509
|
+
return (p as VarDeclaration).get('name').valueOf() === (arg as VarDeclaration).get('name').valueOf();
|
|
1510
|
+
}
|
|
1511
|
+
if (isNode(p, N.Any) && p.role === 'property') {
|
|
1512
|
+
return p.valueOf() === (arg as VarDeclaration).get('name').valueOf();
|
|
1513
|
+
}
|
|
1514
|
+
return false;
|
|
1515
|
+
});
|
|
1516
|
+
if (param) {
|
|
1517
|
+
argValue = (arg as VarDeclaration).get('value') as Node;
|
|
1518
|
+
} else {
|
|
1519
|
+
match = false;
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
} else {
|
|
1523
|
+
param = params.get('value')[pi];
|
|
1524
|
+
if (!param) {
|
|
1525
|
+
match = false;
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
argValue = arg;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
if (!param) {
|
|
1532
|
+
match = false;
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (
|
|
1537
|
+
isNode(param, N.VarDeclaration)
|
|
1538
|
+
|| (isNode(param, N.Any) && param.role === 'property')
|
|
1539
|
+
|| param.type === 'Rest'
|
|
1540
|
+
) {
|
|
1541
|
+
if (bindingSourceParent && (isNode(argValue, N.Reference) || isNode(argValue, N.Mixin))) {
|
|
1542
|
+
setSourceParent(argValue, bindingSourceParent, context);
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
const originalPatternParam = !isNode(arg, N.VarDeclaration)
|
|
1546
|
+
? currentParams?.get('value')[pi]
|
|
1547
|
+
: undefined;
|
|
1548
|
+
const preparedParam = isNode(originalPatternParam as Node | undefined, N.Selector)
|
|
1549
|
+
? await preparePatternOperand(originalPatternParam as Node, context)
|
|
1550
|
+
: isNode(param, N.Selector)
|
|
1551
|
+
? await preparePatternOperand(param, context)
|
|
1552
|
+
: param;
|
|
1553
|
+
if (preparedParam.compare(argValue, context) !== 0) {
|
|
1554
|
+
match = false;
|
|
1555
|
+
break;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
argPos++;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
const positionalArgCount = nodeArgs.filter(argNode => !isNode(argNode, N.VarDeclaration)).length;
|
|
1562
|
+
if (positionalArgCount > maxPositionalArgs) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
if (argPos < requiredPositions) {
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
if (nodeArgs.length > 1 && params.get('value').length === 1 && requiredPositions === 1) {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
if (match) {
|
|
1572
|
+
mixinCandidates.push(mixin);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return mixinCandidates;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// -- Candidate filtering and sorting --
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Walk a node tree looking for `default()` / `DefaultGuard` / `??` calls.
|
|
1584
|
+
*/
|
|
1585
|
+
function guardContainsDefault(node: Node | undefined): boolean {
|
|
1586
|
+
if (!node) {
|
|
1587
|
+
return false;
|
|
1588
|
+
}
|
|
1589
|
+
if (node.type === 'DefaultGuard') {
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
if (node.type === 'Call') {
|
|
1593
|
+
const callName = String((node as unknown as { name?: { valueOf?: () => string } }).name?.valueOf?.()
|
|
1594
|
+
?? (node as unknown as { name?: string }).name ?? '');
|
|
1595
|
+
if (callName === 'default' || callName === '??') {
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const value = (node as unknown as { value: unknown }).value;
|
|
1600
|
+
if (Array.isArray(value)) {
|
|
1601
|
+
for (const item of value) {
|
|
1602
|
+
if (isNode(item) && guardContainsDefault(item)) {
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
if (isPlainObject(value)) {
|
|
1609
|
+
for (const item of Object.values(value as Record<string, unknown>)) {
|
|
1610
|
+
if (isNode(item as Node) && guardContainsDefault(item as Node)) {
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
if (Array.isArray(item)) {
|
|
1614
|
+
for (const child of item) {
|
|
1615
|
+
if (isNode(child) && guardContainsDefault(child)) {
|
|
1616
|
+
return true;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/**
|
|
1626
|
+
* Check whether a node has a failed-guard ancestor (Ruleset with Nil guard).
|
|
1627
|
+
*/
|
|
1628
|
+
function hasFailedGuardAncestor(node: Node<any, any>, context: Context): boolean {
|
|
1629
|
+
let current: Node | undefined = getParent(node as Node, context);
|
|
1630
|
+
while (current) {
|
|
1631
|
+
if (isNode(current, N.Ruleset)) {
|
|
1632
|
+
const guardNode = getCurrentRulesetGuard(current as Ruleset, context);
|
|
1633
|
+
if (guardNode instanceof Nil) {
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
current = getParent(current, context);
|
|
1638
|
+
}
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Filter matched candidates by eval-stack guard and sort default-guard
|
|
1644
|
+
* candidates to the end.
|
|
1645
|
+
*
|
|
1646
|
+
* Returns `{ evalCandidates, hasDefault }`.
|
|
1647
|
+
*/
|
|
1648
|
+
export function filterAndSortMixinEvalCandidates(
|
|
1649
|
+
mixinCandidates: MixinEntry[],
|
|
1650
|
+
context: Context
|
|
1651
|
+
): { evalCandidates: MixinEntry[]; hasDefault: boolean } {
|
|
1652
|
+
let hasDefault = false;
|
|
1653
|
+
let evalCandidates = mixinCandidates
|
|
1654
|
+
.filter((candidate) => {
|
|
1655
|
+
const blockedByFailedGuard = hasFailedGuardAncestor(candidate, context);
|
|
1656
|
+
return !blockedByFailedGuard;
|
|
1657
|
+
})
|
|
1658
|
+
.map<MixinEntry>((candidate) => {
|
|
1659
|
+
const hasDefaultGuard = Boolean(candidate.options?.hasDefault)
|
|
1660
|
+
|| guardContainsDefault((candidate as Mixin).get('guard') as Node | undefined);
|
|
1661
|
+
if (hasDefaultGuard) {
|
|
1662
|
+
candidate.options ??= {};
|
|
1663
|
+
candidate.options.hasDefault = true;
|
|
1664
|
+
hasDefault = true;
|
|
1665
|
+
}
|
|
1666
|
+
return candidate;
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
if (hasDefault) {
|
|
1670
|
+
evalCandidates = evalCandidates.slice(0).sort((a, b) => {
|
|
1671
|
+
const aDefault = a.options?.hasDefault;
|
|
1672
|
+
const bDefault = b.options?.hasDefault;
|
|
1673
|
+
if (!aDefault && !bDefault) {
|
|
1674
|
+
return 0;
|
|
1675
|
+
}
|
|
1676
|
+
if (!aDefault) {
|
|
1677
|
+
return -1;
|
|
1678
|
+
}
|
|
1679
|
+
if (!bDefault) {
|
|
1680
|
+
return 1;
|
|
1681
|
+
}
|
|
1682
|
+
return 0;
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
if (evalCandidates.length === 0) {
|
|
1687
|
+
throw new ReferenceError('No matching mixins found.');
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
return { evalCandidates, hasDefault };
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// -- Candidate output evaluation --
|
|
1694
|
+
|
|
1695
|
+
export type EvaluateCandidateOutputOptions = {
|
|
1696
|
+
sourceParent: Node | undefined;
|
|
1697
|
+
invocationParent: Node | undefined;
|
|
1698
|
+
restrictMixinOutputLookup: boolean;
|
|
1699
|
+
outputRules: Rules[];
|
|
1700
|
+
getCandidateParent: (node: Node<any, any>) => Node;
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Evaluate a single mixin candidate's body and push the result to outputRules.
|
|
1705
|
+
*
|
|
1706
|
+
* Handles explicit recursion guarding, position creation, body eval, output
|
|
1707
|
+
* finalization, param scope projection, and eval state subtree association.
|
|
1708
|
+
*/
|
|
1709
|
+
export async function evaluateCandidateOutput(
|
|
1710
|
+
candidate: MixinEntry,
|
|
1711
|
+
rules: Rules,
|
|
1712
|
+
outerRules: Rules | undefined,
|
|
1713
|
+
params: List<Node> | undefined,
|
|
1714
|
+
context: Context,
|
|
1715
|
+
opts: EvaluateCandidateOutputOptions
|
|
1716
|
+
): Promise<void> {
|
|
1717
|
+
const {
|
|
1718
|
+
sourceParent,
|
|
1719
|
+
invocationParent,
|
|
1720
|
+
restrictMixinOutputLookup,
|
|
1721
|
+
outputRules,
|
|
1722
|
+
getCandidateParent: getParentFn
|
|
1723
|
+
} = opts;
|
|
1724
|
+
const currentCall = context.callStack.at(-1);
|
|
1725
|
+
if (currentCall) {
|
|
1726
|
+
const isRecursive = context.callMap.add(currentCall, params, context);
|
|
1727
|
+
if (isRecursive) {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
try {
|
|
1732
|
+
const callerParent = getParentFn(candidate);
|
|
1733
|
+
const candidateSourceParent = getUsableInvocationSourceParent(
|
|
1734
|
+
getCanonicalSourceParent(candidate as Node),
|
|
1735
|
+
getUsableInvocationSourceParent(
|
|
1736
|
+
getCanonicalSourceParent(callerParent),
|
|
1737
|
+
sourceParent
|
|
1738
|
+
)
|
|
1739
|
+
);
|
|
1740
|
+
const lexicalSourceParent = candidateSourceParent ?? callerParent;
|
|
1741
|
+
const rulesContext = {
|
|
1742
|
+
...context,
|
|
1743
|
+
renderKey: rules.renderKey,
|
|
1744
|
+
rulesContext: rules
|
|
1745
|
+
} as Context;
|
|
1746
|
+
setParent(rules, outerRules ?? callerParent, rulesContext);
|
|
1747
|
+
rules.sourceParent = candidateSourceParent ?? lexicalSourceParent;
|
|
1748
|
+
const previousRenderKey = context.renderKey;
|
|
1749
|
+
context.renderKey = rules.renderKey;
|
|
1750
|
+
let newRules: Rules;
|
|
1751
|
+
try {
|
|
1752
|
+
newRules = await rules.eval(context);
|
|
1753
|
+
} finally {
|
|
1754
|
+
context.renderKey = previousRenderKey;
|
|
1755
|
+
}
|
|
1756
|
+
void outerRules;
|
|
1757
|
+
if (newRules.renderKey === CANONICAL) {
|
|
1758
|
+
newRules.renderKey = rules.renderKey;
|
|
1759
|
+
}
|
|
1760
|
+
const newRulesContext = {
|
|
1761
|
+
...context,
|
|
1762
|
+
renderKey: newRules.renderKey,
|
|
1763
|
+
rulesContext: newRules
|
|
1764
|
+
} as Context;
|
|
1765
|
+
setParent(newRules, invocationParent ?? callerParent, newRulesContext);
|
|
1766
|
+
projectParentChainForRenderKey(invocationParent ?? callerParent, context, newRulesContext);
|
|
1767
|
+
newRules.sourceParent = candidateSourceParent ?? lexicalSourceParent;
|
|
1768
|
+
newRules.index = candidate.index;
|
|
1769
|
+
finalizeInvocationOutputRules(newRules, newRulesContext);
|
|
1770
|
+
|
|
1771
|
+
if (outerRules) {
|
|
1772
|
+
const previousRenderKey = context.renderKey;
|
|
1773
|
+
const previousRulesContext = context.rulesContext;
|
|
1774
|
+
context.renderKey = newRules.renderKey;
|
|
1775
|
+
context.rulesContext = newRules;
|
|
1776
|
+
let outputChildren: readonly Node[];
|
|
1777
|
+
try {
|
|
1778
|
+
outputChildren = newRules.getRegistryChildren(context);
|
|
1779
|
+
} finally {
|
|
1780
|
+
context.renderKey = previousRenderKey;
|
|
1781
|
+
context.rulesContext = previousRulesContext;
|
|
1782
|
+
}
|
|
1783
|
+
const outputContext = {
|
|
1784
|
+
...context,
|
|
1785
|
+
renderKey: newRules.renderKey,
|
|
1786
|
+
rulesContext: newRules
|
|
1787
|
+
} as Context;
|
|
1788
|
+
for (const child of outputChildren) {
|
|
1789
|
+
if (isNode(child, N.Rules)) {
|
|
1790
|
+
projectCurrentRenderParents(
|
|
1791
|
+
child as Rules,
|
|
1792
|
+
{
|
|
1793
|
+
...outputContext,
|
|
1794
|
+
renderKey: (child as Rules).renderKey,
|
|
1795
|
+
rulesContext: child as Rules
|
|
1796
|
+
} as Context
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
bindStructuralSourceParent(newRules, outerRules, outputContext);
|
|
1801
|
+
setParent(newRules, outerRules, outputContext);
|
|
1802
|
+
if (invocationParent ?? callerParent) {
|
|
1803
|
+
addParentEdge(newRules, CALLER, (invocationParent ?? callerParent)!);
|
|
1804
|
+
}
|
|
1805
|
+
newRules.sourceParent = outerRules;
|
|
1806
|
+
newRules.index = candidate.index;
|
|
1807
|
+
newRules.options.isMixinOutput = false;
|
|
1808
|
+
if (process.env.JESS_DEBUG_LOCK === 'throw-output') {
|
|
1809
|
+
const candidateName = String((candidate as Mixin).get('name')?.valueOf?.() ?? '');
|
|
1810
|
+
if (candidateName.includes('lock-mixin')) {
|
|
1811
|
+
throw new Error(`[lock-output] ${JSON.stringify({
|
|
1812
|
+
candidateName,
|
|
1813
|
+
hasOuterRules: Boolean(outerRules),
|
|
1814
|
+
outerRulesChildren: outerRules?.value?.map((child: Node) => child.type) ?? [],
|
|
1815
|
+
candidateSourceParent: candidateSourceParent?.type,
|
|
1816
|
+
lexicalSourceParent: lexicalSourceParent?.type,
|
|
1817
|
+
outputContainerSourceParent: newRules.sourceParent?.type,
|
|
1818
|
+
outputContainerSourceParentChildren: newRules.sourceParent?.value?.map((child: Node) => child.type) ?? []
|
|
1819
|
+
})}`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
outputRules.push(newRules);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
newRules.options.isMixinOutput = restrictMixinOutputLookup;
|
|
1827
|
+
if (context.treeContext?.file) {
|
|
1828
|
+
newRules.options.rulesVisibility ??= {};
|
|
1829
|
+
newRules.options.rulesVisibility.VarDeclaration = 'private';
|
|
1830
|
+
}
|
|
1831
|
+
outputRules.push(newRules);
|
|
1832
|
+
} finally {
|
|
1833
|
+
if (currentCall) {
|
|
1834
|
+
context.callMap.delete(currentCall);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// -- Dispatch orchestration --
|
|
1840
|
+
|
|
1841
|
+
export type MixinDispatchContext = {
|
|
1842
|
+
evalCandidates: MixinEntry[];
|
|
1843
|
+
hasDefault: boolean;
|
|
1844
|
+
nodeArgs: Node[];
|
|
1845
|
+
sourceParent: Node | undefined;
|
|
1846
|
+
invocationParent: Node | undefined;
|
|
1847
|
+
caller: Node | undefined;
|
|
1848
|
+
restrictMixinOutputLookup: boolean;
|
|
1849
|
+
outputRules: Rules[];
|
|
1850
|
+
getCandidateParent: (node: Node<any, any>) => Node;
|
|
1851
|
+
evaluateCandidateOutput: (
|
|
1852
|
+
candidate: MixinEntry,
|
|
1853
|
+
rules: Rules,
|
|
1854
|
+
outerRules: Rules | undefined,
|
|
1855
|
+
params: List<Node> | undefined,
|
|
1856
|
+
) => Promise<void>;
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Dispatch all mixin eval candidates — the main candidate loop.
|
|
1861
|
+
*
|
|
1862
|
+
* Handles Ruleset candidates, detached rulesets, and normal parameterized
|
|
1863
|
+
* mixins. Includes default guard replay and output assembly.
|
|
1864
|
+
*/
|
|
1865
|
+
export async function dispatchMixinEvalCandidates(
|
|
1866
|
+
dispatch: MixinDispatchContext,
|
|
1867
|
+
context: Context
|
|
1868
|
+
): Promise<Rules> {
|
|
1869
|
+
const {
|
|
1870
|
+
evalCandidates,
|
|
1871
|
+
hasDefault,
|
|
1872
|
+
nodeArgs,
|
|
1873
|
+
sourceParent,
|
|
1874
|
+
invocationParent,
|
|
1875
|
+
restrictMixinOutputLookup,
|
|
1876
|
+
outputRules,
|
|
1877
|
+
getCandidateParent,
|
|
1878
|
+
evaluateCandidateOutput
|
|
1879
|
+
} = dispatch;
|
|
1880
|
+
const pendingDefaultCandidates: PendingMixinDefaultCandidate<any>[] = [];
|
|
1881
|
+
let skippedByRecursion = false;
|
|
1882
|
+
for (const candidate of evalCandidates) {
|
|
1883
|
+
if (isNode(candidate, N.Ruleset)) {
|
|
1884
|
+
if ((candidate as Ruleset).get('guard') instanceof Nil) {
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
const currentRules = (candidate as Ruleset).enterRules(context);
|
|
1888
|
+
const sourceRules = getRootSourceRules(currentRules);
|
|
1889
|
+
if (context.rulesEvalStack.includes(sourceRules)) {
|
|
1890
|
+
skippedByRecursion = true;
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
const definitionParent = getCandidateParent(candidate);
|
|
1894
|
+
const placementParent = invocationParent ?? definitionParent;
|
|
1895
|
+
const rules = await evaluateRulesetMixinCandidateOutput(
|
|
1896
|
+
sourceRules,
|
|
1897
|
+
definitionParent,
|
|
1898
|
+
placementParent,
|
|
1899
|
+
candidate.index,
|
|
1900
|
+
restrictMixinOutputLookup,
|
|
1901
|
+
context
|
|
1902
|
+
);
|
|
1903
|
+
outputRules.push(rules);
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// After the Ruleset branch above, candidate must be a Mixin
|
|
1908
|
+
if (!isNode(candidate, N.Mixin)) {
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
if (process.env.JESS_DEBUG_LOCK) {
|
|
1912
|
+
const candidateName = String(candidate.get('name')?.valueOf?.() ?? '');
|
|
1913
|
+
if (candidateName.includes('inner-locked-mixin')) {
|
|
1914
|
+
const candidateParent = getParent(candidate, context);
|
|
1915
|
+
const candidateSourceParent = getSourceParent(candidate, context);
|
|
1916
|
+
const lockInfo = {
|
|
1917
|
+
candidateName,
|
|
1918
|
+
candidateParent: candidateParent?.type,
|
|
1919
|
+
candidateParentIndex: candidateParent?.index,
|
|
1920
|
+
candidateSourceParent: candidateSourceParent?.type,
|
|
1921
|
+
candidateSourceParentIndex: candidateSourceParent?.index,
|
|
1922
|
+
candidateSourceParentName: isNode(candidateSourceParent, N.Mixin)
|
|
1923
|
+
? String(candidateSourceParent.get('name')?.valueOf?.() ?? '')
|
|
1924
|
+
: undefined,
|
|
1925
|
+
sourceParent: sourceParent?.type,
|
|
1926
|
+
invocationParent: invocationParent?.type,
|
|
1927
|
+
lookupScope: context.lookupScope?.type,
|
|
1928
|
+
rulesContext: context.rulesContext?.type
|
|
1929
|
+
};
|
|
1930
|
+
if (process.env.JESS_DEBUG_LOCK === 'throw') {
|
|
1931
|
+
throw new Error(`[lock-dispatch] ${JSON.stringify(lockInfo)}`);
|
|
1932
|
+
}
|
|
1933
|
+
console.log('[lock-dispatch]', lockInfo);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (!candidate.get('name') && !candidate.get('params') && !candidate.get('guard')) {
|
|
1937
|
+
const sourceRules = getRootSourceRules(candidate.get('rules'));
|
|
1938
|
+
const definitionParent = getCandidateParent(candidate);
|
|
1939
|
+
const unlocked = unlockDetachedRulesetMixinCandidateOutput(
|
|
1940
|
+
sourceRules,
|
|
1941
|
+
invocationParent ?? definitionParent,
|
|
1942
|
+
definitionParent,
|
|
1943
|
+
candidate.index,
|
|
1944
|
+
context
|
|
1945
|
+
);
|
|
1946
|
+
outputRules.push(unlocked);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
const candidateRenderKey = candidate.renderKey ?? context.renderKey;
|
|
1951
|
+
let rules = candidate
|
|
1952
|
+
.get('rules', candidateRenderKey ?? context)
|
|
1953
|
+
.withRenderOwner(candidate, candidateRenderKey, context);
|
|
1954
|
+
let params = getCurrentMixinParams(candidate, candidateRenderKey ?? context);
|
|
1955
|
+
const definitionParent = getCandidateParent(candidate);
|
|
1956
|
+
const candidateSourceParent = getUsableInvocationSourceParent(
|
|
1957
|
+
getCanonicalSourceParent(candidate as Node),
|
|
1958
|
+
getUsableInvocationSourceParent(
|
|
1959
|
+
getCanonicalSourceParent(definitionParent),
|
|
1960
|
+
sourceParent
|
|
1961
|
+
)
|
|
1962
|
+
);
|
|
1963
|
+
const prepared = prepareMixinCandidateInvocation(
|
|
1964
|
+
rules,
|
|
1965
|
+
params,
|
|
1966
|
+
definitionParent,
|
|
1967
|
+
candidateSourceParent,
|
|
1968
|
+
candidate.index,
|
|
1969
|
+
nodeArgs,
|
|
1970
|
+
context
|
|
1971
|
+
);
|
|
1972
|
+
rules = prepared.rules;
|
|
1973
|
+
params = prepared.params;
|
|
1974
|
+
const currentGuard = getCurrentRulesetGuard(candidate, candidateRenderKey ?? context) as Condition | Bool | undefined;
|
|
1975
|
+
const candidateHasDefault = Boolean(candidate.options?.hasDefault)
|
|
1976
|
+
|| guardContainsDefault(currentGuard as Node | undefined);
|
|
1977
|
+
const pendingDefaultCandidate = await processPreparedMixinCandidate({
|
|
1978
|
+
candidate,
|
|
1979
|
+
rules,
|
|
1980
|
+
params,
|
|
1981
|
+
outerRules: prepared.outerRules,
|
|
1982
|
+
guard: currentGuard,
|
|
1983
|
+
parent: getCandidateParent(candidate),
|
|
1984
|
+
hasAnyDefault: hasDefault,
|
|
1985
|
+
candidateHasDefault,
|
|
1986
|
+
context,
|
|
1987
|
+
evaluateCandidateOutput
|
|
1988
|
+
});
|
|
1989
|
+
if (pendingDefaultCandidate) {
|
|
1990
|
+
pendingDefaultCandidates.push(pendingDefaultCandidate);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
await replayWinningMixinDefaultCandidates(
|
|
1995
|
+
pendingDefaultCandidates,
|
|
1996
|
+
context,
|
|
1997
|
+
pending => evaluateCandidateOutput(
|
|
1998
|
+
pending.candidate,
|
|
1999
|
+
pending.rules,
|
|
2000
|
+
pending.outerRules,
|
|
2001
|
+
pending.params
|
|
2002
|
+
)
|
|
2003
|
+
);
|
|
2004
|
+
|
|
2005
|
+
if (
|
|
2006
|
+
skippedByRecursion
|
|
2007
|
+
&& outputRules.length === 0
|
|
2008
|
+
&& pendingDefaultCandidates.length === 0
|
|
2009
|
+
) {
|
|
2010
|
+
throw new ReferenceError('No matching mixins found.');
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
const output = assembleMixinInvocationOutput(
|
|
2014
|
+
outputRules,
|
|
2015
|
+
restrictMixinOutputLookup,
|
|
2016
|
+
context
|
|
2017
|
+
);
|
|
2018
|
+
|
|
2019
|
+
return output;
|
|
2020
|
+
}
|