@jesscss/core 2.0.0-alpha.5 → 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 -98
- package/lib/tree/ampersand.d.ts.map +0 -1
- package/lib/tree/ampersand.js +0 -319
- 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,2896 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Node,
|
|
3
|
+
defineType,
|
|
4
|
+
type NodeOptions,
|
|
5
|
+
type LocationInfo, type OptionalLocation,
|
|
6
|
+
type RenderKey,
|
|
7
|
+
CANONICAL,
|
|
8
|
+
EVAL,
|
|
9
|
+
type TreeContext,
|
|
10
|
+
F_STATIC,
|
|
11
|
+
F_VISIBLE,
|
|
12
|
+
isVisibleInContext
|
|
13
|
+
} from './node.js';
|
|
14
|
+
import { Context } from '../context.js';
|
|
15
|
+
import { isNode } from './util/is-node.js';
|
|
16
|
+
import { N } from './node-type.js';
|
|
17
|
+
import { type Ruleset } from './ruleset.js';
|
|
18
|
+
import { type Mixin } from './mixin.js';
|
|
19
|
+
import type { Selector } from './selector.js';
|
|
20
|
+
import { spaced, Sequence } from './sequence.js';
|
|
21
|
+
import { type PrintOptions, getPrintOptions } from './util/print.js';
|
|
22
|
+
|
|
23
|
+
import { atIndex } from './util/collections.js';
|
|
24
|
+
import * as Registries from './util/registry-utils.js';
|
|
25
|
+
import { processExtends } from './util/extend-roots.js';
|
|
26
|
+
import { type MaybePromise, pipe, isThenable, serialForEach } from '@jesscss/awaitable-pipe';
|
|
27
|
+
import { Nil } from './nil.js';
|
|
28
|
+
import { VarDeclaration } from './declaration-var.js';
|
|
29
|
+
import type { Declaration } from './declaration.js';
|
|
30
|
+
import { Any } from './any.js';
|
|
31
|
+
import { List } from './list.js';
|
|
32
|
+
import { indent, normalizeIndent } from './util/serialize-helper.js';
|
|
33
|
+
import { addEdge, addEdgeAt, addParentEdge, getEdgeAt } from './util/cursor.js';
|
|
34
|
+
import { getCurrentParentNode } from './util/selector-utils.js';
|
|
35
|
+
import {
|
|
36
|
+
getChildren,
|
|
37
|
+
getParent,
|
|
38
|
+
getSourceParent,
|
|
39
|
+
setChildren,
|
|
40
|
+
setChildAt,
|
|
41
|
+
getIndex,
|
|
42
|
+
setIndex,
|
|
43
|
+
setParent,
|
|
44
|
+
isPreEvaluated,
|
|
45
|
+
isEvaluated
|
|
46
|
+
} from './util/field-helpers.js';
|
|
47
|
+
import {
|
|
48
|
+
dispatchMixinEvalCandidates,
|
|
49
|
+
evaluateCandidateOutput,
|
|
50
|
+
evaluateMixinArgs,
|
|
51
|
+
filterAndSortMixinEvalCandidates,
|
|
52
|
+
finalizeMixinInvocationReturn,
|
|
53
|
+
getCandidateParent,
|
|
54
|
+
matchMixinCandidates,
|
|
55
|
+
type EvaluateCandidateOutputOptions
|
|
56
|
+
} from './util/mixin-instance-primitives.js';
|
|
57
|
+
import type { Func } from './function.js';
|
|
58
|
+
import type { Call } from './call.js';
|
|
59
|
+
const { isArray } = Array;
|
|
60
|
+
|
|
61
|
+
export const enum Priority {
|
|
62
|
+
None = 0,
|
|
63
|
+
Low = 1,
|
|
64
|
+
Medium = 2,
|
|
65
|
+
High = 3,
|
|
66
|
+
Highest = 4
|
|
67
|
+
}
|
|
68
|
+
export type RulesVisibility = 'public' | 'optional' | 'private';
|
|
69
|
+
export type FlatRulePosition = {
|
|
70
|
+
renderKey?: RenderKey;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type RulesOptions = {
|
|
74
|
+
/**
|
|
75
|
+
* - public = all members are considered in lookup algorithms
|
|
76
|
+
* - optional = members are only considered if not found in the lookup tree
|
|
77
|
+
* - private = can't be looked up
|
|
78
|
+
* - local = only visible in the current scope
|
|
79
|
+
*
|
|
80
|
+
* Different types may have different defaults
|
|
81
|
+
*
|
|
82
|
+
* For Less:
|
|
83
|
+
* - When mixins are parsed, their rules body is set to:
|
|
84
|
+
* visibility: {
|
|
85
|
+
* Ruleset: 'public',
|
|
86
|
+
* Declaration: 'public',
|
|
87
|
+
* VarDeclaration: 'optional',
|
|
88
|
+
* Mixin: 'public'
|
|
89
|
+
* }
|
|
90
|
+
* - When detached rulesets are parsed, their rules body is set to:
|
|
91
|
+
* visibility: {
|
|
92
|
+
* Ruleset: 'public',
|
|
93
|
+
* Declaration: 'public',
|
|
94
|
+
* VarDeclaration: 'private', <-- the one notable difference
|
|
95
|
+
* Mixin: 'public'
|
|
96
|
+
* }
|
|
97
|
+
* @note - The reason Less has "optionality" is likely because it tries
|
|
98
|
+
* to eagerly resolve variables, so even though its in a
|
|
99
|
+
* child scope, it will still be considered if nothing else in the
|
|
100
|
+
* scope is found. I'm guessing this is because "overwriting" a local
|
|
101
|
+
* variable from something like a mixin call would be counter-intuitive,
|
|
102
|
+
* but at the same time, I guess Alexis thought that eagerly resolving
|
|
103
|
+
* the variable might be useful.
|
|
104
|
+
*
|
|
105
|
+
* Note that right now, only Declarations being set to "optional"
|
|
106
|
+
* are supported. Everything else must be public or private.
|
|
107
|
+
*
|
|
108
|
+
* For Imports, the rules body is set to:
|
|
109
|
+
* visibility: {
|
|
110
|
+
* Ruleset: 'public',
|
|
111
|
+
* Declaration: 'public',
|
|
112
|
+
* VarDeclaration: 'public',
|
|
113
|
+
* Mixin: 'public'
|
|
114
|
+
* }
|
|
115
|
+
*/
|
|
116
|
+
rulesVisibility?: Record<string, RulesVisibility>;
|
|
117
|
+
/**
|
|
118
|
+
* If true, this Rules node is output from a mixin call.
|
|
119
|
+
* References with a target (e.g., #ns[@foo]) have public access to all nodes in these Rules.
|
|
120
|
+
* References without a target (e.g., @foo) cannot access these Rules.
|
|
121
|
+
*/
|
|
122
|
+
isMixinOutput?: boolean;
|
|
123
|
+
readonly?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* all imports other than classic `@import` set returned rules to local.
|
|
126
|
+
* The reason is that variables are not transitive, and you need to re-use
|
|
127
|
+
* modules to get the same variables.
|
|
128
|
+
*/
|
|
129
|
+
local?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Sass `@forward` semantics: this Rules node exists as an export surface for downstream
|
|
132
|
+
* consumers, but should not be visible to lookups within the current stylesheet scope.
|
|
133
|
+
*/
|
|
134
|
+
forward?: boolean;
|
|
135
|
+
/** Render gating marker for referenced imports/usages (serializer-time only). */
|
|
136
|
+
referenceMode?: boolean;
|
|
137
|
+
/** Explicit reference imports may render extended descendants; deduped imports may not. */
|
|
138
|
+
referenceRenderOnExtend?: boolean;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export interface Rules extends Node<Node[], RulesOptions & NodeOptions> {
|
|
142
|
+
readonly value: readonly Node[];
|
|
143
|
+
get options(): RulesOptions & NodeOptions & {
|
|
144
|
+
rulesVisibility: Record<string, RulesVisibility>;
|
|
145
|
+
};
|
|
146
|
+
set options(options: RulesOptions & NodeOptions & {
|
|
147
|
+
rulesVisibility: Record<string, RulesVisibility>;
|
|
148
|
+
});
|
|
149
|
+
eval(context: Context): MaybePromise<this>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type InvocationBindingFactory = (rules: Rules, context?: Context) => VarDeclaration;
|
|
153
|
+
|
|
154
|
+
export type InvocationBinding = {
|
|
155
|
+
template?: VarDeclaration;
|
|
156
|
+
factory?: InvocationBindingFactory;
|
|
157
|
+
declaration?: VarDeclaration;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* The class representing a "declaration list".
|
|
161
|
+
* CSS calls it this even though CSS Nesting
|
|
162
|
+
* adds a bunch more things that aren't declarations.
|
|
163
|
+
*
|
|
164
|
+
* Used by Ruleset and Mixin. Additionally, imports / use statements
|
|
165
|
+
* return rules.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* [
|
|
169
|
+
* (Declaration color: black;)
|
|
170
|
+
* (Declaration background-color: white;)
|
|
171
|
+
* ]
|
|
172
|
+
*/
|
|
173
|
+
export interface Rules {
|
|
174
|
+
type: 'Rules' | 'RawRules' | 'Collection';
|
|
175
|
+
shortType: 'rules' | 'rules-raw' | 'coll';
|
|
176
|
+
renderKey: RenderKey;
|
|
177
|
+
}
|
|
178
|
+
export class Rules extends Node<Node[], RulesOptions & NodeOptions> {
|
|
179
|
+
static override childKeys = ['value'] as const;
|
|
180
|
+
|
|
181
|
+
readonly value!: readonly Node[];
|
|
182
|
+
declare renderKey: RenderKey;
|
|
183
|
+
private _wrapperRegistrySeeded = false;
|
|
184
|
+
private _wrapperRegistrySeeding = false;
|
|
185
|
+
private _invocationBindings?: Map<string, InvocationBinding>;
|
|
186
|
+
|
|
187
|
+
functionRegistry: Registries.FunctionRegistry | undefined;
|
|
188
|
+
|
|
189
|
+
private _withOwnRenderKey<T>(
|
|
190
|
+
context: Context | undefined,
|
|
191
|
+
fn: () => T
|
|
192
|
+
): T {
|
|
193
|
+
if (!context) {
|
|
194
|
+
return fn();
|
|
195
|
+
}
|
|
196
|
+
const targetRenderKey = this.renderKey === CANONICAL
|
|
197
|
+
? (context.renderKey ?? this.renderKey)
|
|
198
|
+
: this.renderKey;
|
|
199
|
+
const needsRenderKey = context.renderKey !== targetRenderKey;
|
|
200
|
+
const needsRulesContext = context.rulesContext !== this;
|
|
201
|
+
if (!needsRenderKey && !needsRulesContext) {
|
|
202
|
+
return fn();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const previousRenderKey = context.renderKey;
|
|
206
|
+
const previousRulesContext = context.rulesContext;
|
|
207
|
+
if (needsRenderKey) {
|
|
208
|
+
context.renderKey = targetRenderKey;
|
|
209
|
+
}
|
|
210
|
+
if (needsRulesContext) {
|
|
211
|
+
context.rulesContext = this;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
return fn();
|
|
215
|
+
} finally {
|
|
216
|
+
if (needsRulesContext) {
|
|
217
|
+
context.rulesContext = previousRulesContext;
|
|
218
|
+
}
|
|
219
|
+
if (needsRenderKey) {
|
|
220
|
+
context.renderKey = previousRenderKey;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private _cloneOptionsForContext(_context?: Context): (RulesOptions & NodeOptions) | undefined {
|
|
226
|
+
const options = this.options
|
|
227
|
+
?? (this as any)._meta?.options as (RulesOptions & NodeOptions) | undefined;
|
|
228
|
+
if (!options) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...options,
|
|
233
|
+
rulesVisibility: options.rulesVisibility
|
|
234
|
+
? { ...options.rulesVisibility }
|
|
235
|
+
: options.rulesVisibility
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private _cloneInvocationBindings(): Map<string, InvocationBinding> | undefined {
|
|
240
|
+
if (!this._invocationBindings || this._invocationBindings.size === 0) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const next = new Map<string, InvocationBinding>();
|
|
244
|
+
for (const [key, binding] of this._invocationBindings) {
|
|
245
|
+
next.set(key, {
|
|
246
|
+
template: binding.declaration ?? binding.template,
|
|
247
|
+
factory: binding.factory
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return next;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Rules are often cloned during `preEval()` when a session is active.
|
|
255
|
+
* If callers register functions/mixins/declarations on the parsed tree
|
|
256
|
+
* before evaluation (e.g. via visitors), those registries must survive cloning so
|
|
257
|
+
* lookups during evaluation work as expected.
|
|
258
|
+
*/
|
|
259
|
+
override clone(deep?: boolean, cloneFn?: (n: Node) => Node, ctx?: Context): this {
|
|
260
|
+
const options = this._cloneOptionsForContext(ctx);
|
|
261
|
+
const location = Array.isArray(this.location) && this.location.length === 6
|
|
262
|
+
? this.location as LocationInfo
|
|
263
|
+
: undefined;
|
|
264
|
+
const detachedRenderKey = (
|
|
265
|
+
!deep
|
|
266
|
+
&& this.renderKey === CANONICAL
|
|
267
|
+
&& ctx
|
|
268
|
+
? ctx.nextRenderKey()
|
|
269
|
+
: this.renderKey
|
|
270
|
+
);
|
|
271
|
+
const newRules = deep
|
|
272
|
+
? super.clone(deep, cloneFn, ctx)
|
|
273
|
+
: (() => {
|
|
274
|
+
const wrapper = new (this.constructor as typeof Rules)(
|
|
275
|
+
[],
|
|
276
|
+
options ? { ...options } : undefined,
|
|
277
|
+
location,
|
|
278
|
+
this.treeContext
|
|
279
|
+
) as this;
|
|
280
|
+
wrapper._setValueArray(this.value as Node[]);
|
|
281
|
+
wrapper.inherit(this);
|
|
282
|
+
wrapper.renderKey = detachedRenderKey;
|
|
283
|
+
if (wrapper.renderKey !== CANONICAL) {
|
|
284
|
+
wrapper._connectSharedChildren(wrapper.renderKey);
|
|
285
|
+
}
|
|
286
|
+
return wrapper;
|
|
287
|
+
})();
|
|
288
|
+
|
|
289
|
+
if (deep && options) {
|
|
290
|
+
newRules.options = options as typeof newRules.options;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!deep) {
|
|
294
|
+
newRules.inherit(this);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const invocationBindings = this._cloneInvocationBindings();
|
|
298
|
+
if (invocationBindings) {
|
|
299
|
+
newRules._invocationBindings = invocationBindings;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (ctx) {
|
|
303
|
+
const parent = getCurrentParentNode(this, ctx);
|
|
304
|
+
if (parent) {
|
|
305
|
+
if (newRules.renderKey !== CANONICAL) {
|
|
306
|
+
setParent(newRules, parent, { ...ctx, renderKey: newRules.renderKey });
|
|
307
|
+
} else {
|
|
308
|
+
setParent(newRules, parent, ctx);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Only preserve *function* registry across clones.
|
|
314
|
+
// This supports Less plugin compat, where plugins can inject functions into the registry
|
|
315
|
+
// without creating AST nodes that would be re-registered on clone.
|
|
316
|
+
//
|
|
317
|
+
// Do NOT reuse declaration/mixin/ruleset registries across clones; those should always
|
|
318
|
+
// be rebuilt from AST nodes via lazy indexing.
|
|
319
|
+
if (this.functionRegistry) {
|
|
320
|
+
newRules.functionRegistry = this.functionRegistry.cloneForRules(newRules);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return newRules;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Detached ruleset calls unlock shared top-level children into the active
|
|
328
|
+
* lookup scope, but must not canonically reparent those children.
|
|
329
|
+
*
|
|
330
|
+
* Keep this seam local to Rules so the detached-ruleset path does not need
|
|
331
|
+
* to rely on raw clone(false) semantics.
|
|
332
|
+
*/
|
|
333
|
+
cloneDetachedUnlockWrapper(ctx: Context): this {
|
|
334
|
+
const wrapper = this.createShallowBodyWrapper(ctx) as this;
|
|
335
|
+
return wrapper;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Scope-isolation callers need copied Rules options/visibility while keeping
|
|
340
|
+
* shared top-level children canonically parented. Active lookups should still
|
|
341
|
+
* resolve through the wrapper during the current session.
|
|
342
|
+
*/
|
|
343
|
+
cloneVisibilityIsolationWrapper(ctx: Context): this {
|
|
344
|
+
const wrapper = this.createShallowBodyWrapper(ctx) as this;
|
|
345
|
+
return wrapper;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Lazily create registries for types as needed.
|
|
350
|
+
*/
|
|
351
|
+
private static _registryKey(type: string): 'rulesetRegistry' | 'mixinRegistry' | 'declarationRegistry' | 'functionRegistry' {
|
|
352
|
+
return `${type}Registry` as any;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private static _registryClass(type: string) {
|
|
356
|
+
return Registries[`${type.charAt(0).toUpperCase()}${type.slice(1)}Registry` as 'RulesetRegistry' | 'MixinRegistry' | 'DeclarationRegistry' | 'FunctionRegistry'];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private _ensureDirectRegistry(
|
|
360
|
+
type: 'ruleset' | 'declaration' | 'mixin' | 'function',
|
|
361
|
+
context?: Context
|
|
362
|
+
) {
|
|
363
|
+
const key = Rules._registryKey(type);
|
|
364
|
+
let registry = (this as any)[key];
|
|
365
|
+
if (!registry) {
|
|
366
|
+
registry = new (Rules._registryClass(type))(this, context);
|
|
367
|
+
(this as any)[key] = registry;
|
|
368
|
+
} else if (context && !(registry as any).context) {
|
|
369
|
+
(registry as any).context = context;
|
|
370
|
+
}
|
|
371
|
+
return registry;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private _isWrapperRegistryOwner(): boolean {
|
|
375
|
+
return this.renderKey !== CANONICAL;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private _connectSharedChildren(renderKey: RenderKey): void {
|
|
379
|
+
const seen = new Set<Node>();
|
|
380
|
+
const connectDescendants = (parent: Node, child: Node): void => {
|
|
381
|
+
addParentEdge(child, renderKey, parent);
|
|
382
|
+
if (seen.has(child)) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
seen.add(child);
|
|
386
|
+
const childKeys = (child.constructor as typeof Node).childKeys;
|
|
387
|
+
if (!childKeys) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
for (const key of childKeys) {
|
|
391
|
+
const value = (child as unknown as Record<string, unknown>)[key];
|
|
392
|
+
if (Array.isArray(value)) {
|
|
393
|
+
for (const item of value) {
|
|
394
|
+
if (item instanceof Node) {
|
|
395
|
+
connectDescendants(child, item);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (value instanceof Node) {
|
|
401
|
+
connectDescendants(child, value);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
for (const child of this.value) {
|
|
407
|
+
if (child instanceof Node) {
|
|
408
|
+
connectDescendants(this, child);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private _ensureWrapperRegistrySeeded(context?: Context): void {
|
|
414
|
+
if (!this._isWrapperRegistryOwner() || this._wrapperRegistrySeeded || this._wrapperRegistrySeeding) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this._wrapperRegistrySeeding = true;
|
|
419
|
+
try {
|
|
420
|
+
(this as any).rulesetRegistry = undefined;
|
|
421
|
+
(this as any).mixinRegistry = undefined;
|
|
422
|
+
(this as any).declarationRegistry = undefined;
|
|
423
|
+
this._rulesSet = [];
|
|
424
|
+
|
|
425
|
+
for (const child of this.getRegistryChildren(context)) {
|
|
426
|
+
this.registerNode(child, undefined, context);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this._wrapperRegistrySeeded = true;
|
|
430
|
+
} finally {
|
|
431
|
+
this._wrapperRegistrySeeding = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Register a child node into the appropriate registry.
|
|
437
|
+
* Creates the registry lazily on first registration.
|
|
438
|
+
* Wrapper/derived Rules own their registries directly.
|
|
439
|
+
* Canonical Rules still fall back to direct per-node registries until the
|
|
440
|
+
* remaining lookup paths are fully converged on render-path ownership.
|
|
441
|
+
*/
|
|
442
|
+
register(
|
|
443
|
+
type: 'ruleset' | 'declaration' | 'mixin' | 'function',
|
|
444
|
+
node: Node,
|
|
445
|
+
context?: Context
|
|
446
|
+
) {
|
|
447
|
+
return this._withOwnRenderKey(context, () => {
|
|
448
|
+
this._ensureWrapperRegistrySeeded(context);
|
|
449
|
+
const registry = this._ensureDirectRegistry(type, context);
|
|
450
|
+
return registry.add(node);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get a registry for lookups. Read-only — returns undefined if no
|
|
456
|
+
* registry was ever created (meaning nothing was registered).
|
|
457
|
+
*/
|
|
458
|
+
getRegistry(type: 'ruleset', context?: Context): Registries.RulesetRegistry;
|
|
459
|
+
getRegistry(type: 'declaration', context?: Context): Registries.DeclarationRegistry;
|
|
460
|
+
getRegistry(type: 'mixin', context?: Context): Registries.MixinRegistry;
|
|
461
|
+
getRegistry(type: 'function', context?: Context): Registries.FunctionRegistry;
|
|
462
|
+
getRegistry(type: 'ruleset' | 'declaration' | 'mixin' | 'function', context?: Context): Registries.RulesetRegistry | Registries.DeclarationRegistry | Registries.MixinRegistry | Registries.FunctionRegistry;
|
|
463
|
+
getRegistry(type: 'ruleset' | 'declaration' | 'mixin' | 'function', context?: Context) {
|
|
464
|
+
return this._withOwnRenderKey(context, () => {
|
|
465
|
+
this._ensureWrapperRegistrySeeded(context);
|
|
466
|
+
return this._ensureDirectRegistry(type, context);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
getRegistryParent(context?: Context): Rules | undefined {
|
|
471
|
+
return this._withOwnRenderKey(context, () => {
|
|
472
|
+
let parent = getCurrentParentNode(this, context);
|
|
473
|
+
while (parent && parent.type !== 'Rules') {
|
|
474
|
+
parent = getCurrentParentNode(parent, context);
|
|
475
|
+
}
|
|
476
|
+
if (parent) {
|
|
477
|
+
return parent as Rules | undefined;
|
|
478
|
+
}
|
|
479
|
+
let sourceParent = getSourceParent(this, context);
|
|
480
|
+
while (sourceParent && sourceParent.type !== 'Rules') {
|
|
481
|
+
sourceParent = getCurrentParentNode(sourceParent, context);
|
|
482
|
+
}
|
|
483
|
+
return sourceParent as Rules | undefined;
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* This wrapper is used so we don't prematurely create a registry
|
|
489
|
+
* just to search it.
|
|
490
|
+
*/
|
|
491
|
+
find(type: 'ruleset', keys: string | string[] | Set<string>, filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.RulesetRegistry['find']> | undefined;
|
|
492
|
+
find(type: 'declaration', keys: string, filterType?: string, options?: Registries.DeclarationFindOptions): ReturnType<Registries.DeclarationRegistry['find']> | undefined;
|
|
493
|
+
find(type: 'mixin', keys: string | string[], filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.MixinRegistry['find']> | undefined;
|
|
494
|
+
find(type: 'function', keys: string, filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.FunctionRegistry['find']> | undefined;
|
|
495
|
+
find(type: 'ruleset' | 'declaration' | 'mixin' | 'function', key: string, filterType: string, options?: Registries.FindOptions): ReturnType<Registries.RulesetRegistry['find']> | ReturnType<Registries.DeclarationRegistry['find']> | ReturnType<Registries.MixinRegistry['find']> | ReturnType<Registries.FunctionRegistry['find']> | undefined;
|
|
496
|
+
find(
|
|
497
|
+
type: 'ruleset' | 'declaration' | 'mixin' | 'function',
|
|
498
|
+
keys: string | string[] | Set<string>,
|
|
499
|
+
filterType?: string,
|
|
500
|
+
options: Registries.FindOptions = {}
|
|
501
|
+
): ReturnType<Registries.RulesetRegistry['find']> | ReturnType<Registries.DeclarationRegistry['find']> | ReturnType<Registries.MixinRegistry['find']> | ReturnType<Registries.FunctionRegistry['find']> | undefined {
|
|
502
|
+
return this._withOwnRenderKey(options.context, () => {
|
|
503
|
+
const registry = this.getRegistry(type, options.context);
|
|
504
|
+
return (registry.find as Function)(keys, filterType, options);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
findStatePatchedFunction(
|
|
509
|
+
name: string,
|
|
510
|
+
options: Registries.FindOptions = {}
|
|
511
|
+
): ReturnType<Registries.FunctionRegistry['find']> | undefined {
|
|
512
|
+
const { filter, context, searchParents = true } = options;
|
|
513
|
+
return this._withOwnRenderKey(context, () => {
|
|
514
|
+
let rules: Rules | undefined = this;
|
|
515
|
+
let findRoot = false;
|
|
516
|
+
|
|
517
|
+
while (rules) {
|
|
518
|
+
for (const child of rules.getRegistryChildren(context)) {
|
|
519
|
+
if (!isNode(child, N.Func)) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (filter && !filter(child)) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if ((child as Func).getNameKey(context) === name) {
|
|
526
|
+
return child as Func;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!searchParents) {
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
do {
|
|
535
|
+
rules = rules?.getRegistryParent(context);
|
|
536
|
+
if (findRoot && rules?.type === 'Rules' && rules.getRegistryParent(context) === undefined) {
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
if (rules && rules.sourceNode?.type === 'StyleImport' && rules.sourceNode.options.type !== 'import') {
|
|
540
|
+
findRoot = true;
|
|
541
|
+
}
|
|
542
|
+
} while (!findRoot && rules && rules.type !== 'Rules');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return undefined;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
override toString(options?: PrintOptions): string {
|
|
550
|
+
if (!isVisibleInContext(this, options?.context) && !this.fullRender) {
|
|
551
|
+
return '';
|
|
552
|
+
}
|
|
553
|
+
options = getPrintOptions(options);
|
|
554
|
+
const w = options.writer!;
|
|
555
|
+
const depth = options.depth!;
|
|
556
|
+
const mark = w.mark();
|
|
557
|
+
|
|
558
|
+
const ctx = options.context;
|
|
559
|
+
return this._withOwnRenderKey(ctx, () => {
|
|
560
|
+
const suppressedLeadingComments: Array<{ node: Node; visible: boolean }> = [];
|
|
561
|
+
if (depth === 0) {
|
|
562
|
+
// Snapshot global emit-tracking so repeated `.toString()` calls remain stable.
|
|
563
|
+
const prevCharsetEmitted = ctx?.charsetEmitted;
|
|
564
|
+
const prevTopImports = ctx?.topImports ? [...ctx.topImports] : undefined;
|
|
565
|
+
// @charset must be first
|
|
566
|
+
if (ctx?.currentCharset && !ctx.charsetEmitted) {
|
|
567
|
+
const charset = ctx.currentCharset;
|
|
568
|
+
// Use capture to avoid double-writing (toTrimmedString writes to writer AND returns the string)
|
|
569
|
+
const charsetStr = w.capture(() => charset.toTrimmedString(options));
|
|
570
|
+
w.add(charsetStr, charset);
|
|
571
|
+
w.add('\n');
|
|
572
|
+
// Do not permanently flip `charsetEmitted` here; restore at end.
|
|
573
|
+
ctx.charsetEmitted = true;
|
|
574
|
+
}
|
|
575
|
+
// Less keeps leading comments before hoisted @import output.
|
|
576
|
+
const isCommentLike = (node: Node): boolean => {
|
|
577
|
+
const text = String(node.valueOf?.() ?? '').trimStart();
|
|
578
|
+
if (!text.startsWith('/*')) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return isNode(node, N.Comment) || isNode(node, N.Any);
|
|
582
|
+
};
|
|
583
|
+
if (ctx?.topImports?.length) {
|
|
584
|
+
for (const node of this._getRenderChildren(ctx)) {
|
|
585
|
+
if (!isCommentLike(node)) {
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
const commentStr = w.capture(() => node.toTrimmedString(options));
|
|
589
|
+
w.add(normalizeIndent(commentStr, ''), node);
|
|
590
|
+
w.add('\n');
|
|
591
|
+
const wasVisible = node.hasFlag(F_VISIBLE);
|
|
592
|
+
suppressedLeadingComments.push({ node, visible: wasVisible });
|
|
593
|
+
if (wasVisible) {
|
|
594
|
+
node.removeFlag(F_VISIBLE);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// @import must come after @charset but before other rules
|
|
599
|
+
if (ctx?.topImports?.length) {
|
|
600
|
+
for (const importRule of ctx.topImports) {
|
|
601
|
+
const importStr = w.capture(() => importRule.toString(options));
|
|
602
|
+
w.add(normalizeIndent(importStr, ''), importRule);
|
|
603
|
+
w.add('\n');
|
|
604
|
+
}
|
|
605
|
+
// Do not permanently clear; restore at end.
|
|
606
|
+
}
|
|
607
|
+
// Restore global tracking (we only needed it during this print).
|
|
608
|
+
if (ctx) {
|
|
609
|
+
ctx.charsetEmitted = prevCharsetEmitted;
|
|
610
|
+
if (prevTopImports) {
|
|
611
|
+
ctx.topImports = prevTopImports;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this.processPrePost('pre', '', options);
|
|
617
|
+
const bodyMark = w.mark();
|
|
618
|
+
const bodyStr = this.toTrimmedString(options);
|
|
619
|
+
const bodyEmitted = w.getSince(bodyMark);
|
|
620
|
+
if (bodyEmitted.length === 0 && bodyStr) {
|
|
621
|
+
w.add(bodyStr);
|
|
622
|
+
}
|
|
623
|
+
// At root level, ensure output ends with a single newline (standard for CSS files)
|
|
624
|
+
// Don't propagate all the last child's post content (which may have extra whitespace)
|
|
625
|
+
if (depth === 0) {
|
|
626
|
+
for (const suppressed of suppressedLeadingComments) {
|
|
627
|
+
if (suppressed.visible) {
|
|
628
|
+
suppressed.node.addFlag(F_VISIBLE);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const result = w.getSince(mark).trimEnd();
|
|
632
|
+
// Ensure exactly one trailing newline (only if there's content)
|
|
633
|
+
return result ? result + '\n' : '';
|
|
634
|
+
}
|
|
635
|
+
return w.getSince(mark);
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
pendingExtends = new Set<[find: Selector, extendWith: Selector, partial: boolean]>();
|
|
640
|
+
|
|
641
|
+
_setValueArray(value: Node[]): void {
|
|
642
|
+
(this as unknown as { value: Node[] }).value = value;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
prependWrapperChildren(...items: Node[]): void {
|
|
646
|
+
if (items.length === 0) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
this._setValueArray([...items, ...(this.value as Node[])]);
|
|
650
|
+
for (const item of items) {
|
|
651
|
+
if (item instanceof Node) {
|
|
652
|
+
this.adopt(item);
|
|
653
|
+
this.registerNode(item);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Create a shallow body wrapper for mixin eval.
|
|
660
|
+
*
|
|
661
|
+
* Creates a new Rules that SHARES the current children array and does NOT adopt
|
|
662
|
+
* children canonically (their canonical `.parent` stays unchanged). Render-key
|
|
663
|
+
* parent edges carry the per-placement parent chain.
|
|
664
|
+
*
|
|
665
|
+
* This replaces clone(true) in the mixin body path for massive perf improvement:
|
|
666
|
+
* a mixin body with 100 declarations creates 1 Rules wrapper
|
|
667
|
+
* instead of recursively cloning all 100+ nodes.
|
|
668
|
+
*/
|
|
669
|
+
createShallowBodyWrapper(ctx?: Context, renderKey?: RenderKey): Rules {
|
|
670
|
+
const options = this._cloneOptionsForContext(ctx);
|
|
671
|
+
const location = Array.isArray(this.location) && this.location.length === 6
|
|
672
|
+
? this.location as LocationInfo
|
|
673
|
+
: undefined;
|
|
674
|
+
const nextRenderKey = renderKey ?? EVAL;
|
|
675
|
+
// Create a new Rules with empty children — bypass constructor adoption
|
|
676
|
+
const wrapper = new (this.constructor as typeof Rules)(
|
|
677
|
+
[],
|
|
678
|
+
options ? { ...options } : undefined,
|
|
679
|
+
location,
|
|
680
|
+
this.treeContext
|
|
681
|
+
);
|
|
682
|
+
// Reuse the same child array directly — NOT through the constructor
|
|
683
|
+
// so adopt() is NOT called on canonical children.
|
|
684
|
+
wrapper._setValueArray(this.value as Node[]);
|
|
685
|
+
wrapper.inherit(this);
|
|
686
|
+
wrapper.renderKey = nextRenderKey;
|
|
687
|
+
const invocationBindings = this._cloneInvocationBindings();
|
|
688
|
+
if (invocationBindings) {
|
|
689
|
+
wrapper._invocationBindings = invocationBindings;
|
|
690
|
+
}
|
|
691
|
+
if (this.functionRegistry) {
|
|
692
|
+
wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
|
|
693
|
+
}
|
|
694
|
+
wrapper._connectSharedChildren(wrapper.renderKey);
|
|
695
|
+
const sourceValueEdges = (this as unknown as { valueEdges?: Array<Map<RenderKey, Node> | undefined> }).valueEdges;
|
|
696
|
+
if (sourceValueEdges) {
|
|
697
|
+
for (let index = 0; index < sourceValueEdges.length; index++) {
|
|
698
|
+
const override = sourceValueEdges[index]?.get(nextRenderKey);
|
|
699
|
+
if (!override) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
addEdgeAt(wrapper, 'value', index, nextRenderKey, override);
|
|
703
|
+
addParentEdge(override, nextRenderKey, wrapper);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return wrapper;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
createPlacementWrapper(ctx?: Context, renderKey?: RenderKey): Rules {
|
|
710
|
+
const options = this._cloneOptionsForContext(ctx);
|
|
711
|
+
const location = Array.isArray(this.location) && this.location.length === 6
|
|
712
|
+
? this.location as LocationInfo
|
|
713
|
+
: undefined;
|
|
714
|
+
const nextRenderKey = renderKey ?? EVAL;
|
|
715
|
+
const wrapper = new (this.constructor as typeof Rules)(
|
|
716
|
+
[],
|
|
717
|
+
options ? { ...options } : undefined,
|
|
718
|
+
location,
|
|
719
|
+
this.treeContext
|
|
720
|
+
);
|
|
721
|
+
const previousRenderKey = ctx?.renderKey;
|
|
722
|
+
if (ctx && this.renderKey !== CANONICAL) {
|
|
723
|
+
ctx.renderKey = this.renderKey;
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
wrapper._setValueArray([...getChildren(this, ctx)] as Node[]);
|
|
727
|
+
} finally {
|
|
728
|
+
if (ctx) {
|
|
729
|
+
ctx.renderKey = previousRenderKey;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
wrapper.inherit(this);
|
|
733
|
+
wrapper.renderKey = nextRenderKey;
|
|
734
|
+
const invocationBindings = this._cloneInvocationBindings();
|
|
735
|
+
if (invocationBindings) {
|
|
736
|
+
wrapper._invocationBindings = invocationBindings;
|
|
737
|
+
}
|
|
738
|
+
if (this.functionRegistry) {
|
|
739
|
+
wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
|
|
740
|
+
}
|
|
741
|
+
wrapper._connectSharedChildren(wrapper.renderKey);
|
|
742
|
+
return wrapper;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
createPlacementWrapperWithChildren(children: readonly Node[], renderKey: RenderKey = EVAL): Rules {
|
|
746
|
+
const options = this._cloneOptionsForContext(undefined);
|
|
747
|
+
const location = Array.isArray(this.location) && this.location.length === 6
|
|
748
|
+
? this.location as LocationInfo
|
|
749
|
+
: undefined;
|
|
750
|
+
const wrapper = new (this.constructor as typeof Rules)(
|
|
751
|
+
[],
|
|
752
|
+
options ? { ...options } : undefined,
|
|
753
|
+
location,
|
|
754
|
+
this.treeContext
|
|
755
|
+
);
|
|
756
|
+
wrapper._setValueArray([...children] as Node[]);
|
|
757
|
+
wrapper.inherit(this);
|
|
758
|
+
wrapper.renderKey = renderKey;
|
|
759
|
+
const invocationBindings = this._cloneInvocationBindings();
|
|
760
|
+
if (invocationBindings) {
|
|
761
|
+
wrapper._invocationBindings = invocationBindings;
|
|
762
|
+
}
|
|
763
|
+
if (this.functionRegistry) {
|
|
764
|
+
wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
|
|
765
|
+
}
|
|
766
|
+
wrapper._connectSharedChildren(wrapper.renderKey);
|
|
767
|
+
return wrapper;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
withRenderOwner(
|
|
771
|
+
owner: Node,
|
|
772
|
+
renderKey?: RenderKey,
|
|
773
|
+
context?: Context
|
|
774
|
+
): Rules {
|
|
775
|
+
let rules: Rules = this;
|
|
776
|
+
const effectiveRenderKey = renderKey ?? owner.renderKey;
|
|
777
|
+
if (effectiveRenderKey !== undefined && effectiveRenderKey !== CANONICAL && rules.renderKey === CANONICAL) {
|
|
778
|
+
const existing = ((owner as unknown as Record<string, unknown>).rulesEdge as Map<RenderKey, Rules> | undefined)
|
|
779
|
+
?.get(effectiveRenderKey);
|
|
780
|
+
if (existing) {
|
|
781
|
+
rules = existing;
|
|
782
|
+
} else {
|
|
783
|
+
const wrapped = rules.createShallowBodyWrapper(undefined, effectiveRenderKey);
|
|
784
|
+
wrapped.parent = owner;
|
|
785
|
+
addEdge(owner, 'rules', effectiveRenderKey, wrapped);
|
|
786
|
+
if (owner.renderKey === effectiveRenderKey) {
|
|
787
|
+
(owner as unknown as { rules: Rules }).rules = wrapped;
|
|
788
|
+
}
|
|
789
|
+
rules = wrapped;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (context && getCurrentParentNode(rules, context) !== owner) {
|
|
793
|
+
owner.adopt(rules, context);
|
|
794
|
+
}
|
|
795
|
+
return rules;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
constructor(
|
|
799
|
+
value: readonly Node[],
|
|
800
|
+
options?: RulesOptions & NodeOptions,
|
|
801
|
+
location?: OptionalLocation,
|
|
802
|
+
context?: Context | TreeContext
|
|
803
|
+
) {
|
|
804
|
+
const treeContext = context instanceof Context
|
|
805
|
+
? context.treeContext
|
|
806
|
+
: context;
|
|
807
|
+
const ctx = context instanceof Context
|
|
808
|
+
? context
|
|
809
|
+
: undefined;
|
|
810
|
+
|
|
811
|
+
let rulesVisibility = options?.rulesVisibility ?? {};
|
|
812
|
+
rulesVisibility.Declaration ??= 'public';
|
|
813
|
+
rulesVisibility.Ruleset ??= 'public';
|
|
814
|
+
rulesVisibility.VarDeclaration ??= 'public';
|
|
815
|
+
rulesVisibility.Mixin ??= 'public';
|
|
816
|
+
const mergedOptions = { ...options, rulesVisibility };
|
|
817
|
+
const normalized = (value ?? []) as Node[];
|
|
818
|
+
super(normalized, mergedOptions, location, treeContext);
|
|
819
|
+
this._setValueArray(normalized);
|
|
820
|
+
for (const child of normalized) {
|
|
821
|
+
if (child instanceof Node) {
|
|
822
|
+
this.adopt(child, ctx);
|
|
823
|
+
this.registerNode(child);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
this.allowRoot = true;
|
|
827
|
+
this.allowRuleRoot = true;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
setInvocationBinding(name: string, binding: InvocationBinding): void {
|
|
831
|
+
(this._invocationBindings ??= new Map()).set(name, { ...binding });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
getInvocationBinding(name: string, context?: Context): VarDeclaration | undefined {
|
|
835
|
+
const binding = this._invocationBindings?.get(name);
|
|
836
|
+
if (!binding) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
if (binding.declaration) {
|
|
840
|
+
return binding.declaration;
|
|
841
|
+
}
|
|
842
|
+
const bindingContext = context
|
|
843
|
+
? {
|
|
844
|
+
...context,
|
|
845
|
+
rulesContext: this,
|
|
846
|
+
renderKey: context.renderKey ?? this.renderKey
|
|
847
|
+
} as Context
|
|
848
|
+
: undefined;
|
|
849
|
+
const declaration = binding.factory
|
|
850
|
+
? binding.factory(this, bindingContext)
|
|
851
|
+
: binding.template?.clone(false, undefined, bindingContext);
|
|
852
|
+
if (!declaration) {
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
declaration.renderKey = bindingContext?.renderKey ?? this.renderKey ?? declaration.renderKey;
|
|
856
|
+
if (bindingContext) {
|
|
857
|
+
setParent(declaration, this, bindingContext);
|
|
858
|
+
} else {
|
|
859
|
+
declaration.parent = this;
|
|
860
|
+
}
|
|
861
|
+
binding.declaration = declaration;
|
|
862
|
+
return declaration;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
* [Symbol.iterator]() {
|
|
866
|
+
let value = this.value;
|
|
867
|
+
/**
|
|
868
|
+
* This should always be the case? But at one point something somewhere
|
|
869
|
+
* set the value to undefined I think, so just leaving this defensively.
|
|
870
|
+
*/
|
|
871
|
+
if (isArray(value)) {
|
|
872
|
+
yield* value.entries();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private _getRenderVisibleChildAt(index: number, _context?: Context): Node | undefined {
|
|
877
|
+
const canonical = this.value[index];
|
|
878
|
+
if (this.renderKey === CANONICAL) {
|
|
879
|
+
return canonical;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const cursor = { node: this as Node, renderKey: this.renderKey };
|
|
883
|
+
const existing = getEdgeAt(cursor, 'value', index)?.node;
|
|
884
|
+
if (existing) {
|
|
885
|
+
return existing;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return canonical;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
private _getRenderChildren(context?: Context): readonly Node[] {
|
|
892
|
+
return this._withOwnRenderKey(context, () => {
|
|
893
|
+
if (this.renderKey === CANONICAL) {
|
|
894
|
+
return context ? getChildren(this, context) : this.value;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const children: Node[] = [];
|
|
898
|
+
for (let i = 0; i < this.value.length; i++) {
|
|
899
|
+
const child = this._getRenderVisibleChildAt(i, context);
|
|
900
|
+
if (child) {
|
|
901
|
+
children.push(child);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return children;
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
getRegistryChildren(context?: Context): readonly Node[] {
|
|
909
|
+
return this._getRenderChildren(context);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
ensureCurrentRenderRulesRegistered(context?: Context): void {
|
|
913
|
+
const currentRenderRules = this
|
|
914
|
+
.getRegistryChildren(context)
|
|
915
|
+
.filter((child): child is Rules => isNode(child, N.Rules));
|
|
916
|
+
|
|
917
|
+
for (let i = this.rulesSet.length; i < currentRenderRules.length; i++) {
|
|
918
|
+
this.registerNode(currentRenderRules[i]!, undefined, context);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private _setChildren(value: readonly Node[], context?: Context, markDirty: boolean = true): void {
|
|
923
|
+
if (context) {
|
|
924
|
+
setChildren(this, value, context, { markDirty });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const nextValue = [...value];
|
|
928
|
+
this._setValueArray(nextValue);
|
|
929
|
+
for (const child of nextValue) {
|
|
930
|
+
this.adopt(child);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private _setChildAt(index: number, node: Node, context?: Context, markDirty: boolean = true): void {
|
|
935
|
+
if (context) {
|
|
936
|
+
setChildAt(this, index, node, context, { markDirty });
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const nextValue = [...this.value];
|
|
940
|
+
nextValue[index] = node;
|
|
941
|
+
this._setValueArray(nextValue);
|
|
942
|
+
this.adopt(node);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Used by Ruleset, Mixins, and AtRules etc to render
|
|
947
|
+
* rules with braces.
|
|
948
|
+
*/
|
|
949
|
+
toBraced(options?: PrintOptions) {
|
|
950
|
+
let opts = getPrintOptions(options);
|
|
951
|
+
// Use options.depth if provided, otherwise calculate from frameState
|
|
952
|
+
const depth = opts.depth!;
|
|
953
|
+
const w = opts.writer!;
|
|
954
|
+
const mark = w.mark();
|
|
955
|
+
let space = ''.padStart(depth * 2);
|
|
956
|
+
return this._withOwnRenderKey(opts.context, () => {
|
|
957
|
+
w.add('{');
|
|
958
|
+
// Children render one level deeper inside braces.
|
|
959
|
+
const childOptions = { ...opts, depth: depth + 1 };
|
|
960
|
+
childOptions.writer!.add('\n');
|
|
961
|
+
Rules.prototype.toTrimmedString.call(this, childOptions);
|
|
962
|
+
// ensure closing brace is on its own properly indented line
|
|
963
|
+
w.add('\n');
|
|
964
|
+
if (depth !== 0) {
|
|
965
|
+
w.add(space);
|
|
966
|
+
}
|
|
967
|
+
w.add('}');
|
|
968
|
+
// At root level (depth === 0), don't add a newline after the closing brace
|
|
969
|
+
// The parent _emitRulesBody will add the newline before the next item
|
|
970
|
+
// For nested rules (depth > 0), the newline is handled by the parent's _emitRulesBody
|
|
971
|
+
return w.getSince(mark);
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
override toTrimmedString(options?: PrintOptions) {
|
|
976
|
+
options = getPrintOptions(options);
|
|
977
|
+
const w = options.writer!;
|
|
978
|
+
const mark = w.mark();
|
|
979
|
+
const ctx = options.context;
|
|
980
|
+
return this._withOwnRenderKey(ctx, () => {
|
|
981
|
+
const depth = options.depth ?? 0;
|
|
982
|
+
const space = indent(depth);
|
|
983
|
+
const value = this._getRenderChildren(options.context);
|
|
984
|
+
const referenceMode = Boolean(options.referenceMode);
|
|
985
|
+
const referenceRenderEnabled = referenceMode ? Boolean(options.referenceRenderEnabled) : true;
|
|
986
|
+
const items = value.filter(n => isVisibleInContext(n, options.context));
|
|
987
|
+
|
|
988
|
+
const isInlineSourceRules = (node: Node): boolean => {
|
|
989
|
+
if (node.type !== 'Rules') {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
const rulesNode = node as Rules;
|
|
993
|
+
const rulesValue = rulesNode._getRenderChildren(options.context);
|
|
994
|
+
if (rulesValue.length !== 1) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
const only = rulesValue[0]!;
|
|
998
|
+
return only.type === 'Any' && (only as Any).role === 'any';
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
let emittedCount = 0;
|
|
1002
|
+
let lastEmittedType: string | undefined;
|
|
1003
|
+
let lastEmittedWasInlineSourceRules = false;
|
|
1004
|
+
for (const n of items) {
|
|
1005
|
+
const isContainer = n.type === 'Ruleset' || n.type === 'AtRule' || n.type === 'Rules';
|
|
1006
|
+
if (referenceMode && !referenceRenderEnabled && !isContainer) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (emittedCount > 0) {
|
|
1010
|
+
const currentBuffer = w.getSince(0);
|
|
1011
|
+
const bufferEndsWithNewline = currentBuffer.endsWith('\n');
|
|
1012
|
+
const needsInlineBoundarySpacing = (
|
|
1013
|
+
(lastEmittedType === 'Any' && n.type !== 'Any')
|
|
1014
|
+
|| (lastEmittedWasInlineSourceRules && n.type !== 'Any')
|
|
1015
|
+
);
|
|
1016
|
+
if (!bufferEndsWithNewline || needsInlineBoundarySpacing) {
|
|
1017
|
+
w.add('\n');
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const isChildRules = n.type === 'Rules';
|
|
1022
|
+
const isRulesetOrAtRule = n.type === 'Ruleset' || n.type === 'AtRule';
|
|
1023
|
+
if (!isChildRules && !isRulesetOrAtRule && depth !== 0) {
|
|
1024
|
+
w.add(space);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
let childOptions = { ...options, depth };
|
|
1028
|
+
if (isChildRules) {
|
|
1029
|
+
if (referenceMode && referenceRenderEnabled) {
|
|
1030
|
+
childOptions = {
|
|
1031
|
+
...childOptions,
|
|
1032
|
+
referenceMode: false,
|
|
1033
|
+
referenceRenderEnabled: true
|
|
1034
|
+
};
|
|
1035
|
+
} else {
|
|
1036
|
+
const ownReferenceMode = (n.options as any)?.referenceMode === true;
|
|
1037
|
+
const childReferenceMode = referenceMode || ownReferenceMode;
|
|
1038
|
+
const enteringReferenceMode = !referenceMode && ownReferenceMode;
|
|
1039
|
+
const ownReferenceRenderOnExtend = (n.options as RulesOptions | undefined)?.referenceRenderOnExtend !== false;
|
|
1040
|
+
const childReferenceRenderOnExtend = childReferenceMode
|
|
1041
|
+
? (enteringReferenceMode ? ownReferenceRenderOnExtend : options.referenceRenderOnExtend !== false)
|
|
1042
|
+
: true;
|
|
1043
|
+
const childReferenceRenderEnabled = childReferenceMode
|
|
1044
|
+
? (enteringReferenceMode ? false : referenceRenderEnabled)
|
|
1045
|
+
: true;
|
|
1046
|
+
childOptions = {
|
|
1047
|
+
...childOptions,
|
|
1048
|
+
referenceMode: childReferenceMode,
|
|
1049
|
+
referenceRenderEnabled: childReferenceRenderEnabled,
|
|
1050
|
+
referenceRenderOnExtend: childReferenceRenderOnExtend
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
} else if (
|
|
1054
|
+
referenceMode
|
|
1055
|
+
&& referenceRenderEnabled
|
|
1056
|
+
&& isRulesetOrAtRule
|
|
1057
|
+
) {
|
|
1058
|
+
const keepReferenceFiltering = (
|
|
1059
|
+
isNode(n, N.Ruleset)
|
|
1060
|
+
&& (() => {
|
|
1061
|
+
const ownSelector = (n as Ruleset).getOwnSelector();
|
|
1062
|
+
return Boolean(
|
|
1063
|
+
ownSelector
|
|
1064
|
+
&& !(ownSelector instanceof Nil)
|
|
1065
|
+
&& isBareAmpersandOwnSelector(ownSelector)
|
|
1066
|
+
);
|
|
1067
|
+
})()
|
|
1068
|
+
);
|
|
1069
|
+
childOptions = {
|
|
1070
|
+
...childOptions,
|
|
1071
|
+
referenceMode: keepReferenceFiltering,
|
|
1072
|
+
referenceRenderEnabled: true
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const rule = w.capture(() => n.toTrimmedString(childOptions));
|
|
1077
|
+
if (!rule && isContainer) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
w.add(rule, n);
|
|
1081
|
+
const needsSemi = isNode(n, N.Declaration | N.VarDeclaration)
|
|
1082
|
+
? (n as Declaration).requiresSemi(childOptions.context)
|
|
1083
|
+
: (n as Node).requiredSemi;
|
|
1084
|
+
if (needsSemi && n.options.semi !== false) {
|
|
1085
|
+
w.add(';', n);
|
|
1086
|
+
}
|
|
1087
|
+
emittedCount++;
|
|
1088
|
+
lastEmittedType = n.type;
|
|
1089
|
+
lastEmittedWasInlineSourceRules = isInlineSourceRules(n);
|
|
1090
|
+
}
|
|
1091
|
+
return w.getSince(mark);
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** All rules, with nested rules flattened */
|
|
1096
|
+
flatRules(visibleOnly: boolean = false, context?: Context, positionMap?: WeakMap<Node, FlatRulePosition>) {
|
|
1097
|
+
const finalRules: Node[] = [];
|
|
1098
|
+
const iterateRules = (
|
|
1099
|
+
rules: Rules,
|
|
1100
|
+
inheritedRenderKey?: RenderKey
|
|
1101
|
+
) => {
|
|
1102
|
+
type DeferredEntry =
|
|
1103
|
+
| {
|
|
1104
|
+
kind: 'node';
|
|
1105
|
+
node: Node;
|
|
1106
|
+
renderKey?: RenderKey;
|
|
1107
|
+
}
|
|
1108
|
+
| {
|
|
1109
|
+
kind: 'flatten';
|
|
1110
|
+
rules: Rules;
|
|
1111
|
+
inheritedRenderKey?: RenderKey;
|
|
1112
|
+
};
|
|
1113
|
+
const renderKey = rules.renderKey ?? inheritedRenderKey;
|
|
1114
|
+
const scopedContext = context
|
|
1115
|
+
? (
|
|
1116
|
+
renderKey !== undefined
|
|
1117
|
+
&& context.renderKey !== renderKey
|
|
1118
|
+
? { ...context, renderKey, rulesContext: rules } as Context
|
|
1119
|
+
: (context.rulesContext !== rules
|
|
1120
|
+
? { ...context, rulesContext: rules } as Context
|
|
1121
|
+
: context)
|
|
1122
|
+
)
|
|
1123
|
+
: undefined;
|
|
1124
|
+
const pendingDescendants: DeferredEntry[] = [];
|
|
1125
|
+
let hasEmittedLocalNonContainer = false;
|
|
1126
|
+
const emitNode = (node: Node, nodeRenderKey?: RenderKey): void => {
|
|
1127
|
+
if (positionMap && nodeRenderKey !== CANONICAL && nodeRenderKey !== undefined) {
|
|
1128
|
+
positionMap.set(node, { renderKey: nodeRenderKey });
|
|
1129
|
+
}
|
|
1130
|
+
if (!isNode(node, N.Ruleset | N.AtRule | N.Rules)) {
|
|
1131
|
+
hasEmittedLocalNonContainer = true;
|
|
1132
|
+
}
|
|
1133
|
+
finalRules.push(node);
|
|
1134
|
+
};
|
|
1135
|
+
const flushPendingDescendants = (): void => {
|
|
1136
|
+
for (const entry of pendingDescendants) {
|
|
1137
|
+
if (entry.kind === 'flatten') {
|
|
1138
|
+
iterateRules(entry.rules, entry.inheritedRenderKey);
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
emitNode(entry.node, entry.renderKey);
|
|
1142
|
+
}
|
|
1143
|
+
pendingDescendants.length = 0;
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
for (let n of rules._getRenderChildren(scopedContext)) {
|
|
1147
|
+
if (isNode(n, N.Rules)) {
|
|
1148
|
+
if ((n.options as RulesOptions)?.referenceMode === true) {
|
|
1149
|
+
flushPendingDescendants();
|
|
1150
|
+
emitNode(
|
|
1151
|
+
n,
|
|
1152
|
+
(n as Rules).renderKey ?? renderKey
|
|
1153
|
+
);
|
|
1154
|
+
} else {
|
|
1155
|
+
if (pendingDescendants.length > 0 && !hasEmittedLocalNonContainer) {
|
|
1156
|
+
pendingDescendants.push({
|
|
1157
|
+
kind: 'flatten',
|
|
1158
|
+
rules: n as Rules,
|
|
1159
|
+
inheritedRenderKey: renderKey
|
|
1160
|
+
});
|
|
1161
|
+
} else {
|
|
1162
|
+
iterateRules(n, renderKey);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (
|
|
1168
|
+
visibleOnly
|
|
1169
|
+
&& isNode(n, N.Ruleset)
|
|
1170
|
+
&& !isVisibleInContext(n, scopedContext)
|
|
1171
|
+
&& !n.fullRender
|
|
1172
|
+
) {
|
|
1173
|
+
pendingDescendants.push({
|
|
1174
|
+
kind: 'flatten',
|
|
1175
|
+
rules: (n as Ruleset).enterRules(scopedContext),
|
|
1176
|
+
inheritedRenderKey: renderKey
|
|
1177
|
+
});
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
if (!visibleOnly || isVisibleInContext(n, scopedContext) || n.fullRender) {
|
|
1181
|
+
if (isNode(n, N.Ruleset)) {
|
|
1182
|
+
pendingDescendants.push({
|
|
1183
|
+
kind: 'node',
|
|
1184
|
+
node: n,
|
|
1185
|
+
renderKey
|
|
1186
|
+
});
|
|
1187
|
+
} else if (isNode(n, N.AtRule)) {
|
|
1188
|
+
flushPendingDescendants();
|
|
1189
|
+
emitNode(n, renderKey);
|
|
1190
|
+
} else {
|
|
1191
|
+
emitNode(n, renderKey);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
flushPendingDescendants();
|
|
1197
|
+
};
|
|
1198
|
+
iterateRules(this);
|
|
1199
|
+
return finalRules;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
visibleRules(context?: Context) {
|
|
1203
|
+
return this._getRenderChildren(context).filter(n => isVisibleInContext(n, context));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Return an object representation of a ruleset
|
|
1208
|
+
*/
|
|
1209
|
+
toObject(convertToPrimitives: true, context?: Context): Record<string, string | number | boolean>;
|
|
1210
|
+
toObject(convertToPrimitives: false, context?: Context): Record<string, Node>;
|
|
1211
|
+
toObject(convertToPrimitives?: boolean, context?: Context): Record<string, string | number | boolean | Node>;
|
|
1212
|
+
toObject(convertToPrimitives: boolean = true, context?: Context): Record<string, string | number | boolean | Node> {
|
|
1213
|
+
let output = new Map<string, boolean | string | number | Node>();
|
|
1214
|
+
const iterateRules = (rules: Rules) => {
|
|
1215
|
+
for (let n of rules._getRenderChildren(context)) {
|
|
1216
|
+
if (isNode(n, N.Declaration)) {
|
|
1217
|
+
let { name, value, important } = n as any;
|
|
1218
|
+
if (convertToPrimitives) {
|
|
1219
|
+
let primitive = value.valueOf();
|
|
1220
|
+
let outputValue = important ? `${primitive} ${important}` : primitive;
|
|
1221
|
+
if (outputValue === undefined) {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
output.set(name.toString(), outputValue);
|
|
1225
|
+
} else {
|
|
1226
|
+
let outputValue = important ? new Sequence([n, important]) : n;
|
|
1227
|
+
output.set(name.toString(), outputValue);
|
|
1228
|
+
}
|
|
1229
|
+
} else if (n instanceof Rules) {
|
|
1230
|
+
iterateRules(n);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
iterateRules(this as unknown as Rules);
|
|
1235
|
+
return Object.fromEntries(output);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/** @todo - Refactor? */
|
|
1239
|
+
_rulesSet: RulesEntry[] | undefined;
|
|
1240
|
+
get rulesSet(): RulesEntry[] {
|
|
1241
|
+
return (this._rulesSet ??= []);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
registerNode(node: Node, options?: Record<string, any>, context?: Context) {
|
|
1245
|
+
if (isNode(node, N.Rules)) {
|
|
1246
|
+
const nodeOptions = (node as Rules).options;
|
|
1247
|
+
// Use options if provided, otherwise use node's settings, otherwise empty
|
|
1248
|
+
// Then merge with node's settings to preserve any values not in options
|
|
1249
|
+
let optionsVisibility = options?.rulesVisibility;
|
|
1250
|
+
let nodeVisibility = nodeOptions.rulesVisibility ?? {};
|
|
1251
|
+
let rulesVisibility = optionsVisibility
|
|
1252
|
+
? { ...nodeVisibility, ...optionsVisibility }
|
|
1253
|
+
: nodeVisibility;
|
|
1254
|
+
|
|
1255
|
+
/** Only Declaration and Ruleset are public by default.
|
|
1256
|
+
* VarDeclaration visibility should be set by the parser (optional for Less, private for Jess/Sass).
|
|
1257
|
+
* Mixin visibility should be set by the parser.
|
|
1258
|
+
*/
|
|
1259
|
+
rulesVisibility.Declaration ??= 'public';
|
|
1260
|
+
rulesVisibility.Ruleset ??= 'public';
|
|
1261
|
+
rulesVisibility.Mixin ??= 'public';
|
|
1262
|
+
|
|
1263
|
+
/** Either one set as readonly will win */
|
|
1264
|
+
let readonly = Boolean(options?.readonly || nodeOptions.readonly);
|
|
1265
|
+
this.rulesSet.push({
|
|
1266
|
+
node,
|
|
1267
|
+
rulesVisibility,
|
|
1268
|
+
readonly
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// Note: Rulesets from imported Rules are registered in treeRoot's registry
|
|
1272
|
+
// after evaluation completes (in evalNode), when treeRoot is guaranteed to be set
|
|
1273
|
+
} else if (isNode(node, N.Declaration)) {
|
|
1274
|
+
/**
|
|
1275
|
+
* setDefined works like Sass's !default flag - it finds the original variable
|
|
1276
|
+
* declaration and inserts a new declaration at the same rules level as the
|
|
1277
|
+
* found variable, but before the current nested node.
|
|
1278
|
+
*/
|
|
1279
|
+
if (node.options?.setDefined && context) {
|
|
1280
|
+
const key = (node as any).name?.toString();
|
|
1281
|
+
const sourceNode = node.sourceNode ?? node;
|
|
1282
|
+
let opts: Registries.FindOptions = {};
|
|
1283
|
+
opts.searchParents = true;
|
|
1284
|
+
opts.context = context;
|
|
1285
|
+
opts.start = undefined;
|
|
1286
|
+
// Exclude the current declaration by source identity so a derived eval node
|
|
1287
|
+
// cannot resolve its own canonical registry entry as the "existing" declaration.
|
|
1288
|
+
opts.filter = (n: Node) => (n.sourceNode ?? n) !== sourceNode;
|
|
1289
|
+
let result = this.find('declaration', key, node.type as 'VarDeclaration' | 'Declaration', opts);
|
|
1290
|
+
if (result) {
|
|
1291
|
+
if (result.options?.readonly || opts.readonly) {
|
|
1292
|
+
throw new ReferenceError(`"${key}" is readonly`);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Find the Rules node that contains the found declaration
|
|
1296
|
+
let foundRules = getCurrentParentNode(result, context) as Rules | undefined;
|
|
1297
|
+
|
|
1298
|
+
if (!foundRules) {
|
|
1299
|
+
throw new Error(`Could not find parent Rules for declaration '${key}'`);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Create a new declaration with the same name but our value
|
|
1303
|
+
const newDeclaration = node.copy();
|
|
1304
|
+
newDeclaration.options = { ...newDeclaration.options };
|
|
1305
|
+
newDeclaration.options.setDefined = undefined; // Remove setDefined flag
|
|
1306
|
+
|
|
1307
|
+
// Adopt the new declaration to the found Rules
|
|
1308
|
+
foundRules.adopt(newDeclaration);
|
|
1309
|
+
|
|
1310
|
+
// Add to the value array AFTER the found declaration
|
|
1311
|
+
// This ensures it shadows the original and is evaluated after it
|
|
1312
|
+
const foundIndex = foundRules.value.indexOf(result);
|
|
1313
|
+
if (foundIndex !== -1) {
|
|
1314
|
+
if (context) {
|
|
1315
|
+
foundRules.splice(context, foundIndex + 1, 0, newDeclaration);
|
|
1316
|
+
} else {
|
|
1317
|
+
foundRules.splice(foundIndex + 1, 0, newDeclaration);
|
|
1318
|
+
}
|
|
1319
|
+
} else {
|
|
1320
|
+
// If not found in array, add at the beginning
|
|
1321
|
+
if (context) {
|
|
1322
|
+
foundRules.unshift(context, newDeclaration);
|
|
1323
|
+
} else {
|
|
1324
|
+
foundRules.unshift(newDeclaration);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Register it via registerNode to ensure it's properly indexed
|
|
1329
|
+
// Note: registerNode will call register('declaration', ...) which adds to registry
|
|
1330
|
+
// We skip setDefined processing since we already removed the flag
|
|
1331
|
+
foundRules.registerNode(newDeclaration, undefined, context);
|
|
1332
|
+
} else {
|
|
1333
|
+
throw new ReferenceError(`"${key}" is not defined`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
this.register('declaration', node, context);
|
|
1338
|
+
} else if (isNode(node, N.Ruleset)) {
|
|
1339
|
+
// Register to 'mixin' for mixin calls
|
|
1340
|
+
// Always register - guard filtering happens at call time in getFunctionFromMixins
|
|
1341
|
+
// Note: 'ruleset' registration for extends now happens in Ruleset.preEval to the extend root's registry
|
|
1342
|
+
this.register('mixin', node, context);
|
|
1343
|
+
} else if (isNode(node, N.Mixin)) {
|
|
1344
|
+
this.register('mixin', node, context);
|
|
1345
|
+
} else if (isNode(node, N.Func)) {
|
|
1346
|
+
this.register('function', node, context);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
override push(...nodes: Node[]): void;
|
|
1351
|
+
override push(ctx: Context, ...nodes: Node[]): void;
|
|
1352
|
+
override push(...args: [Context, ...Node[]] | Node[]): void {
|
|
1353
|
+
const hasCtx = args.length > 0 && args[0] instanceof Context;
|
|
1354
|
+
const ctx = hasCtx ? args[0] as Context : undefined;
|
|
1355
|
+
const nodes = (hasCtx ? args.slice(1) : args) as Node[];
|
|
1356
|
+
// Route through _getChildren/_setChildren overlay when context is active
|
|
1357
|
+
if (ctx) {
|
|
1358
|
+
const nextValue = [...this._getRenderChildren(ctx)];
|
|
1359
|
+
for (const node of nodes) {
|
|
1360
|
+
this.adopt(node, ctx);
|
|
1361
|
+
nextValue.push(node);
|
|
1362
|
+
}
|
|
1363
|
+
this._setChildren(nextValue, ctx);
|
|
1364
|
+
for (const node of nodes) {
|
|
1365
|
+
this.registerNode(node, undefined, ctx);
|
|
1366
|
+
}
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
this._setValueArray([...this.value]);
|
|
1370
|
+
for (const node of nodes) {
|
|
1371
|
+
this.adopt(node, ctx);
|
|
1372
|
+
(this.value as Node[]).push(node);
|
|
1373
|
+
this.registerNode(node, undefined, ctx);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
override splice(start: number, deleteCount: number, ...items: Node[]): Node[];
|
|
1378
|
+
override splice(ctx: Context, start: number, deleteCount: number, ...items: Node[]): Node[];
|
|
1379
|
+
override splice(...args: [Context, number, number, ...Node[]] | [number, number, ...Node[]]): Node[] {
|
|
1380
|
+
const hasCtx = args[0] instanceof Context;
|
|
1381
|
+
const ctx = hasCtx ? args[0] as Context : undefined;
|
|
1382
|
+
const [start, deleteCount, ...items] = (hasCtx ? args.slice(1) : args) as [number, number, ...Node[]];
|
|
1383
|
+
// Route through overlay when context is active
|
|
1384
|
+
if (ctx) {
|
|
1385
|
+
const nextValue = [...this._getRenderChildren(ctx)];
|
|
1386
|
+
const removed = nextValue.splice(start, deleteCount, ...items);
|
|
1387
|
+
for (const item of items) {
|
|
1388
|
+
if (item instanceof Node) {
|
|
1389
|
+
this.adopt(item, ctx);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
this._setChildren(nextValue, ctx);
|
|
1393
|
+
for (const item of items) {
|
|
1394
|
+
if (item instanceof Node) {
|
|
1395
|
+
this.registerNode(item, undefined, ctx);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
(this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
|
|
1399
|
+
return removed as Node[];
|
|
1400
|
+
}
|
|
1401
|
+
const nextValue = [...this.value];
|
|
1402
|
+
const removed = nextValue.splice(start, deleteCount, ...items);
|
|
1403
|
+
this._setValueArray(nextValue);
|
|
1404
|
+
for (const item of items) {
|
|
1405
|
+
if (item instanceof Node) {
|
|
1406
|
+
this.adopt(item, ctx);
|
|
1407
|
+
this.registerNode(item, undefined, ctx);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
(this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
|
|
1411
|
+
return removed as Node[];
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
override unshift(...items: Node[]): void;
|
|
1415
|
+
override unshift(ctx: Context, ...items: Node[]): void;
|
|
1416
|
+
override unshift(...args: [Context, ...Node[]] | Node[]): void {
|
|
1417
|
+
const hasCtx = args.length > 0 && args[0] instanceof Context;
|
|
1418
|
+
const ctx = hasCtx ? args[0] as Context : undefined;
|
|
1419
|
+
const items = (hasCtx ? args.slice(1) : args) as Node[];
|
|
1420
|
+
// Route through overlay when context is active
|
|
1421
|
+
if (ctx) {
|
|
1422
|
+
for (const item of items) {
|
|
1423
|
+
if (item instanceof Node) {
|
|
1424
|
+
this.adopt(item, ctx);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
this._setChildren([...items, ...this._getRenderChildren(ctx)], ctx);
|
|
1428
|
+
for (const item of items) {
|
|
1429
|
+
if (item instanceof Node) {
|
|
1430
|
+
this.registerNode(item, undefined, ctx);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
(this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
this._setValueArray([...this.value]);
|
|
1437
|
+
(this.value as Node[]).unshift(...items);
|
|
1438
|
+
for (const item of items) {
|
|
1439
|
+
if (item instanceof Node) {
|
|
1440
|
+
this.adopt(item, ctx);
|
|
1441
|
+
this.registerNode(item, undefined, ctx);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
(this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
at(index: number, context?: Context) {
|
|
1448
|
+
return atIndex(this._getRenderChildren(context), index);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* This traverses deeply to visit all nodes, but indexes locally.
|
|
1453
|
+
*/
|
|
1454
|
+
override preEval(context: Context) {
|
|
1455
|
+
if (!this.preEvaluated) {
|
|
1456
|
+
context.depth++;
|
|
1457
|
+
let rules = this;
|
|
1458
|
+
// When this is the nestable at-rule wrapper (one child Ruleset(&)), do not clone so
|
|
1459
|
+
// inner rulesets register to the same object we push and register as extend root.
|
|
1460
|
+
const nestableAtRuleNames = new Set(['@media', '@supports', '@layer', '@container', '@scope']);
|
|
1461
|
+
const activeParent = getCurrentParentNode(this, context);
|
|
1462
|
+
const sourceParent = getSourceParent(this, context);
|
|
1463
|
+
const parentAtRule = activeParent?.type === 'AtRule'
|
|
1464
|
+
? activeParent
|
|
1465
|
+
: (sourceParent?.type === 'AtRule' ? sourceParent : null);
|
|
1466
|
+
const isNestableAtRuleBody =
|
|
1467
|
+
parentAtRule
|
|
1468
|
+
&& nestableAtRuleNames.has(String((parentAtRule as any).name?.valueOf?.() ?? ''));
|
|
1469
|
+
const children = rules._getRenderChildren(context);
|
|
1470
|
+
const first = children[0];
|
|
1471
|
+
const isWrapper =
|
|
1472
|
+
isNestableAtRuleBody
|
|
1473
|
+
&& children.length === 1
|
|
1474
|
+
&& isNode(first, N.Ruleset)
|
|
1475
|
+
&& isNode((first as Ruleset).get('selector'), N.Ampersand);
|
|
1476
|
+
if (isWrapper) {
|
|
1477
|
+
rules = this;
|
|
1478
|
+
}
|
|
1479
|
+
rules.preEvaluated = true;
|
|
1480
|
+
// Save current context and set up new context for variable lookups during preEval
|
|
1481
|
+
const saved = this._snapshotContext(context);
|
|
1482
|
+
this._setupContextForRules(context, rules);
|
|
1483
|
+
|
|
1484
|
+
// Set context.root early if this is the main root
|
|
1485
|
+
const isMainRoot = !context.root;
|
|
1486
|
+
if (isMainRoot) {
|
|
1487
|
+
context.root = rules;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* I think maybe we can just set the index to the actual order?
|
|
1492
|
+
*/
|
|
1493
|
+
for (let i = 0; i < children.length; i++) {
|
|
1494
|
+
let n = children[i]!;
|
|
1495
|
+
setIndex(n, i, context);
|
|
1496
|
+
}
|
|
1497
|
+
// Preserve parent when cloning - if this Rules is inside a ruleset, maintain the parent relationship
|
|
1498
|
+
const parent = getCurrentParentNode(this, context);
|
|
1499
|
+
if (parent && !getCurrentParentNode(rules, context)) {
|
|
1500
|
+
parent.adopt(rules, context);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// Set context.root if not already set (needed for preEval visitors)
|
|
1504
|
+
if (!context.root) {
|
|
1505
|
+
context.root = rules;
|
|
1506
|
+
}
|
|
1507
|
+
// When getTree() set context.root to the original Rules but we're processing a clone,
|
|
1508
|
+
// use the clone as context.root so registerRoot/pushExtendRoot run and rulesets register to the clone (extend fix).
|
|
1509
|
+
if (context.root === this && this !== rules) {
|
|
1510
|
+
context.root = rules;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Register main root as extend root if this is the root (needed for extends in preEval)
|
|
1514
|
+
// Check rules === context.root at registration time (not using stale isMainRoot)
|
|
1515
|
+
if (rules === context.root && !context.extendRoots.root) {
|
|
1516
|
+
context.extendRoots.registerRoot(rules);
|
|
1517
|
+
context.extendRoots.pushExtendRoot(rules);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Always push nestable at-rule body so inner rulesets register to it (not document root).
|
|
1521
|
+
// Needed for both: wrapper (collapseNesting) and direct body (collapseNesting: false).
|
|
1522
|
+
if (isNestableAtRuleBody) {
|
|
1523
|
+
context.extendRoots.pushExtendRoot(rules);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Multi-pass registration system for handling interpolated names
|
|
1527
|
+
const mp = this._multiPassPreEval(rules, context, saved);
|
|
1528
|
+
const popNestableBody = () => {
|
|
1529
|
+
if (isNestableAtRuleBody) {
|
|
1530
|
+
context.extendRoots.popExtendRoot();
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
if (isThenable(mp)) {
|
|
1534
|
+
return (mp as Promise<this>).then((result) => {
|
|
1535
|
+
popNestableBody();
|
|
1536
|
+
return result;
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
popNestableBody();
|
|
1540
|
+
return mp;
|
|
1541
|
+
}
|
|
1542
|
+
return this;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* Multi-pass preEval system to handle interpolated names and dependencies
|
|
1547
|
+
*/
|
|
1548
|
+
private _multiPassPreEval(rules: Rules, context: Context, saved: any): MaybePromise<this> {
|
|
1549
|
+
// First pass: Only register nodes with static names
|
|
1550
|
+
const staticNodes: Node[] = [];
|
|
1551
|
+
const dynamicNodes: Node[] = [];
|
|
1552
|
+
|
|
1553
|
+
// Process each node with static name, handling both sync and async preEval
|
|
1554
|
+
const processResult = serialForEach(rules._getRenderChildren(context), (node, index) => {
|
|
1555
|
+
// Check if node has a static name (can be registered immediately)
|
|
1556
|
+
if (node.type === 'Any' && (node as any).role === 'charset') {
|
|
1557
|
+
/** Special case where we register the charset node immediately */
|
|
1558
|
+
const charsetNode = (node as Any).preEval(context);
|
|
1559
|
+
rules._setChildAt(index, charsetNode, context, false);
|
|
1560
|
+
rules.adopt(charsetNode, context);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
// Nodes that don't register by name (Call, Expression, etc.) skip
|
|
1564
|
+
// both preEval and dynamic resolution — they're handled by the eval queue.
|
|
1565
|
+
if (!this._isRegisterableType(node)) {
|
|
1566
|
+
setIndex(node, index, context);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (this._hasStaticName(node, context)) {
|
|
1570
|
+
// Pre-evaluate nodes with static names before registration
|
|
1571
|
+
// This ensures selectors are evaluated and keySets are available for rulesets
|
|
1572
|
+
const preEvald = node.preEval(context);
|
|
1573
|
+
if (isThenable(preEvald)) {
|
|
1574
|
+
return (preEvald as Promise<Node>).then((preEvaldNode) => {
|
|
1575
|
+
rules._setChildAt(index, preEvaldNode, context, false);
|
|
1576
|
+
rules.adopt(preEvaldNode, context);
|
|
1577
|
+
setIndex(preEvaldNode as Node, index, context);
|
|
1578
|
+
// After async preEval, check if it still has a static name
|
|
1579
|
+
if (this._hasStaticName(preEvaldNode, context)) {
|
|
1580
|
+
staticNodes.push(preEvaldNode);
|
|
1581
|
+
this._registerNodeIfEligible(rules, preEvaldNode, context);
|
|
1582
|
+
} else {
|
|
1583
|
+
dynamicNodes.push(preEvaldNode);
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
rules._setChildAt(index, preEvald as Node, context, false);
|
|
1588
|
+
rules.adopt(preEvald as Node, context);
|
|
1589
|
+
setIndex(preEvald as Node, index, context);
|
|
1590
|
+
const nodeToRegister = preEvald as Node;
|
|
1591
|
+
staticNodes.push(nodeToRegister);
|
|
1592
|
+
this._registerNodeIfEligible(rules, nodeToRegister, context);
|
|
1593
|
+
} else {
|
|
1594
|
+
dynamicNodes.push(node);
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
const finish = () => {
|
|
1599
|
+
// If no dynamic nodes, we're done
|
|
1600
|
+
if (dynamicNodes.length === 0) {
|
|
1601
|
+
// Restore context after preEval is complete
|
|
1602
|
+
context.rulesContext = saved.rulesContext;
|
|
1603
|
+
context.renderKey = saved.renderKey;
|
|
1604
|
+
context.treeRoot = saved.treeRoot;
|
|
1605
|
+
// Only restore context.root if saved.root is defined (not the outermost root)
|
|
1606
|
+
// If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
|
|
1607
|
+
if (saved.root !== undefined) {
|
|
1608
|
+
context.root = saved.root;
|
|
1609
|
+
}
|
|
1610
|
+
return rules as this;
|
|
1611
|
+
}
|
|
1612
|
+
// Multi-pass resolution of dynamic nodes
|
|
1613
|
+
return this._resolveDynamicNodes(rules, context, saved, dynamicNodes);
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
if (isThenable(processResult)) {
|
|
1617
|
+
return (processResult as Promise<void>).then(() => finish());
|
|
1618
|
+
}
|
|
1619
|
+
return finish();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* Helper to check if a value is static (either a Node with F_STATIC flag or a primitive value)
|
|
1624
|
+
*/
|
|
1625
|
+
private _isStatic(value: any): boolean {
|
|
1626
|
+
if (value && typeof value.hasFlag === 'function') {
|
|
1627
|
+
return value.hasFlag(F_STATIC);
|
|
1628
|
+
}
|
|
1629
|
+
// Primitive values (strings, numbers, etc.) are considered static
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Check if a node type participates in name-based registration.
|
|
1635
|
+
* Only these node types have names/selectors that _resolveDynamicNodes
|
|
1636
|
+
* needs to resolve. Everything else (Call, Expression, Comment, etc.)
|
|
1637
|
+
* goes straight to the eval queue without preEval.
|
|
1638
|
+
*/
|
|
1639
|
+
private _isRegisterableType(node: Node): boolean {
|
|
1640
|
+
return isNode(node, N.VarDeclaration | N.Declaration | N.Mixin | N.Ruleset) || (node as Node).type === 'StyleImport';
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Check if a node has a static name that can be registered immediately
|
|
1645
|
+
*/
|
|
1646
|
+
private _hasStaticName(node: Node, context?: Context): boolean {
|
|
1647
|
+
if (isNode(node, N.VarDeclaration)) {
|
|
1648
|
+
return this._isStatic(node.get('name'));
|
|
1649
|
+
}
|
|
1650
|
+
if (isNode(node, N.Mixin)) {
|
|
1651
|
+
// Check position-patched name: preEval may have resolved an interpolated name
|
|
1652
|
+
const name = node.get('name', context);
|
|
1653
|
+
return this._isStatic(name);
|
|
1654
|
+
}
|
|
1655
|
+
if (isNode(node, N.Declaration)) {
|
|
1656
|
+
return this._isStatic(node.get('name'));
|
|
1657
|
+
}
|
|
1658
|
+
if (node.type === 'StyleImport') {
|
|
1659
|
+
return this._isStatic((node as Node & { path: unknown }).path);
|
|
1660
|
+
}
|
|
1661
|
+
if (isNode(node, N.Ruleset)) {
|
|
1662
|
+
const selector: Node = (node as Ruleset).get('selector');
|
|
1663
|
+
if (isNode(selector, N.BasicSelector | N.CompoundSelector | N.ComplexSelector | N.SelectorList | N.Nil)) {
|
|
1664
|
+
return true;
|
|
1665
|
+
}
|
|
1666
|
+
if (context && isPreEvaluated(node, context)) {
|
|
1667
|
+
return true;
|
|
1668
|
+
}
|
|
1669
|
+
return (selector as Node).hasFlag(F_STATIC);
|
|
1670
|
+
}
|
|
1671
|
+
return node.hasFlag(F_STATIC);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/**
|
|
1675
|
+
* Register a node if it's eligible for registration
|
|
1676
|
+
*/
|
|
1677
|
+
private _registerNodeIfEligible(rules: Rules, node: Node, context: Context) {
|
|
1678
|
+
if (isNode(node, N.Declaration)) {
|
|
1679
|
+
rules.registerNode(node, undefined, context);
|
|
1680
|
+
} else if (isNode(node, N.Mixin)) {
|
|
1681
|
+
rules.registerNode(node, undefined, context);
|
|
1682
|
+
} else if (isNode(node, N.Ruleset)) {
|
|
1683
|
+
// registerNode handles both 'mixin' and 'ruleset' registries
|
|
1684
|
+
rules.registerNode(node, undefined, context);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* Multi-pass resolution of dynamic nodes with interpolated names
|
|
1690
|
+
*/
|
|
1691
|
+
private _resolveDynamicNodes(rules: Rules, context: Context, saved: any, dynamicNodes: Node[]): MaybePromise<this> {
|
|
1692
|
+
const resolvedNodes: Node[] = [];
|
|
1693
|
+
|
|
1694
|
+
const handleResolvedNode = (resolvedNode: Node, node: Node, stillUnresolved: Node[]): boolean => {
|
|
1695
|
+
if (resolvedNode.index === undefined) {
|
|
1696
|
+
resolvedNode.index = node.index;
|
|
1697
|
+
}
|
|
1698
|
+
if (!resolvedNode.sourceNode) {
|
|
1699
|
+
resolvedNode.sourceNode = node.sourceNode ?? node;
|
|
1700
|
+
}
|
|
1701
|
+
if (resolvedNode.type === 'Ruleset') {
|
|
1702
|
+
rules.registerNode(resolvedNode, undefined, context);
|
|
1703
|
+
}
|
|
1704
|
+
if (isNode(resolvedNode, N.Nil) || this._hasStaticName(resolvedNode, context)) {
|
|
1705
|
+
resolvedNodes.push(resolvedNode);
|
|
1706
|
+
this._registerNodeIfEligible(rules, resolvedNode, context);
|
|
1707
|
+
return true; // made progress
|
|
1708
|
+
} else {
|
|
1709
|
+
stillUnresolved.push(resolvedNode);
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
|
|
1714
|
+
const applyResolvedNodes = () => {
|
|
1715
|
+
const children = rules._getRenderChildren(context);
|
|
1716
|
+
for (let i = 0; i < children.length; i++) {
|
|
1717
|
+
const node = children[i]!;
|
|
1718
|
+
const nodeIdx = getIndex(node, context);
|
|
1719
|
+
const resolvedNode = resolvedNodes.find(n => getIndex(n, context) === nodeIdx);
|
|
1720
|
+
if (resolvedNode && resolvedNode !== node) {
|
|
1721
|
+
rules._setChildAt(i, resolvedNode.inherit(node), context, false);
|
|
1722
|
+
rules.adopt(resolvedNode, context);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
const finishResolution = (): this => {
|
|
1728
|
+
applyResolvedNodes();
|
|
1729
|
+
context.rulesContext = saved.rulesContext;
|
|
1730
|
+
context.renderKey = saved.renderKey;
|
|
1731
|
+
context.treeRoot = saved.treeRoot;
|
|
1732
|
+
if (saved.root !== undefined) {
|
|
1733
|
+
context.root = saved.root;
|
|
1734
|
+
}
|
|
1735
|
+
return rules as this;
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
// Separate declarations (whose dynamic names might depend on each other)
|
|
1739
|
+
// from non-declarations (which depend on declaration VALUES, not names,
|
|
1740
|
+
// so retrying during preEval won't help).
|
|
1741
|
+
const isDeclarationType = (n: Node) =>
|
|
1742
|
+
isNode(n, N.VarDeclaration) || isNode(n, N.Declaration);
|
|
1743
|
+
|
|
1744
|
+
const dynamicDeclarations: Node[] = [];
|
|
1745
|
+
const otherDynamic: Node[] = [];
|
|
1746
|
+
for (const node of dynamicNodes) {
|
|
1747
|
+
if (isDeclarationType(node)) {
|
|
1748
|
+
dynamicDeclarations.push(node);
|
|
1749
|
+
} else {
|
|
1750
|
+
otherDynamic.push(node);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// Phase 1: Resolve declarations with dynamic names.
|
|
1755
|
+
// Retry because one declaration's name might depend on another's being registered.
|
|
1756
|
+
const MAX_DECL_RETRIES = 5;
|
|
1757
|
+
let declRetries = 0;
|
|
1758
|
+
const unresolvedDecls: Node[] = [...dynamicDeclarations];
|
|
1759
|
+
|
|
1760
|
+
const resolveDeclarations = (): MaybePromise<void> => {
|
|
1761
|
+
declRetries++;
|
|
1762
|
+
if (declRetries > MAX_DECL_RETRIES || unresolvedDecls.length === 0) {
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const stillUnresolved: Node[] = [];
|
|
1766
|
+
let madeProgress = false;
|
|
1767
|
+
|
|
1768
|
+
for (let i = 0; i < unresolvedDecls.length; i++) {
|
|
1769
|
+
const node = unresolvedDecls[i]!;
|
|
1770
|
+
try {
|
|
1771
|
+
const result = node.preEval(context);
|
|
1772
|
+
|
|
1773
|
+
if (isThenable(result)) {
|
|
1774
|
+
const remaining = unresolvedDecls.slice(i + 1);
|
|
1775
|
+
return (result as Promise<Node>).then((resolvedNode) => {
|
|
1776
|
+
if (handleResolvedNode(resolvedNode, node, stillUnresolved)) {
|
|
1777
|
+
madeProgress = true;
|
|
1778
|
+
}
|
|
1779
|
+
unresolvedDecls.length = 0;
|
|
1780
|
+
unresolvedDecls.push(...stillUnresolved, ...remaining);
|
|
1781
|
+
if (madeProgress && unresolvedDecls.length > 0) {
|
|
1782
|
+
return resolveDeclarations();
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
if (handleResolvedNode(result as Node, node, stillUnresolved)) {
|
|
1788
|
+
madeProgress = true;
|
|
1789
|
+
}
|
|
1790
|
+
} catch {
|
|
1791
|
+
stillUnresolved.push(node);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
if (madeProgress && stillUnresolved.length > 0) {
|
|
1796
|
+
unresolvedDecls.length = 0;
|
|
1797
|
+
unresolvedDecls.push(...stillUnresolved);
|
|
1798
|
+
return resolveDeclarations();
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
// Phase 2: Try non-declarations once. Their interpolated names typically
|
|
1803
|
+
// depend on declaration VALUES (e.g. @infix from breakpoint-infix()),
|
|
1804
|
+
// which aren't evaluated until the eval phase. Retrying won't help.
|
|
1805
|
+
const resolveOtherOnce = (): MaybePromise<void> => {
|
|
1806
|
+
for (let i = 0; i < otherDynamic.length; i++) {
|
|
1807
|
+
const node = otherDynamic[i]!;
|
|
1808
|
+
try {
|
|
1809
|
+
const result = node.preEval(context);
|
|
1810
|
+
|
|
1811
|
+
if (isThenable(result)) {
|
|
1812
|
+
const remaining = otherDynamic.slice(i + 1);
|
|
1813
|
+
return (result as Promise<Node>).then((resolvedNode) => {
|
|
1814
|
+
handleResolvedNode(resolvedNode, node, []);
|
|
1815
|
+
// Continue with remaining nodes
|
|
1816
|
+
otherDynamic.length = 0;
|
|
1817
|
+
otherDynamic.push(...remaining);
|
|
1818
|
+
return resolveOtherOnce();
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
handleResolvedNode(result as Node, node, []);
|
|
1823
|
+
} catch {
|
|
1824
|
+
// Can't resolve during preEval — leave in place for eval phase
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
return pipe(
|
|
1830
|
+
() => resolveDeclarations(),
|
|
1831
|
+
() => {
|
|
1832
|
+
applyResolvedNodes();
|
|
1833
|
+
return resolveOtherOnce();
|
|
1834
|
+
},
|
|
1835
|
+
() => finishResolution()
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/**
|
|
1840
|
+
* Helper method to continue preEval'ing remaining children after an async preEval.
|
|
1841
|
+
*/
|
|
1842
|
+
private _preEvalRemainingChildren(rules: Rules, context: Context, startIndex: number, saved?: any): MaybePromise<this> {
|
|
1843
|
+
const children = rules._getRenderChildren(context);
|
|
1844
|
+
for (let i = startIndex; i < children.length; i++) {
|
|
1845
|
+
const node = children[i]!;
|
|
1846
|
+
|
|
1847
|
+
// Always call preEval to ensure deep traversal and name resolution
|
|
1848
|
+
const result = node.preEval(context);
|
|
1849
|
+
if (isThenable(result)) {
|
|
1850
|
+
// Handle async preEval by returning a promise that resolves after all children
|
|
1851
|
+
return result.then((resolvedNode) => {
|
|
1852
|
+
// Update the node if preEval returned a different instance
|
|
1853
|
+
if (resolvedNode !== node) {
|
|
1854
|
+
rules._setChildAt(i, resolvedNode, context, false);
|
|
1855
|
+
rules.adopt(resolvedNode, context);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// Register the node after preEval (name resolution) if not already registered
|
|
1859
|
+
if (!isNode(node, N.VarDeclaration)) {
|
|
1860
|
+
rules.registerNode(resolvedNode, undefined, context);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// Continue with the rest of the children
|
|
1864
|
+
return this._preEvalRemainingChildren(rules, context, i + 1, saved);
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Update the node if preEval returned a different instance
|
|
1869
|
+
if (result !== node) {
|
|
1870
|
+
rules._setChildAt(i, result, context, false);
|
|
1871
|
+
rules.adopt(result, context);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Register the node after preEval (name resolution) if not already registered
|
|
1875
|
+
if (!isNode(node, N.VarDeclaration)) {
|
|
1876
|
+
rules.registerNode(result, undefined, context);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// Restore context after preEval is complete (for async case)
|
|
1881
|
+
if (saved) {
|
|
1882
|
+
context.rulesContext = saved.rulesContext;
|
|
1883
|
+
context.renderKey = saved.renderKey;
|
|
1884
|
+
context.treeRoot = saved.treeRoot;
|
|
1885
|
+
// Only restore context.root if saved.root is defined (not the outermost root)
|
|
1886
|
+
// If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
|
|
1887
|
+
if (saved.root !== undefined) {
|
|
1888
|
+
context.root = saved.root;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return rules as this;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/** Save current context roots to restore later */
|
|
1895
|
+
private _snapshotContext(context: Context) {
|
|
1896
|
+
return {
|
|
1897
|
+
rulesContext: context.rulesContext,
|
|
1898
|
+
renderKey: context.renderKey,
|
|
1899
|
+
treeContext: context.treeContext,
|
|
1900
|
+
treeRoot: context.treeRoot,
|
|
1901
|
+
root: context.root,
|
|
1902
|
+
extendRootStackLength: context.extendRoots.extendRootStack.length
|
|
1903
|
+
} as const;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
/** Setup context for evaluating these rules */
|
|
1907
|
+
private _setupContextForRules(context: Context, rules: Rules) {
|
|
1908
|
+
const treeContext = context.treeContext;
|
|
1909
|
+
// Only switch treeContext if the rules have one AND it's different
|
|
1910
|
+
// Dynamically created Rules (e.g., mixin parameter wrappers) may not have treeContext
|
|
1911
|
+
// and we don't want to lose leakyRules and other settings
|
|
1912
|
+
// Check _meta.treeContext (private field) not treeContext (getter that lazily creates)
|
|
1913
|
+
const rulesTreeContext = (rules as any)._meta?.treeContext as TreeContext | undefined;
|
|
1914
|
+
if (rulesTreeContext && (!treeContext || treeContext !== rulesTreeContext)) {
|
|
1915
|
+
context.allRoots.push(rules);
|
|
1916
|
+
context.treeContext = rulesTreeContext;
|
|
1917
|
+
context.treeRoot = rules;
|
|
1918
|
+
}
|
|
1919
|
+
// Always set root if not set - needed for extends to work with API-created Rules
|
|
1920
|
+
context.root ??= rules;
|
|
1921
|
+
context.rulesContext = rules;
|
|
1922
|
+
if (rules.renderKey !== CANONICAL) {
|
|
1923
|
+
context.renderKey = rules.renderKey;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/** Assign depth-first document order to every Ruleset under the given Rules (single walk, source order). */
|
|
1928
|
+
private _assignDocumentOrderDepthFirst(
|
|
1929
|
+
rules: Rules,
|
|
1930
|
+
map: WeakMap<Ruleset, number>,
|
|
1931
|
+
counter: { value: number },
|
|
1932
|
+
context?: Context
|
|
1933
|
+
): void {
|
|
1934
|
+
const value = rules._getRenderChildren(context);
|
|
1935
|
+
if (!isArray(value)) {
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
for (const node of value) {
|
|
1939
|
+
if (isNode(node, N.Ruleset)) {
|
|
1940
|
+
map.set(node as Ruleset, counter.value);
|
|
1941
|
+
counter.value++;
|
|
1942
|
+
}
|
|
1943
|
+
const innerRules = (node as any).rules;
|
|
1944
|
+
if (innerRules && isNode(innerRules, N.Rules)) {
|
|
1945
|
+
this._assignDocumentOrderDepthFirst(innerRules as Rules, map, counter, context);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/** Build the evaluation queue partitioned by priority */
|
|
1951
|
+
private _buildEvalQueue(rules: Rules, context: Context): EvalQueueMap {
|
|
1952
|
+
let evalQueue: EvalQueueMap = new Map();
|
|
1953
|
+
for (const item of rules._getRenderChildren(context).entries()) {
|
|
1954
|
+
let [idx, rule] = item;
|
|
1955
|
+
if (rule.index === undefined) {
|
|
1956
|
+
rule.index = idx;
|
|
1957
|
+
}
|
|
1958
|
+
let priority = NodeTypeToPriority.get(rule.type) ?? Priority.None;
|
|
1959
|
+
// Less variable-calls `@foo();` are parsed as Expression(Call(variable-ref)).
|
|
1960
|
+
// We *selectively* boost only those calls that "unlock mixins" (i.e. calling a variable whose
|
|
1961
|
+
// value is a detached ruleset containing mixin definitions). This avoids changing evaluation
|
|
1962
|
+
// order for regular detached rulesets like `@ruleset()` used for property blocks.
|
|
1963
|
+
if (priority === Priority.None && rules.treeContext?.leakyRules === true && isNode(rule, N.Expression)) {
|
|
1964
|
+
const inner = (rule as any).value;
|
|
1965
|
+
if (isNode(inner, N.Call) && isNode((inner as any).name, N.Reference)) {
|
|
1966
|
+
const ref = (inner as any).name;
|
|
1967
|
+
const refType = String(ref?.options?.type ?? '');
|
|
1968
|
+
if (refType === 'variable') {
|
|
1969
|
+
const raw = ref.key;
|
|
1970
|
+
const keyStr = Array.isArray(raw) ? raw.join('') : String(raw?.valueOf?.() ?? raw ?? '');
|
|
1971
|
+
// Only if variable exists and its value is a detached ruleset Mixin with nested Mixin definitions.
|
|
1972
|
+
const decl = rules.find('declaration', keyStr, 'VarDeclaration', { context }) as any;
|
|
1973
|
+
const val = decl?.value;
|
|
1974
|
+
const hasNestedMixinDefinitions =
|
|
1975
|
+
isNode(val, N.Mixin)
|
|
1976
|
+
&& isNode((val as any).rules, N.Rules)
|
|
1977
|
+
&& (val as any).rules._getRenderChildren(context).some((n: any) => n?.type === 'Mixin');
|
|
1978
|
+
if (hasNestedMixinDefinitions) {
|
|
1979
|
+
priority = Priority.High;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
let queue = evalQueue.get(priority) ?? [];
|
|
1985
|
+
queue.push(item as [number, Node]);
|
|
1986
|
+
evalQueue.set(priority, queue);
|
|
1987
|
+
}
|
|
1988
|
+
return evalQueue;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
/** Evaluate the built queues in priority order */
|
|
1992
|
+
private _evaluateQueue(rules: Rules, evalQueue: EvalQueueMap, context: Context): MaybePromise<boolean> {
|
|
1993
|
+
let rulesToHoist = false;
|
|
1994
|
+
const scheduledPriority = new WeakMap<Node, Priority>();
|
|
1995
|
+
const failuresByPriority = new WeakMap<Node, Map<Priority, number>>();
|
|
1996
|
+
|
|
1997
|
+
const priorities: Priority[] = Array.from({ length: Priority.Highest + 1 }).map((_, i) => (Priority.Highest - i) as Priority);
|
|
1998
|
+
const runPriority = (p: Priority): MaybePromise<void> => {
|
|
1999
|
+
const queue = evalQueue.get(p);
|
|
2000
|
+
if (!queue) {
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const enqueueRetry = (priority: Priority, item: [number, Node], rule: Node): void => {
|
|
2004
|
+
const retryQueue = evalQueue.get(priority) ?? [];
|
|
2005
|
+
retryQueue.push(item);
|
|
2006
|
+
evalQueue.set(priority, retryQueue);
|
|
2007
|
+
scheduledPriority.set(rule, priority);
|
|
2008
|
+
};
|
|
2009
|
+
const countFailure = (rule: Node, priority: Priority): number => {
|
|
2010
|
+
const byPriority = failuresByPriority.get(rule) ?? new Map<Priority, number>();
|
|
2011
|
+
const nextCount = (byPriority.get(priority) ?? 0) + 1;
|
|
2012
|
+
byPriority.set(priority, nextCount);
|
|
2013
|
+
failuresByPriority.set(rule, byPriority);
|
|
2014
|
+
return nextCount;
|
|
2015
|
+
};
|
|
2016
|
+
const runSingleEntry = (q: number): MaybePromise<void | undefined> => {
|
|
2017
|
+
const [idx, rule] = queue[q]!;
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* Var declarations have late evaluation, so they are skipped.
|
|
2021
|
+
* (Meaning: they are not evaluated until they are referenced.)
|
|
2022
|
+
*/
|
|
2023
|
+
if (isNode(rule, N.VarDeclaration)) {
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// Skip stale entries for nodes that were re-queued to a different priority.
|
|
2028
|
+
const expectedPriority = scheduledPriority.get(rule);
|
|
2029
|
+
if (expectedPriority !== undefined && expectedPriority !== p) {
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
const onEvalError = (error: unknown): Node | undefined => {
|
|
2034
|
+
// Most node failures are semantic failures and should throw immediately.
|
|
2035
|
+
// Retry scheduling is reserved for StyleImport ordering/interpolation cases.
|
|
2036
|
+
if (rule.type !== 'StyleImport') {
|
|
2037
|
+
throw error;
|
|
2038
|
+
}
|
|
2039
|
+
// Final pass: no retries remain.
|
|
2040
|
+
if (p === Priority.None) {
|
|
2041
|
+
throw error;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// Only retry when the import path itself couldn't be resolved
|
|
2045
|
+
// (e.g. @import "@{theme}/file" where @theme isn't available yet).
|
|
2046
|
+
// Path resolution is cheap (no cloning). Content evaluation errors
|
|
2047
|
+
// (after cloning the import tree) are never retried — each retry
|
|
2048
|
+
// would re-clone the entire tree, causing memory blowup.
|
|
2049
|
+
const isPathError = error instanceof Error && (error as any)._isPathResolutionError;
|
|
2050
|
+
if (!isPathError) {
|
|
2051
|
+
throw error;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// Retry policy:
|
|
2055
|
+
// 1) first failure at a priority -> retry once at same priority
|
|
2056
|
+
// 2) second+ failure at that priority -> step down one level
|
|
2057
|
+
const failures = countFailure(rule, p);
|
|
2058
|
+
const nextPriority = failures === 1 ? p : (p - 1) as Priority;
|
|
2059
|
+
enqueueRetry(nextPriority, [idx, rule], rule);
|
|
2060
|
+
return;
|
|
2061
|
+
};
|
|
2062
|
+
const tryStepResult = (): MaybePromise<Node | undefined> => {
|
|
2063
|
+
try {
|
|
2064
|
+
const result = rule.eval(context);
|
|
2065
|
+
if (isThenable(result)) {
|
|
2066
|
+
return (result as Promise<Node>).catch(onEvalError);
|
|
2067
|
+
}
|
|
2068
|
+
return result as Node;
|
|
2069
|
+
} catch (error) {
|
|
2070
|
+
return onEvalError(error);
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
const stepResult = pipe(
|
|
2074
|
+
tryStepResult,
|
|
2075
|
+
(result: Node | undefined) => {
|
|
2076
|
+
// Undefined means we re-queued this node for retry.
|
|
2077
|
+
if (result === undefined) {
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
scheduledPriority.delete(rule);
|
|
2081
|
+
// Apply the result
|
|
2082
|
+
if (result !== rule) {
|
|
2083
|
+
rules._setChildAt(idx, result, context, false);
|
|
2084
|
+
queue[q] = [idx, result];
|
|
2085
|
+
// If a StyleImport evaluated to Rules, register them in the parent's _rulesSet
|
|
2086
|
+
// so variables from the import can be found by the parent
|
|
2087
|
+
// Also register Rules from Call results (mixin calls) in the same way
|
|
2088
|
+
if (isNode(result, N.Rules)) {
|
|
2089
|
+
// Set the index of the imported Rules to the StyleImport's index
|
|
2090
|
+
// so we can compare Rules indices when determining which variable was declared later
|
|
2091
|
+
setIndex(result, idx, context);
|
|
2092
|
+
rules.adopt(result, context);
|
|
2093
|
+
rules.registerNode(result, {
|
|
2094
|
+
rulesVisibility: result.options.rulesVisibility,
|
|
2095
|
+
readonly: result.options.readonly
|
|
2096
|
+
}, context);
|
|
2097
|
+
if (result.sourceNode?.type === 'StyleImport') {
|
|
2098
|
+
result.getRegistry('declaration')?.indexPendingItems();
|
|
2099
|
+
result.getRegistry('mixin')?.indexPendingItems();
|
|
2100
|
+
}
|
|
2101
|
+
} else {
|
|
2102
|
+
// For non-Rules results, adopt them to set up parent chain
|
|
2103
|
+
rules.adopt(result, context);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (result.hoistToRoot) {
|
|
2107
|
+
rulesToHoist = true;
|
|
2108
|
+
}
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
);
|
|
2112
|
+
// If stepResult is a thenable, propagate any errors
|
|
2113
|
+
if (isThenable(stepResult)) {
|
|
2114
|
+
return stepResult;
|
|
2115
|
+
}
|
|
2116
|
+
return;
|
|
2117
|
+
};
|
|
2118
|
+
const runFromIndex = (q: number): MaybePromise<void> => {
|
|
2119
|
+
if (q >= queue.length) {
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
const step = runSingleEntry(q);
|
|
2123
|
+
if (isThenable(step)) {
|
|
2124
|
+
return (step as Promise<void>).then(() => runFromIndex(q + 1));
|
|
2125
|
+
}
|
|
2126
|
+
return runFromIndex(q + 1);
|
|
2127
|
+
};
|
|
2128
|
+
return runFromIndex(0);
|
|
2129
|
+
};
|
|
2130
|
+
const phaseRun = serialForEach(priorities, runPriority);
|
|
2131
|
+
|
|
2132
|
+
if (isThenable(phaseRun)) {
|
|
2133
|
+
return (phaseRun as Promise<void>).then(() => {
|
|
2134
|
+
return rulesToHoist;
|
|
2135
|
+
}).catch((error) => {
|
|
2136
|
+
throw error;
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
return rulesToHoist;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
/**
|
|
2143
|
+
* Coalesce assignment-normalized declaration chains in one stage after evaluation.
|
|
2144
|
+
* This handles both in-scope merges and merges that span call-produced Rules blocks.
|
|
2145
|
+
*/
|
|
2146
|
+
private _coalesceMergedDeclarations(rules: Rules, context?: Context): void {
|
|
2147
|
+
const getDeclValue = (node: Declaration): Node => node.getCurrentValue(context);
|
|
2148
|
+
const getDeclImportant = (node: Declaration): Node | undefined => node.getCurrentImportant(context);
|
|
2149
|
+
const getDeclName = (node: Declaration): string => {
|
|
2150
|
+
const name = node.getCurrentName(context);
|
|
2151
|
+
return String(name?.valueOf?.() ?? name);
|
|
2152
|
+
};
|
|
2153
|
+
const getDeclAssign = (node: Declaration): string => {
|
|
2154
|
+
const options = node.options;
|
|
2155
|
+
return String(options?.normalizedFromAssign ?? '');
|
|
2156
|
+
};
|
|
2157
|
+
const setDeclValue = (node: Declaration, value: Node): void => {
|
|
2158
|
+
node.setCurrentValue(value, context);
|
|
2159
|
+
};
|
|
2160
|
+
const setDeclImportant = (node: Declaration, value: Node | undefined): void => {
|
|
2161
|
+
node.setCurrentImportant(value as Declaration['important'], context);
|
|
2162
|
+
};
|
|
2163
|
+
const removeVisibleFlag = (node: Node): void => {
|
|
2164
|
+
if (context) {
|
|
2165
|
+
node._removeFlag(F_VISIBLE, context);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
node.removeFlag(F_VISIBLE);
|
|
2169
|
+
};
|
|
2170
|
+
const isMergedAssign = (assign: unknown): boolean => (
|
|
2171
|
+
assign === '+:' || assign === '&,:' || assign === '&_:'
|
|
2172
|
+
);
|
|
2173
|
+
const cloneMergeValuePart = (value: Node): Node => {
|
|
2174
|
+
return context
|
|
2175
|
+
? value.clone(false, undefined, context)
|
|
2176
|
+
: value.clone(false);
|
|
2177
|
+
};
|
|
2178
|
+
const collectAddMergeParts = (value: Node): Node[] => {
|
|
2179
|
+
if (isNode(value, N.Nil)) {
|
|
2180
|
+
return [];
|
|
2181
|
+
}
|
|
2182
|
+
if (isNode(value, N.List)) {
|
|
2183
|
+
return value.get('value', context).map(part => cloneMergeValuePart(part as Node));
|
|
2184
|
+
}
|
|
2185
|
+
return [cloneMergeValuePart(value)];
|
|
2186
|
+
};
|
|
2187
|
+
const rebaseLinearMergedValue = (anchor: Declaration, value: Node, assign: string): Node | undefined => {
|
|
2188
|
+
if (assign === '&_:') {
|
|
2189
|
+
if (!isNode(value, N.Sequence)) {
|
|
2190
|
+
return undefined;
|
|
2191
|
+
}
|
|
2192
|
+
const parts = value.get('value', context);
|
|
2193
|
+
if (
|
|
2194
|
+
parts.length < 2
|
|
2195
|
+
|| !isNode(parts[0]!, N.Reference)
|
|
2196
|
+
|| parts[0]!.options?.resolution !== 'linear'
|
|
2197
|
+
) {
|
|
2198
|
+
return undefined;
|
|
2199
|
+
}
|
|
2200
|
+
return spaced([
|
|
2201
|
+
getDeclValue(anchor),
|
|
2202
|
+
...parts.slice(1).map(part => cloneMergeValuePart(part as Node))
|
|
2203
|
+
]);
|
|
2204
|
+
}
|
|
2205
|
+
if (!isNode(value, N.List)) {
|
|
2206
|
+
return undefined;
|
|
2207
|
+
}
|
|
2208
|
+
const parts = value.get('value', context);
|
|
2209
|
+
if (
|
|
2210
|
+
parts.length < 2
|
|
2211
|
+
|| !isNode(parts[0]!, N.Reference)
|
|
2212
|
+
|| parts[0]!.options?.resolution !== 'linear'
|
|
2213
|
+
) {
|
|
2214
|
+
return undefined;
|
|
2215
|
+
}
|
|
2216
|
+
return new List([
|
|
2217
|
+
getDeclValue(anchor),
|
|
2218
|
+
...parts.slice(1).map(part => cloneMergeValuePart(part as Node))
|
|
2219
|
+
]);
|
|
2220
|
+
};
|
|
2221
|
+
const appendMergedValue = (anchor: Declaration, value: Node, assign: string): Node => {
|
|
2222
|
+
if (assign === '&_:') {
|
|
2223
|
+
return isNode(value, N.Sequence)
|
|
2224
|
+
? spaced([getDeclValue(anchor), ...value.get('value', context).map(part => cloneMergeValuePart(part as Node))])
|
|
2225
|
+
: spaced([getDeclValue(anchor), cloneMergeValuePart(value)]);
|
|
2226
|
+
}
|
|
2227
|
+
return isNode(value, N.List)
|
|
2228
|
+
? new List([
|
|
2229
|
+
...collectAddMergeParts(getDeclValue(anchor)),
|
|
2230
|
+
...value.get('value', context).map(part => cloneMergeValuePart(part as Node))
|
|
2231
|
+
])
|
|
2232
|
+
: new List([
|
|
2233
|
+
...collectAddMergeParts(getDeclValue(anchor)),
|
|
2234
|
+
cloneMergeValuePart(value)
|
|
2235
|
+
]);
|
|
2236
|
+
};
|
|
2237
|
+
const isDeclarationOnlyRules = (node: Node): node is Rules => (
|
|
2238
|
+
isNode(node, N.Rules)
|
|
2239
|
+
&& node._getRenderChildren(context).length > 0
|
|
2240
|
+
&& node._getRenderChildren(context).every(child => isNode(child, N.Declaration | N.Comment))
|
|
2241
|
+
);
|
|
2242
|
+
const composeMergedValue = (decl: Declaration, prior: Declaration, assign: string): void => {
|
|
2243
|
+
if (!isNode(decl, N.Declaration) || !isNode(prior, N.Declaration)) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
const priorValue = getDeclValue(prior);
|
|
2247
|
+
const nextValue = getDeclValue(decl);
|
|
2248
|
+
setDeclValue(decl, assign === '&_:'
|
|
2249
|
+
? spaced([priorValue, nextValue])
|
|
2250
|
+
: new List([priorValue, nextValue]));
|
|
2251
|
+
if (!getDeclImportant(decl) && getDeclImportant(prior)) {
|
|
2252
|
+
setDeclImportant(decl, getDeclImportant(prior));
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
const normalizeMergedDeclarationValue = (node: Node): void => {
|
|
2256
|
+
if (!isNode(node, N.Declaration)) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const current = getDeclValue(node);
|
|
2260
|
+
if (!isNode(current, N.List) || current.get('value').length === 0) {
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
const [first, ...rest] = current.get('value');
|
|
2264
|
+
const isEmptyPlaceholder = Boolean(
|
|
2265
|
+
first
|
|
2266
|
+
&& (
|
|
2267
|
+
isNode(first, N.Nil)
|
|
2268
|
+
|| (isNode(first, N.List) && first.get('value').length === 0)
|
|
2269
|
+
)
|
|
2270
|
+
);
|
|
2271
|
+
if (!isEmptyPlaceholder) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
if (rest.length === 0) {
|
|
2275
|
+
setDeclValue(node, new Nil());
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (rest.length === 1) {
|
|
2279
|
+
setDeclValue(node, rest[0]!);
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
setDeclValue(node, new List(rest));
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
const lastVisibleByName = new Map<string, Node>();
|
|
2286
|
+
const mergedAnchorByName = new Map<string, Node>();
|
|
2287
|
+
const stream: Node[] = [];
|
|
2288
|
+
|
|
2289
|
+
for (const node of rules._getRenderChildren(context)) {
|
|
2290
|
+
if (isNode(node, N.Declaration)) {
|
|
2291
|
+
stream.push(node);
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
if (isDeclarationOnlyRules(node)) {
|
|
2295
|
+
for (const child of node._getRenderChildren(context)) {
|
|
2296
|
+
if (isNode(child, N.Declaration)) {
|
|
2297
|
+
stream.push(child);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
for (const node of stream) {
|
|
2304
|
+
if (!isNode(node, N.Declaration)) {
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
const name = getDeclName(node);
|
|
2308
|
+
const assign = getDeclAssign(node);
|
|
2309
|
+
const merged = isMergedAssign(assign);
|
|
2310
|
+
|
|
2311
|
+
if (!merged) {
|
|
2312
|
+
mergedAnchorByName.delete(name);
|
|
2313
|
+
if (isVisibleInContext(node, context)) {
|
|
2314
|
+
lastVisibleByName.set(name, node);
|
|
2315
|
+
}
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
normalizeMergedDeclarationValue(node);
|
|
2319
|
+
|
|
2320
|
+
const prior = lastVisibleByName.get(name);
|
|
2321
|
+
if (
|
|
2322
|
+
prior
|
|
2323
|
+
&& prior !== node
|
|
2324
|
+
&& (
|
|
2325
|
+
context
|
|
2326
|
+
? getCurrentParentNode(prior, context) !== getCurrentParentNode(node, context)
|
|
2327
|
+
: prior.parent !== node.parent
|
|
2328
|
+
)
|
|
2329
|
+
) {
|
|
2330
|
+
composeMergedValue(node, prior, assign);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
const existingAnchor = mergedAnchorByName.get(name);
|
|
2334
|
+
if (existingAnchor && existingAnchor !== node && isNode(existingAnchor, N.Declaration)) {
|
|
2335
|
+
// @todo — copy(true) was used here for comment suppression (stripping
|
|
2336
|
+
// pre/post comments from merged values). Need a position-aware
|
|
2337
|
+
// alternative: either a serialization-time comment suppression flag
|
|
2338
|
+
// or field patches on pre/post.
|
|
2339
|
+
const currentValue = getDeclValue(node);
|
|
2340
|
+
const nextAnchorValue = assign === '+:'
|
|
2341
|
+
? new List([
|
|
2342
|
+
...collectAddMergeParts(getDeclValue(existingAnchor)),
|
|
2343
|
+
...collectAddMergeParts(currentValue)
|
|
2344
|
+
])
|
|
2345
|
+
: rebaseLinearMergedValue(existingAnchor, currentValue, assign)
|
|
2346
|
+
?? appendMergedValue(existingAnchor, currentValue, assign);
|
|
2347
|
+
setDeclValue(
|
|
2348
|
+
existingAnchor,
|
|
2349
|
+
nextAnchorValue
|
|
2350
|
+
);
|
|
2351
|
+
if (!getDeclImportant(existingAnchor) && getDeclImportant(node)) {
|
|
2352
|
+
setDeclImportant(existingAnchor, getDeclImportant(node));
|
|
2353
|
+
}
|
|
2354
|
+
removeVisibleFlag(node);
|
|
2355
|
+
if (isVisibleInContext(existingAnchor, context)) {
|
|
2356
|
+
lastVisibleByName.set(name, existingAnchor);
|
|
2357
|
+
}
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
mergedAnchorByName.set(name, node);
|
|
2362
|
+
if (isVisibleInContext(node, context)) {
|
|
2363
|
+
lastVisibleByName.set(name, node);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
/**
|
|
2369
|
+
* Normalize call-produced declaration-only Rules ordering so declarations
|
|
2370
|
+
* emitted from late-evaluated calls (e.g. each/$for) appear before nested
|
|
2371
|
+
* rulesets/at-rules in the same parent Rules container.
|
|
2372
|
+
*
|
|
2373
|
+
* This runs after queue evaluation to avoid mutating rule indices mid-eval.
|
|
2374
|
+
*/
|
|
2375
|
+
private _normalizeCallDeclarationRulesOrder(rules: Rules, context?: Context): void {
|
|
2376
|
+
const children = rules._getRenderChildren(context);
|
|
2377
|
+
const firstNestedIdx = children.findIndex(n => isNode(n, N.Ruleset | N.AtRule));
|
|
2378
|
+
if (firstNestedIdx < 0) {
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
const beforeNested = children.slice(0, firstNestedIdx);
|
|
2382
|
+
const afterNested = children.slice(firstNestedIdx);
|
|
2383
|
+
const shouldMove = (n: Node) => {
|
|
2384
|
+
const sourceParent = context
|
|
2385
|
+
? getSourceParent(n, context)
|
|
2386
|
+
: n.sourceParent;
|
|
2387
|
+
if (
|
|
2388
|
+
!isNode(n, N.Rules)
|
|
2389
|
+
|| !isNode(sourceParent, N.Call)
|
|
2390
|
+
|| n._getRenderChildren(context).length === 0
|
|
2391
|
+
|| !n._getRenderChildren(context).every(child => isNode(child, N.Declaration | N.Comment))
|
|
2392
|
+
) {
|
|
2393
|
+
return false;
|
|
2394
|
+
}
|
|
2395
|
+
const sourceName = (sourceParent as any).name;
|
|
2396
|
+
// Keep mixin-call declaration blocks in source order relative to nested rulesets.
|
|
2397
|
+
if (
|
|
2398
|
+
isNode(sourceName, N.Reference)
|
|
2399
|
+
&& (sourceName.options?.type === 'mixin'
|
|
2400
|
+
|| sourceName.options?.type === 'mixin-ruleset'
|
|
2401
|
+
|| sourceName.options?.type === 'ruleset')
|
|
2402
|
+
) {
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
return true;
|
|
2406
|
+
};
|
|
2407
|
+
const moved = afterNested.filter(shouldMove);
|
|
2408
|
+
if (moved.length === 0) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const remainder = afterNested.filter(n => !shouldMove(n));
|
|
2412
|
+
rules._setChildren([...beforeNested, ...moved, ...remainder], context, false);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
private _evaluateQueuedTopImports(context: Context): MaybePromise<void> {
|
|
2416
|
+
const queued = context.topImports;
|
|
2417
|
+
if (!queued?.length) {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
const evaluated: Node[] = [];
|
|
2422
|
+
const evaluateOne = (importRule: Node): MaybePromise<void> => {
|
|
2423
|
+
if (!isNode(importRule, N.AtRule)) {
|
|
2424
|
+
evaluated.push(importRule);
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
const evaldImport = importRule.clone(false, undefined, context);
|
|
2429
|
+
evaldImport.preEvaluated = true;
|
|
2430
|
+
const out = evaldImport.eval(context);
|
|
2431
|
+
if (isThenable(out)) {
|
|
2432
|
+
return (out as Promise<Node | Nil>).then((result) => {
|
|
2433
|
+
if (!(result instanceof Nil)) {
|
|
2434
|
+
evaluated.push(result);
|
|
2435
|
+
}
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
if (!(out instanceof Nil)) {
|
|
2439
|
+
evaluated.push(out as Node);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
|
|
2443
|
+
const out = serialForEach(queued, evaluateOne);
|
|
2444
|
+
if (isThenable(out)) {
|
|
2445
|
+
return (out as Promise<void>).then(() => {
|
|
2446
|
+
context.topImports = evaluated;
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
context.topImports = evaluated;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* After preEval: ensure root on extend stack, build eval queue, run evaluation.
|
|
2454
|
+
* Used by evalNode so that when eval() is called without preEval (e.g. jess compile()),
|
|
2455
|
+
* we still have all rulesets registered and root set for extend lookups.
|
|
2456
|
+
*/
|
|
2457
|
+
private _afterPreEvalStep(rules: Rules, context: Context): MaybePromise<{ rules: Rules; rulesToHoist: boolean }> {
|
|
2458
|
+
if (
|
|
2459
|
+
rules === context.root
|
|
2460
|
+
&& rules.renderKey === CANONICAL
|
|
2461
|
+
&& !rules.hasFlag(F_STATIC)
|
|
2462
|
+
&& (context.renderKey === undefined || context.renderKey === CANONICAL)
|
|
2463
|
+
&& getCurrentParentNode(rules, context) === undefined
|
|
2464
|
+
) {
|
|
2465
|
+
const evalRoot = rules.createShallowBodyWrapper(context, EVAL);
|
|
2466
|
+
context.root = evalRoot;
|
|
2467
|
+
context.rulesContext = evalRoot;
|
|
2468
|
+
context.renderKey = evalRoot.renderKey;
|
|
2469
|
+
rules = evalRoot;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
const isMainRoot = rules === context.root;
|
|
2473
|
+
if (isMainRoot && context.extendRoots.extendRootStack.length === 0) {
|
|
2474
|
+
if (!context.extendRoots.root) {
|
|
2475
|
+
context.extendRoots.registerRoot(rules);
|
|
2476
|
+
}
|
|
2477
|
+
context.extendRoots.pushExtendRoot(rules);
|
|
2478
|
+
}
|
|
2479
|
+
if (isEvaluated(rules, context)) {
|
|
2480
|
+
return { rules, rulesToHoist: false };
|
|
2481
|
+
}
|
|
2482
|
+
if (rules === context.root) {
|
|
2483
|
+
const map = new WeakMap<Ruleset, number>();
|
|
2484
|
+
context.documentOrderByRuleset = map;
|
|
2485
|
+
this._assignDocumentOrderDepthFirst(rules, map, { value: 0 }, context);
|
|
2486
|
+
}
|
|
2487
|
+
const evalQueue = this._buildEvalQueue(rules, context);
|
|
2488
|
+
const maybeHoist = this._evaluateQueue(rules, evalQueue, context);
|
|
2489
|
+
if (isThenable(maybeHoist)) {
|
|
2490
|
+
return (maybeHoist as Promise<boolean>).then((rulesToHoist) => {
|
|
2491
|
+
const finalize = () => {
|
|
2492
|
+
this._normalizeCallDeclarationRulesOrder(rules, context);
|
|
2493
|
+
this._coalesceMergedDeclarations(rules, context);
|
|
2494
|
+
return {
|
|
2495
|
+
rules,
|
|
2496
|
+
rulesToHoist
|
|
2497
|
+
};
|
|
2498
|
+
};
|
|
2499
|
+
if (rules === context.root && context.topImports?.length) {
|
|
2500
|
+
const maybeEvalTopImports = this._evaluateQueuedTopImports(context);
|
|
2501
|
+
if (isThenable(maybeEvalTopImports)) {
|
|
2502
|
+
return (maybeEvalTopImports as Promise<void>).then(finalize);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return finalize();
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
if (rules === context.root && context.topImports?.length) {
|
|
2509
|
+
const maybeEvalTopImports = this._evaluateQueuedTopImports(context);
|
|
2510
|
+
if (isThenable(maybeEvalTopImports)) {
|
|
2511
|
+
return (maybeEvalTopImports as Promise<void>).then(() => {
|
|
2512
|
+
this._normalizeCallDeclarationRulesOrder(rules, context);
|
|
2513
|
+
this._coalesceMergedDeclarations(rules, context);
|
|
2514
|
+
return { rules, rulesToHoist: maybeHoist as boolean };
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
this._normalizeCallDeclarationRulesOrder(rules, context);
|
|
2519
|
+
this._coalesceMergedDeclarations(rules, context);
|
|
2520
|
+
return { rules, rulesToHoist: maybeHoist as boolean };
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
override evalNode(context: Context): MaybePromise<this> {
|
|
2524
|
+
const saved = this._snapshotContext(context);
|
|
2525
|
+
context.rulesEvalStack.push(this.sourceNode as Rules);
|
|
2526
|
+
const restoreContextOnError = () => {
|
|
2527
|
+
context.rulesContext = saved.rulesContext;
|
|
2528
|
+
context.renderKey = saved.renderKey;
|
|
2529
|
+
if (saved.treeRoot !== undefined) {
|
|
2530
|
+
context.treeRoot = saved.treeRoot;
|
|
2531
|
+
}
|
|
2532
|
+
if (saved.root !== undefined) {
|
|
2533
|
+
context.root = saved.root;
|
|
2534
|
+
}
|
|
2535
|
+
const currentLength = context.extendRoots.extendRootStack.length;
|
|
2536
|
+
if (saved.extendRootStackLength !== undefined && currentLength > saved.extendRootStackLength) {
|
|
2537
|
+
while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
|
|
2538
|
+
context.extendRoots.popExtendRoot();
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (context.rulesEvalStack[context.rulesEvalStack.length - 1] === (this.sourceNode as Rules)) {
|
|
2542
|
+
context.rulesEvalStack.pop();
|
|
2543
|
+
}
|
|
2544
|
+
context.depth--;
|
|
2545
|
+
};
|
|
2546
|
+
let pipeResult: MaybePromise<this>;
|
|
2547
|
+
try {
|
|
2548
|
+
pipeResult = pipe(
|
|
2549
|
+
() => {
|
|
2550
|
+
this._setupContextForRules(context, this);
|
|
2551
|
+
// Run preEval first if not yet run (e.g. when jess compile() calls eval() without preEval).
|
|
2552
|
+
// preEval registers the root and all nested rulesets so extend lookups find targets in child roots (e.g. .ma inside @media).
|
|
2553
|
+
const runPreEvalIfNeeded = (rules: Rules): MaybePromise<Rules> => {
|
|
2554
|
+
if (rules.preEvaluated) {
|
|
2555
|
+
return rules;
|
|
2556
|
+
}
|
|
2557
|
+
const result = rules.preEval(context);
|
|
2558
|
+
return isThenable(result) ? (result as Promise<Rules>) : result;
|
|
2559
|
+
};
|
|
2560
|
+
const rulesAfterPreEval = runPreEvalIfNeeded(this);
|
|
2561
|
+
const afterPreEval = (rules: Rules) => {
|
|
2562
|
+
// When we're the outermost Rules, use the tree we're evaling as root (may differ from context.root set in getTree, or be preEval's clone).
|
|
2563
|
+
if (context.rulesEvalStack.length === 1) {
|
|
2564
|
+
context.root = rules;
|
|
2565
|
+
}
|
|
2566
|
+
return this._afterPreEvalStep(rules, context);
|
|
2567
|
+
};
|
|
2568
|
+
if (isThenable(rulesAfterPreEval)) {
|
|
2569
|
+
return (rulesAfterPreEval as Promise<Rules>).then(afterPreEval);
|
|
2570
|
+
}
|
|
2571
|
+
return afterPreEval(rulesAfterPreEval as Rules);
|
|
2572
|
+
},
|
|
2573
|
+
({ rules }: { rules: Rules; rulesToHoist: boolean }) => {
|
|
2574
|
+
// Note: Rulesets from imported Rules are already registered to their own treeRoot
|
|
2575
|
+
// during preEval when the imported Rules node is evaluated. The extend search
|
|
2576
|
+
// loops through allRoots, so it should find them. The _searchRulesChildrenForRulesets
|
|
2577
|
+
// method in RulesetRegistry also searches imported Rules' registries.
|
|
2578
|
+
|
|
2579
|
+
// After all evaluation stages, check if any variables in the current Rules
|
|
2580
|
+
// shadow readonly variables from imported Rules (compose type) at the same level
|
|
2581
|
+
// Only check direct children of the Rules node, not nested variables (e.g., inside rulesets)
|
|
2582
|
+
if (rules.rulesSet.length > 0) {
|
|
2583
|
+
for (const entry of rules.rulesSet) {
|
|
2584
|
+
if (entry.readonly) {
|
|
2585
|
+
const importedVars = Registries
|
|
2586
|
+
.getDirectDeclarationsByKey(entry.node, undefined, context)
|
|
2587
|
+
.filter((decl): decl is VarDeclaration => isNode(decl, N.VarDeclaration));
|
|
2588
|
+
for (const decl of importedVars) {
|
|
2589
|
+
const key = decl.get('name').toString();
|
|
2590
|
+
const currentDeclarations = Registries.getDirectDeclarationsByKey(rules, key, context);
|
|
2591
|
+
for (const currentDecl of currentDeclarations) {
|
|
2592
|
+
if (isNode(currentDecl, N.VarDeclaration) && !currentDecl.options?.setDefined) {
|
|
2593
|
+
// Only throw if the variable is a direct child of the Rules node (same level)
|
|
2594
|
+
// Nested variables (e.g., inside rulesets) are allowed to shadow
|
|
2595
|
+
if (getCurrentParentNode(currentDecl, context) === rules) {
|
|
2596
|
+
throw new ReferenceError(`"${key}" is readonly`);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// Check if we're at the outermost level BEFORE restoring context
|
|
2606
|
+
// Only process extends at the TRUE outermost root (context.root)
|
|
2607
|
+
// This ensures extends are processed AFTER all evaluation completes,
|
|
2608
|
+
// including imports and nested Rules
|
|
2609
|
+
const isOutermost = rules === context.root;
|
|
2610
|
+
|
|
2611
|
+
if (isOutermost) {
|
|
2612
|
+
processExtends(context);
|
|
2613
|
+
}
|
|
2614
|
+
/** Restore contexts */
|
|
2615
|
+
context.rulesContext = saved.rulesContext;
|
|
2616
|
+
context.renderKey = saved.renderKey;
|
|
2617
|
+
// Only restore context.treeRoot if saved.treeRoot is defined and we're not at the outermost level
|
|
2618
|
+
// If saved.treeRoot is undefined, it means we're at the outermost level, so keep context.treeRoot as is
|
|
2619
|
+
// This ensures extends evaluated during selector evaluation can still access the correct treeRoot
|
|
2620
|
+
if (saved.treeRoot !== undefined && !isOutermost) {
|
|
2621
|
+
context.treeRoot = saved.treeRoot;
|
|
2622
|
+
}
|
|
2623
|
+
// Only restore context.root if we're not at the outermost level (where it was originally set)
|
|
2624
|
+
// If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
|
|
2625
|
+
if (saved.root !== undefined && !isOutermost) {
|
|
2626
|
+
context.root = saved.root;
|
|
2627
|
+
}
|
|
2628
|
+
// Restore extend root stack to its original length (if we're not the main root)
|
|
2629
|
+
// The main root manages its own push/pop, but nested Rules should restore the stack
|
|
2630
|
+
if (!isOutermost && saved.extendRootStackLength !== undefined) {
|
|
2631
|
+
const currentLength = context.extendRoots.extendRootStack.length;
|
|
2632
|
+
if (currentLength > saved.extendRootStackLength) {
|
|
2633
|
+
// Pop any extend roots that were pushed during this Rules evaluation
|
|
2634
|
+
while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
|
|
2635
|
+
context.extendRoots.popExtendRoot();
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
// Pop extend root if we pushed it (check if this is still the root)
|
|
2640
|
+
if (rules === context.root) {
|
|
2641
|
+
context.extendRoots.popExtendRoot();
|
|
2642
|
+
}
|
|
2643
|
+
context.rulesEvalStack.pop();
|
|
2644
|
+
context.depth--;
|
|
2645
|
+
return rules;
|
|
2646
|
+
}
|
|
2647
|
+
) as MaybePromise<this>;
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
restoreContextOnError();
|
|
2650
|
+
throw error;
|
|
2651
|
+
}
|
|
2652
|
+
if (isThenable(pipeResult)) {
|
|
2653
|
+
return (pipeResult as Promise<this>).catch((error) => {
|
|
2654
|
+
restoreContextOnError();
|
|
2655
|
+
throw error;
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
return pipeResult as MaybePromise<this>;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
export const rules = defineType(Rules, 'Rules');
|
|
2663
|
+
|
|
2664
|
+
type EvalQueueMap = Map<Priority, Array<[number, Node]>>;
|
|
2665
|
+
|
|
2666
|
+
/**
|
|
2667
|
+
* @todo - Will need lots of massaging, to resolve things like
|
|
2668
|
+
* mixins which rely on variables which have interpolated names,
|
|
2669
|
+
* and variables with interpolated names that rely on mixins.
|
|
2670
|
+
*
|
|
2671
|
+
* @note - Registration of declaration names and mixins / selectors
|
|
2672
|
+
* should have already happened in pre-eval.
|
|
2673
|
+
*/
|
|
2674
|
+
const NodeTypeToPriority = new Map([
|
|
2675
|
+
/** First, resolve imports */
|
|
2676
|
+
['StyleImport', Priority.Highest],
|
|
2677
|
+
/** Then, resolve calls */
|
|
2678
|
+
['Call', Priority.High],
|
|
2679
|
+
/** Then, resolve declarations */
|
|
2680
|
+
['VarDeclaration', Priority.Medium],
|
|
2681
|
+
['Declaration', Priority.Medium],
|
|
2682
|
+
/** Then... */
|
|
2683
|
+
['Mixin', Priority.Low],
|
|
2684
|
+
['Ruleset', Priority.Low],
|
|
2685
|
+
/** Extend should evaluate at the same priority as Ruleset to ensure it evaluates before nested rulesets */
|
|
2686
|
+
['Extend', Priority.Low],
|
|
2687
|
+
/** AtRule (e.g., @media) should evaluate at the same priority as Ruleset to preserve source order */
|
|
2688
|
+
['AtRule', Priority.Low]
|
|
2689
|
+
/** Then, everything else? */
|
|
2690
|
+
]);
|
|
2691
|
+
|
|
2692
|
+
// const TypeToNodeType = new Map([
|
|
2693
|
+
// ['Mixin', NodeType.MIXIN],
|
|
2694
|
+
// ['Ruleset', NodeType.RULESET],
|
|
2695
|
+
// ['Declaration', NodeType.PROPERTY],
|
|
2696
|
+
// ['VarDeclaration', NodeType.VARIABLE],
|
|
2697
|
+
// ['Rules', NodeType.RULES]
|
|
2698
|
+
// ])
|
|
2699
|
+
|
|
2700
|
+
// export const enum NodeTypeIndex {
|
|
2701
|
+
// NONE = 0b000000,
|
|
2702
|
+
// MIXIN = 0b000001,
|
|
2703
|
+
// RULESET = 0b000010,
|
|
2704
|
+
// MIXIN_OR_RULESET = 0b000011,
|
|
2705
|
+
// PROPERTY = 0b000100,
|
|
2706
|
+
// VARIABLE = 0b001000,
|
|
2707
|
+
// VAR_OR_PROP = 0b001100,
|
|
2708
|
+
// /**
|
|
2709
|
+
// * Variables and mixins can leak
|
|
2710
|
+
// */
|
|
2711
|
+
// LEAKY_RULES = 0b010000,
|
|
2712
|
+
// /** @note - Properties and rulesets are always visible. */
|
|
2713
|
+
// PRIVATE_RULES = 0b100000,
|
|
2714
|
+
// RULES = 0b110000
|
|
2715
|
+
// }
|
|
2716
|
+
|
|
2717
|
+
// type IndexKey = `${NodeType}${string}`
|
|
2718
|
+
|
|
2719
|
+
interface RulesEntry {
|
|
2720
|
+
node: Rules;
|
|
2721
|
+
rulesVisibility?: RulesOptions['rulesVisibility'];
|
|
2722
|
+
/**
|
|
2723
|
+
* These are from use, from, and import statements. Can't be assigned with $$
|
|
2724
|
+
* (verify that this is not possible with SCSS).
|
|
2725
|
+
*/
|
|
2726
|
+
readonly?: boolean;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* Right now, the only nodes that can be registered to the scope for lookups
|
|
2731
|
+
*/
|
|
2732
|
+
// type ScopeNodes = Declaration | VarDeclaration | Mixin | Ruleset | Rules
|
|
2733
|
+
export type MixinEntry = Mixin | Ruleset;
|
|
2734
|
+
|
|
2735
|
+
/**
|
|
2736
|
+
* Returns a plain JS function for calling a set of mixins
|
|
2737
|
+
*
|
|
2738
|
+
* This is in the same file as Rules to avoid circular dependencies.
|
|
2739
|
+
*
|
|
2740
|
+
* @note this will be called as a result after a mixin find is executed.
|
|
2741
|
+
*/
|
|
2742
|
+
export function getFunctionFromMixins(mixins: MixinEntry | MixinEntry[]) {
|
|
2743
|
+
let mixinArr = isArray(mixins) ? mixins : [mixins];
|
|
2744
|
+
/**
|
|
2745
|
+
* This will be called by a mixin call or by JavaScript
|
|
2746
|
+
*
|
|
2747
|
+
* @note - Mixins resolve to async functions because they
|
|
2748
|
+
* can contain dynamic imports.
|
|
2749
|
+
*/
|
|
2750
|
+
async function returnFunc(this: unknown, ...args: any[]): Promise<Rules | Record<string, string>>;
|
|
2751
|
+
async function returnFunc(this: Context, ...args: any[]): Promise<Rules>;
|
|
2752
|
+
async function returnFunc(this: Context | unknown, ...args: any[]) {
|
|
2753
|
+
// When called via callWithContext, 'this' is functionThis, not Context
|
|
2754
|
+
// We need to extract the context from functionThis or use a fallback
|
|
2755
|
+
let thisContext: Context;
|
|
2756
|
+
|
|
2757
|
+
if (this instanceof Context) {
|
|
2758
|
+
thisContext = this;
|
|
2759
|
+
} else if (this && typeof this === 'object' && 'context' in this) {
|
|
2760
|
+
// This is functionThis from callWithContext
|
|
2761
|
+
thisContext = (this as any).context;
|
|
2762
|
+
} else {
|
|
2763
|
+
thisContext = new Context();
|
|
2764
|
+
}
|
|
2765
|
+
let caller = thisContext.caller;
|
|
2766
|
+
const callerSourceNode = (caller as any)?.name instanceof Node
|
|
2767
|
+
? (caller as any).name
|
|
2768
|
+
: caller;
|
|
2769
|
+
let sourceParent = callerSourceNode
|
|
2770
|
+
? getSourceParent(callerSourceNode, thisContext)
|
|
2771
|
+
: undefined;
|
|
2772
|
+
if (sourceParent && isNode(sourceParent, N.Reference | N.Call)) {
|
|
2773
|
+
sourceParent = caller;
|
|
2774
|
+
}
|
|
2775
|
+
sourceParent ??= caller;
|
|
2776
|
+
const invocationParent = thisContext.rulesContext
|
|
2777
|
+
?? (caller ? getParent(caller, thisContext) : undefined);
|
|
2778
|
+
|
|
2779
|
+
const nodeArgs = await evaluateMixinArgs(args, caller, thisContext);
|
|
2780
|
+
const mixinCandidates = await matchMixinCandidates(mixinArr, nodeArgs, caller, sourceParent, thisContext);
|
|
2781
|
+
const { evalCandidates, hasDefault } = filterAndSortMixinEvalCandidates(mixinCandidates, thisContext);
|
|
2782
|
+
|
|
2783
|
+
const outputRules: Rules[] = [];
|
|
2784
|
+
const candidateOutputOpts: EvaluateCandidateOutputOptions = {
|
|
2785
|
+
sourceParent,
|
|
2786
|
+
invocationParent,
|
|
2787
|
+
restrictMixinOutputLookup: thisContext.leakyRules !== true,
|
|
2788
|
+
outputRules,
|
|
2789
|
+
getCandidateParent: node => getCandidateParent(node, thisContext)
|
|
2790
|
+
};
|
|
2791
|
+
|
|
2792
|
+
const output = await dispatchMixinEvalCandidates({
|
|
2793
|
+
evalCandidates,
|
|
2794
|
+
hasDefault,
|
|
2795
|
+
nodeArgs,
|
|
2796
|
+
sourceParent,
|
|
2797
|
+
invocationParent,
|
|
2798
|
+
caller,
|
|
2799
|
+
restrictMixinOutputLookup: candidateOutputOpts.restrictMixinOutputLookup,
|
|
2800
|
+
outputRules,
|
|
2801
|
+
getCandidateParent: candidateOutputOpts.getCandidateParent,
|
|
2802
|
+
evaluateCandidateOutput: (candidate, rules, outerRules, params) =>
|
|
2803
|
+
evaluateCandidateOutput(candidate, rules, outerRules, params, thisContext, candidateOutputOpts)
|
|
2804
|
+
}, thisContext);
|
|
2805
|
+
|
|
2806
|
+
return finalizeMixinInvocationReturn(output, this instanceof Context ? this : thisContext);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
return returnFunc;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
/**
|
|
2813
|
+
* Direct mixin invocation — calls dispatch primitives without the
|
|
2814
|
+
* getFunctionFromMixins → callWithContext → returnFunc indirection.
|
|
2815
|
+
*
|
|
2816
|
+
* The result is already fully evaluated (each candidate's body was
|
|
2817
|
+
* evaluated before being assembled into the return Rules). Callers must NOT
|
|
2818
|
+
* re-evaluate the result.
|
|
2819
|
+
*/
|
|
2820
|
+
export async function evalMixinDirect(
|
|
2821
|
+
context: Context,
|
|
2822
|
+
mixins: MixinEntry | MixinEntry[],
|
|
2823
|
+
args: List<Node> | undefined
|
|
2824
|
+
): Promise<Rules | Nil> {
|
|
2825
|
+
const mixinArr = isArray(mixins) ? mixins : [mixins];
|
|
2826
|
+
const caller = context.caller;
|
|
2827
|
+
const callerSourceNode = caller && isNode(caller, N.Call) && caller.get('name') instanceof Node
|
|
2828
|
+
? caller.get('name')
|
|
2829
|
+
: caller;
|
|
2830
|
+
let sourceParent = callerSourceNode
|
|
2831
|
+
? getSourceParent(callerSourceNode as Node, context)
|
|
2832
|
+
: undefined;
|
|
2833
|
+
if (sourceParent && isNode(sourceParent, N.Reference | N.Call)) {
|
|
2834
|
+
sourceParent = caller;
|
|
2835
|
+
}
|
|
2836
|
+
sourceParent ??= caller;
|
|
2837
|
+
const invocationParent = context.rulesContext
|
|
2838
|
+
?? (caller ? getParent(caller as Node, context) : undefined);
|
|
2839
|
+
|
|
2840
|
+
const nodeArgs = await evaluateMixinArgs(
|
|
2841
|
+
args ? [...args.get('value', context)] : [],
|
|
2842
|
+
caller,
|
|
2843
|
+
context
|
|
2844
|
+
);
|
|
2845
|
+
const mixinCandidates = await matchMixinCandidates(
|
|
2846
|
+
mixinArr, nodeArgs, caller, sourceParent, context
|
|
2847
|
+
);
|
|
2848
|
+
const { evalCandidates, hasDefault } = filterAndSortMixinEvalCandidates(
|
|
2849
|
+
mixinCandidates, context
|
|
2850
|
+
);
|
|
2851
|
+
|
|
2852
|
+
const outputRules: Rules[] = [];
|
|
2853
|
+
if (process.env.JESS_DEBUG_LOCK === 'throw-direct') {
|
|
2854
|
+
const callerName = caller && isNode(caller, N.Call) && (caller as Call).name instanceof Node
|
|
2855
|
+
? (caller as Call).name
|
|
2856
|
+
: undefined;
|
|
2857
|
+
const callerKey = isNode(callerName, N.Reference)
|
|
2858
|
+
? String(callerName.key?.valueOf?.() ?? '')
|
|
2859
|
+
: '';
|
|
2860
|
+
if (callerKey.includes('inner-locked-mixin')) {
|
|
2861
|
+
throw new Error(`[lock-direct] ${JSON.stringify({
|
|
2862
|
+
callerKey,
|
|
2863
|
+
mixinCount: mixinArr.length,
|
|
2864
|
+
matchedCount: mixinCandidates.length,
|
|
2865
|
+
evalCandidateCount: evalCandidates.length,
|
|
2866
|
+
hasDefault,
|
|
2867
|
+
sourceParent: sourceParent?.type,
|
|
2868
|
+
invocationParent: invocationParent?.type,
|
|
2869
|
+
matchNames: mixinCandidates.map(candidate => String((candidate as Mixin).get?.('name')?.valueOf?.() ?? candidate.type))
|
|
2870
|
+
})}`);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
const candidateOutputOpts: EvaluateCandidateOutputOptions = {
|
|
2874
|
+
sourceParent,
|
|
2875
|
+
invocationParent,
|
|
2876
|
+
restrictMixinOutputLookup: context.leakyRules !== true,
|
|
2877
|
+
outputRules,
|
|
2878
|
+
getCandidateParent: node => getCandidateParent(node, context)
|
|
2879
|
+
};
|
|
2880
|
+
|
|
2881
|
+
const output = await dispatchMixinEvalCandidates({
|
|
2882
|
+
evalCandidates,
|
|
2883
|
+
hasDefault,
|
|
2884
|
+
nodeArgs,
|
|
2885
|
+
sourceParent,
|
|
2886
|
+
invocationParent,
|
|
2887
|
+
caller,
|
|
2888
|
+
restrictMixinOutputLookup: candidateOutputOpts.restrictMixinOutputLookup,
|
|
2889
|
+
outputRules,
|
|
2890
|
+
getCandidateParent: candidateOutputOpts.getCandidateParent,
|
|
2891
|
+
evaluateCandidateOutput: (candidate, rules, outerRules, params) =>
|
|
2892
|
+
evaluateCandidateOutput(candidate, rules, outerRules, params, context, candidateOutputOpts)
|
|
2893
|
+
}, context);
|
|
2894
|
+
|
|
2895
|
+
return finalizeMixinInvocationReturn(output, context) as Rules | Nil;
|
|
2896
|
+
}
|