@jesscss/core 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/lib/context.d.ts +352 -0
- package/lib/context.d.ts.map +1 -0
- package/lib/context.js +636 -0
- package/lib/context.js.map +1 -0
- package/lib/conversions.d.ts +73 -0
- package/lib/conversions.d.ts.map +1 -0
- package/lib/conversions.js +253 -0
- package/lib/conversions.js.map +1 -0
- package/lib/debug-log.d.ts +2 -0
- package/lib/debug-log.d.ts.map +1 -0
- package/lib/debug-log.js +27 -0
- package/lib/debug-log.js.map +1 -0
- package/lib/define-function.d.ts +587 -0
- package/lib/define-function.d.ts.map +1 -0
- package/lib/define-function.js +726 -0
- package/lib/define-function.js.map +1 -0
- package/lib/deprecation.d.ts +34 -0
- package/lib/deprecation.d.ts.map +1 -0
- package/lib/deprecation.js +57 -0
- package/lib/deprecation.js.map +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +23 -0
- package/lib/index.js.map +1 -0
- package/lib/jess-error.d.ts +343 -0
- package/lib/jess-error.d.ts.map +1 -0
- package/lib/jess-error.js +508 -0
- package/lib/jess-error.js.map +1 -0
- package/lib/logger/deprecation-processing.d.ts +41 -0
- package/lib/logger/deprecation-processing.d.ts.map +1 -0
- package/lib/logger/deprecation-processing.js +81 -0
- package/lib/logger/deprecation-processing.js.map +1 -0
- package/lib/logger.d.ts +10 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +20 -0
- package/lib/logger.js.map +1 -0
- package/lib/plugin.d.ts +94 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +174 -0
- package/lib/plugin.js.map +1 -0
- package/lib/tree/ampersand.d.ts +94 -0
- package/lib/tree/ampersand.d.ts.map +1 -0
- package/lib/tree/ampersand.js +269 -0
- package/lib/tree/ampersand.js.map +1 -0
- package/lib/tree/any.d.ts +58 -0
- package/lib/tree/any.d.ts.map +1 -0
- package/lib/tree/any.js +101 -0
- package/lib/tree/any.js.map +1 -0
- package/lib/tree/at-rule.d.ts +53 -0
- package/lib/tree/at-rule.d.ts.map +1 -0
- package/lib/tree/at-rule.js +503 -0
- package/lib/tree/at-rule.js.map +1 -0
- package/lib/tree/block.d.ts +22 -0
- package/lib/tree/block.d.ts.map +1 -0
- package/lib/tree/block.js +24 -0
- package/lib/tree/block.js.map +1 -0
- package/lib/tree/bool.d.ts +17 -0
- package/lib/tree/bool.d.ts.map +1 -0
- package/lib/tree/bool.js +24 -0
- package/lib/tree/bool.js.map +1 -0
- package/lib/tree/call.d.ts +66 -0
- package/lib/tree/call.d.ts.map +1 -0
- package/lib/tree/call.js +306 -0
- package/lib/tree/call.js.map +1 -0
- package/lib/tree/collection.d.ts +30 -0
- package/lib/tree/collection.d.ts.map +1 -0
- package/lib/tree/collection.js +37 -0
- package/lib/tree/collection.js.map +1 -0
- package/lib/tree/color.d.ts +101 -0
- package/lib/tree/color.d.ts.map +1 -0
- package/lib/tree/color.js +513 -0
- package/lib/tree/color.js.map +1 -0
- package/lib/tree/combinator.d.ts +12 -0
- package/lib/tree/combinator.d.ts.map +1 -0
- package/lib/tree/combinator.js +8 -0
- package/lib/tree/combinator.js.map +1 -0
- package/lib/tree/comment.d.ts +20 -0
- package/lib/tree/comment.d.ts.map +1 -0
- package/lib/tree/comment.js +18 -0
- package/lib/tree/comment.js.map +1 -0
- package/lib/tree/condition.d.ts +31 -0
- package/lib/tree/condition.d.ts.map +1 -0
- package/lib/tree/condition.js +103 -0
- package/lib/tree/condition.js.map +1 -0
- package/lib/tree/control.d.ts +104 -0
- package/lib/tree/control.d.ts.map +1 -0
- package/lib/tree/control.js +430 -0
- package/lib/tree/control.js.map +1 -0
- package/lib/tree/declaration-custom.d.ts +18 -0
- package/lib/tree/declaration-custom.d.ts.map +1 -0
- package/lib/tree/declaration-custom.js +24 -0
- package/lib/tree/declaration-custom.js.map +1 -0
- package/lib/tree/declaration-var.d.ts +36 -0
- package/lib/tree/declaration-var.d.ts.map +1 -0
- package/lib/tree/declaration-var.js +63 -0
- package/lib/tree/declaration-var.js.map +1 -0
- package/lib/tree/declaration.d.ts +78 -0
- package/lib/tree/declaration.d.ts.map +1 -0
- package/lib/tree/declaration.js +289 -0
- package/lib/tree/declaration.js.map +1 -0
- package/lib/tree/default-guard.d.ts +15 -0
- package/lib/tree/default-guard.d.ts.map +1 -0
- package/lib/tree/default-guard.js +19 -0
- package/lib/tree/default-guard.js.map +1 -0
- package/lib/tree/dimension.d.ts +33 -0
- package/lib/tree/dimension.d.ts.map +1 -0
- package/lib/tree/dimension.js +291 -0
- package/lib/tree/dimension.js.map +1 -0
- package/lib/tree/expression.d.ts +24 -0
- package/lib/tree/expression.d.ts.map +1 -0
- package/lib/tree/expression.js +28 -0
- package/lib/tree/expression.js.map +1 -0
- package/lib/tree/extend-list.d.ts +23 -0
- package/lib/tree/extend-list.d.ts.map +1 -0
- package/lib/tree/extend-list.js +20 -0
- package/lib/tree/extend-list.js.map +1 -0
- package/lib/tree/extend.d.ts +47 -0
- package/lib/tree/extend.d.ts.map +1 -0
- package/lib/tree/extend.js +292 -0
- package/lib/tree/extend.js.map +1 -0
- package/lib/tree/function.d.ts +48 -0
- package/lib/tree/function.d.ts.map +1 -0
- package/lib/tree/function.js +74 -0
- package/lib/tree/function.js.map +1 -0
- package/lib/tree/import-js.d.ts +35 -0
- package/lib/tree/import-js.d.ts.map +1 -0
- package/lib/tree/import-js.js +45 -0
- package/lib/tree/import-js.js.map +1 -0
- package/lib/tree/import-style.d.ts +156 -0
- package/lib/tree/import-style.d.ts.map +1 -0
- package/lib/tree/import-style.js +556 -0
- package/lib/tree/import-style.js.map +1 -0
- package/lib/tree/index.d.ts +71 -0
- package/lib/tree/index.d.ts.map +1 -0
- package/lib/tree/index.js +95 -0
- package/lib/tree/index.js.map +1 -0
- package/lib/tree/interpolated-reference.d.ts +24 -0
- package/lib/tree/interpolated-reference.d.ts.map +1 -0
- package/lib/tree/interpolated-reference.js +37 -0
- package/lib/tree/interpolated-reference.js.map +1 -0
- package/lib/tree/interpolated.d.ts +62 -0
- package/lib/tree/interpolated.d.ts.map +1 -0
- package/lib/tree/interpolated.js +204 -0
- package/lib/tree/interpolated.js.map +1 -0
- package/lib/tree/js-array.d.ts +10 -0
- package/lib/tree/js-array.d.ts.map +1 -0
- package/lib/tree/js-array.js +10 -0
- package/lib/tree/js-array.js.map +1 -0
- package/lib/tree/js-expr.d.ts +23 -0
- package/lib/tree/js-expr.d.ts.map +1 -0
- package/lib/tree/js-expr.js +28 -0
- package/lib/tree/js-expr.js.map +1 -0
- package/lib/tree/js-function.d.ts +20 -0
- package/lib/tree/js-function.d.ts.map +1 -0
- package/lib/tree/js-function.js +16 -0
- package/lib/tree/js-function.js.map +1 -0
- package/lib/tree/js-object.d.ts +10 -0
- package/lib/tree/js-object.d.ts.map +1 -0
- package/lib/tree/js-object.js +10 -0
- package/lib/tree/js-object.js.map +1 -0
- package/lib/tree/list.d.ts +38 -0
- package/lib/tree/list.d.ts.map +1 -0
- package/lib/tree/list.js +83 -0
- package/lib/tree/list.js.map +1 -0
- package/lib/tree/log.d.ts +29 -0
- package/lib/tree/log.d.ts.map +1 -0
- package/lib/tree/log.js +56 -0
- package/lib/tree/log.js.map +1 -0
- package/lib/tree/mixin.d.ts +87 -0
- package/lib/tree/mixin.d.ts.map +1 -0
- package/lib/tree/mixin.js +112 -0
- package/lib/tree/mixin.js.map +1 -0
- package/lib/tree/negative.d.ts +17 -0
- package/lib/tree/negative.d.ts.map +1 -0
- package/lib/tree/negative.js +22 -0
- package/lib/tree/negative.js.map +1 -0
- package/lib/tree/nil.d.ts +31 -0
- package/lib/tree/nil.d.ts.map +1 -0
- package/lib/tree/nil.js +36 -0
- package/lib/tree/nil.js.map +1 -0
- package/lib/tree/node-base.d.ts +359 -0
- package/lib/tree/node-base.d.ts.map +1 -0
- package/lib/tree/node-base.js +884 -0
- package/lib/tree/node-base.js.map +1 -0
- package/lib/tree/node.d.ts +10 -0
- package/lib/tree/node.d.ts.map +1 -0
- package/lib/tree/node.js +45 -0
- package/lib/tree/node.js.map +1 -0
- package/lib/tree/number.d.ts +21 -0
- package/lib/tree/number.d.ts.map +1 -0
- package/lib/tree/number.js +27 -0
- package/lib/tree/number.js.map +1 -0
- package/lib/tree/operation.d.ts +26 -0
- package/lib/tree/operation.d.ts.map +1 -0
- package/lib/tree/operation.js +103 -0
- package/lib/tree/operation.js.map +1 -0
- package/lib/tree/paren.d.ts +18 -0
- package/lib/tree/paren.d.ts.map +1 -0
- package/lib/tree/paren.js +86 -0
- package/lib/tree/paren.js.map +1 -0
- package/lib/tree/query-condition.d.ts +17 -0
- package/lib/tree/query-condition.d.ts.map +1 -0
- package/lib/tree/query-condition.js +39 -0
- package/lib/tree/query-condition.js.map +1 -0
- package/lib/tree/quoted.d.ts +27 -0
- package/lib/tree/quoted.d.ts.map +1 -0
- package/lib/tree/quoted.js +66 -0
- package/lib/tree/quoted.js.map +1 -0
- package/lib/tree/range.d.ts +33 -0
- package/lib/tree/range.d.ts.map +1 -0
- package/lib/tree/range.js +47 -0
- package/lib/tree/range.js.map +1 -0
- package/lib/tree/reference.d.ts +76 -0
- package/lib/tree/reference.d.ts.map +1 -0
- package/lib/tree/reference.js +521 -0
- package/lib/tree/reference.js.map +1 -0
- package/lib/tree/rest.d.ts +15 -0
- package/lib/tree/rest.d.ts.map +1 -0
- package/lib/tree/rest.js +32 -0
- package/lib/tree/rest.js.map +1 -0
- package/lib/tree/rules-raw.d.ts +17 -0
- package/lib/tree/rules-raw.d.ts.map +1 -0
- package/lib/tree/rules-raw.js +37 -0
- package/lib/tree/rules-raw.js.map +1 -0
- package/lib/tree/rules.d.ts +255 -0
- package/lib/tree/rules.d.ts.map +1 -0
- package/lib/tree/rules.js +2293 -0
- package/lib/tree/rules.js.map +1 -0
- package/lib/tree/ruleset.d.ts +91 -0
- package/lib/tree/ruleset.d.ts.map +1 -0
- package/lib/tree/ruleset.js +506 -0
- package/lib/tree/ruleset.js.map +1 -0
- package/lib/tree/selector-attr.d.ts +31 -0
- package/lib/tree/selector-attr.d.ts.map +1 -0
- package/lib/tree/selector-attr.js +99 -0
- package/lib/tree/selector-attr.js.map +1 -0
- package/lib/tree/selector-basic.d.ts +23 -0
- package/lib/tree/selector-basic.d.ts.map +1 -0
- package/lib/tree/selector-basic.js +34 -0
- package/lib/tree/selector-basic.js.map +1 -0
- package/lib/tree/selector-capture.d.ts +23 -0
- package/lib/tree/selector-capture.d.ts.map +1 -0
- package/lib/tree/selector-capture.js +34 -0
- package/lib/tree/selector-capture.js.map +1 -0
- package/lib/tree/selector-complex.d.ts +40 -0
- package/lib/tree/selector-complex.d.ts.map +1 -0
- package/lib/tree/selector-complex.js +143 -0
- package/lib/tree/selector-complex.js.map +1 -0
- package/lib/tree/selector-compound.d.ts +16 -0
- package/lib/tree/selector-compound.d.ts.map +1 -0
- package/lib/tree/selector-compound.js +114 -0
- package/lib/tree/selector-compound.js.map +1 -0
- package/lib/tree/selector-interpolated.d.ts +23 -0
- package/lib/tree/selector-interpolated.d.ts.map +1 -0
- package/lib/tree/selector-interpolated.js +27 -0
- package/lib/tree/selector-interpolated.js.map +1 -0
- package/lib/tree/selector-list.d.ts +17 -0
- package/lib/tree/selector-list.d.ts.map +1 -0
- package/lib/tree/selector-list.js +184 -0
- package/lib/tree/selector-list.js.map +1 -0
- package/lib/tree/selector-pseudo.d.ts +42 -0
- package/lib/tree/selector-pseudo.d.ts.map +1 -0
- package/lib/tree/selector-pseudo.js +191 -0
- package/lib/tree/selector-pseudo.js.map +1 -0
- package/lib/tree/selector-simple.d.ts +5 -0
- package/lib/tree/selector-simple.d.ts.map +1 -0
- package/lib/tree/selector-simple.js +6 -0
- package/lib/tree/selector-simple.js.map +1 -0
- package/lib/tree/selector.d.ts +43 -0
- package/lib/tree/selector.d.ts.map +1 -0
- package/lib/tree/selector.js +56 -0
- package/lib/tree/selector.js.map +1 -0
- package/lib/tree/sequence.d.ts +43 -0
- package/lib/tree/sequence.d.ts.map +1 -0
- package/lib/tree/sequence.js +148 -0
- package/lib/tree/sequence.js.map +1 -0
- package/lib/tree/tree.d.ts +87 -0
- package/lib/tree/tree.d.ts.map +1 -0
- package/lib/tree/tree.js +2 -0
- package/lib/tree/tree.js.map +1 -0
- package/lib/tree/url.d.ts +18 -0
- package/lib/tree/url.d.ts.map +1 -0
- package/lib/tree/url.js +35 -0
- package/lib/tree/url.js.map +1 -0
- package/lib/tree/util/__tests__/debug-log.d.ts +1 -0
- package/lib/tree/util/__tests__/debug-log.d.ts.map +1 -0
- package/lib/tree/util/__tests__/debug-log.js +36 -0
- package/lib/tree/util/__tests__/debug-log.js.map +1 -0
- package/lib/tree/util/calculate.d.ts +3 -0
- package/lib/tree/util/calculate.d.ts.map +1 -0
- package/lib/tree/util/calculate.js +10 -0
- package/lib/tree/util/calculate.js.map +1 -0
- package/lib/tree/util/cast.d.ts +10 -0
- package/lib/tree/util/cast.d.ts.map +1 -0
- package/lib/tree/util/cast.js +87 -0
- package/lib/tree/util/cast.js.map +1 -0
- package/lib/tree/util/cloning.d.ts +4 -0
- package/lib/tree/util/cloning.d.ts.map +1 -0
- package/lib/tree/util/cloning.js +8 -0
- package/lib/tree/util/cloning.js.map +1 -0
- package/lib/tree/util/collections.d.ts +57 -0
- package/lib/tree/util/collections.d.ts.map +1 -0
- package/lib/tree/util/collections.js +136 -0
- package/lib/tree/util/collections.js.map +1 -0
- package/lib/tree/util/compare.d.ts +11 -0
- package/lib/tree/util/compare.d.ts.map +1 -0
- package/lib/tree/util/compare.js +89 -0
- package/lib/tree/util/compare.js.map +1 -0
- package/lib/tree/util/extend-helpers.d.ts +2 -0
- package/lib/tree/util/extend-helpers.d.ts.map +1 -0
- package/lib/tree/util/extend-helpers.js +2 -0
- package/lib/tree/util/extend-helpers.js.map +1 -0
- package/lib/tree/util/extend-roots.d.ts +37 -0
- package/lib/tree/util/extend-roots.d.ts.map +1 -0
- package/lib/tree/util/extend-roots.js +682 -0
- package/lib/tree/util/extend-roots.js.map +1 -0
- package/lib/tree/util/extend-roots.old.d.ts +132 -0
- package/lib/tree/util/extend-roots.old.d.ts.map +1 -0
- package/lib/tree/util/extend-roots.old.js +2272 -0
- package/lib/tree/util/extend-roots.old.js.map +1 -0
- package/lib/tree/util/extend-trace-debug.d.ts +13 -0
- package/lib/tree/util/extend-trace-debug.d.ts.map +1 -0
- package/lib/tree/util/extend-trace-debug.js +34 -0
- package/lib/tree/util/extend-trace-debug.js.map +1 -0
- package/lib/tree/util/extend.d.ts +218 -0
- package/lib/tree/util/extend.d.ts.map +1 -0
- package/lib/tree/util/extend.js +3033 -0
- package/lib/tree/util/extend.js.map +1 -0
- package/lib/tree/util/find-extendable-locations.d.ts +2 -0
- package/lib/tree/util/find-extendable-locations.d.ts.map +1 -0
- package/lib/tree/util/find-extendable-locations.js +2 -0
- package/lib/tree/util/find-extendable-locations.js.map +1 -0
- package/lib/tree/util/format.d.ts +20 -0
- package/lib/tree/util/format.d.ts.map +1 -0
- package/lib/tree/util/format.js +67 -0
- package/lib/tree/util/format.js.map +1 -0
- package/lib/tree/util/is-node.d.ts +13 -0
- package/lib/tree/util/is-node.d.ts.map +1 -0
- package/lib/tree/util/is-node.js +43 -0
- package/lib/tree/util/is-node.js.map +1 -0
- package/lib/tree/util/print.d.ts +80 -0
- package/lib/tree/util/print.d.ts.map +1 -0
- package/lib/tree/util/print.js +205 -0
- package/lib/tree/util/print.js.map +1 -0
- package/lib/tree/util/process-leading-is.d.ts +25 -0
- package/lib/tree/util/process-leading-is.d.ts.map +1 -0
- package/lib/tree/util/process-leading-is.js +364 -0
- package/lib/tree/util/process-leading-is.js.map +1 -0
- package/lib/tree/util/recursion-helper.d.ts +15 -0
- package/lib/tree/util/recursion-helper.d.ts.map +1 -0
- package/lib/tree/util/recursion-helper.js +43 -0
- package/lib/tree/util/recursion-helper.js.map +1 -0
- package/lib/tree/util/regex.d.ts +4 -0
- package/lib/tree/util/regex.d.ts.map +1 -0
- package/lib/tree/util/regex.js +4 -0
- package/lib/tree/util/regex.js.map +1 -0
- package/lib/tree/util/registry-utils.d.ts +192 -0
- package/lib/tree/util/registry-utils.d.ts.map +1 -0
- package/lib/tree/util/registry-utils.js +1242 -0
- package/lib/tree/util/registry-utils.js.map +1 -0
- package/lib/tree/util/ruleset-trace.d.ts +4 -0
- package/lib/tree/util/ruleset-trace.d.ts.map +1 -0
- package/lib/tree/util/ruleset-trace.js +14 -0
- package/lib/tree/util/ruleset-trace.js.map +1 -0
- package/lib/tree/util/selector-compare.d.ts +2 -0
- package/lib/tree/util/selector-compare.d.ts.map +1 -0
- package/lib/tree/util/selector-compare.js +2 -0
- package/lib/tree/util/selector-compare.js.map +1 -0
- package/lib/tree/util/selector-match-core.d.ts +171 -0
- package/lib/tree/util/selector-match-core.d.ts.map +1 -0
- package/lib/tree/util/selector-match-core.js +1578 -0
- package/lib/tree/util/selector-match-core.js.map +1 -0
- package/lib/tree/util/selector-utils.d.ts +30 -0
- package/lib/tree/util/selector-utils.d.ts.map +1 -0
- package/lib/tree/util/selector-utils.js +100 -0
- package/lib/tree/util/selector-utils.js.map +1 -0
- package/lib/tree/util/serialize-helper.d.ts +13 -0
- package/lib/tree/util/serialize-helper.d.ts.map +1 -0
- package/lib/tree/util/serialize-helper.js +387 -0
- package/lib/tree/util/serialize-helper.js.map +1 -0
- package/lib/tree/util/serialize-types.d.ts +9 -0
- package/lib/tree/util/serialize-types.d.ts.map +1 -0
- package/lib/tree/util/serialize-types.js +216 -0
- package/lib/tree/util/serialize-types.js.map +1 -0
- package/lib/tree/util/should-operate.d.ts +23 -0
- package/lib/tree/util/should-operate.d.ts.map +1 -0
- package/lib/tree/util/should-operate.js +46 -0
- package/lib/tree/util/should-operate.js.map +1 -0
- package/lib/tree/util/sourcemap.d.ts +7 -0
- package/lib/tree/util/sourcemap.d.ts.map +1 -0
- package/lib/tree/util/sourcemap.js +25 -0
- package/lib/tree/util/sourcemap.js.map +1 -0
- package/lib/types/config.d.ts +205 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/config.js +2 -0
- package/lib/types/config.js.map +1 -0
- package/lib/types/index.d.ts +15 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +3 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/modes.d.ts +24 -0
- package/lib/types/modes.d.ts.map +1 -0
- package/lib/types/modes.js +2 -0
- package/lib/types/modes.js.map +1 -0
- package/lib/types.d.ts +61 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/use-webpack-resolver.d.ts +9 -0
- package/lib/use-webpack-resolver.d.ts.map +1 -0
- package/lib/use-webpack-resolver.js +41 -0
- package/lib/use-webpack-resolver.js.map +1 -0
- package/lib/visitor/index.d.ts +136 -0
- package/lib/visitor/index.d.ts.map +1 -0
- package/lib/visitor/index.js +135 -0
- package/lib/visitor/index.js.map +1 -0
- package/lib/visitor/less-visitor.d.ts +7 -0
- package/lib/visitor/less-visitor.d.ts.map +1 -0
- package/lib/visitor/less-visitor.js +7 -0
- package/lib/visitor/less-visitor.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,1578 @@
|
|
|
1
|
+
import { SelectorList } from '../selector-list.js';
|
|
2
|
+
import { ComplexSelector } from '../selector-complex.js';
|
|
3
|
+
import { CompoundSelector } from '../selector-compound.js';
|
|
4
|
+
import { isNode } from './is-node.js';
|
|
5
|
+
/**
|
|
6
|
+
* Helper functions for extend operations that eliminate genuine code duplication
|
|
7
|
+
* These preserve all original logic while extracting commonly repeated patterns
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Determines the extension type based on selector type and location context
|
|
11
|
+
* Extracted from multiple places in extend.ts with preserved original logic
|
|
12
|
+
*/
|
|
13
|
+
export function determineExtensionType(selector, basePath) {
|
|
14
|
+
// If we're inside a pseudo-selector argument (like :where() or :is())
|
|
15
|
+
if (basePath.some(segment => segment === 'arg')) {
|
|
16
|
+
// Check if we're matching a component within a compound selector inside the argument
|
|
17
|
+
// Path format: ['arg', selectorListIndex, compoundIndex, ...]
|
|
18
|
+
// If we have at least 3 segments and the last numeric segment is a compound index,
|
|
19
|
+
// we should wrap to preserve compound selector structure
|
|
20
|
+
const numericSegments = basePath.filter((s) => typeof s === 'number');
|
|
21
|
+
if (numericSegments.length >= 2) {
|
|
22
|
+
// We're inside a compound selector - use 'wrap' to create :is() wrapper
|
|
23
|
+
return 'wrap';
|
|
24
|
+
}
|
|
25
|
+
return 'append'; // Can append to pseudo-selector argument lists
|
|
26
|
+
}
|
|
27
|
+
// Check if we're matching a component within a compound selector
|
|
28
|
+
// Path format: [compoundIndex, ...] where compoundIndex is a number
|
|
29
|
+
// If the path starts with a number and we're in a compound selector context, use 'wrap'
|
|
30
|
+
if (basePath.length > 0 && typeof basePath[0] === 'number') {
|
|
31
|
+
// This could be a compound selector component match - check if selector is CompoundSelector
|
|
32
|
+
// Actually, we can't check the selector type here, so we'll rely on the caller to set 'wrap'
|
|
33
|
+
// For now, default to 'replace' for numeric paths
|
|
34
|
+
}
|
|
35
|
+
// If we're in a SelectorList context (not just any numeric path)
|
|
36
|
+
// We need to check the context more carefully
|
|
37
|
+
// Numeric paths can mean: SelectorList index, CompoundSelector index, or ComplexSelector index
|
|
38
|
+
// Only SelectorList contexts should use 'append' - others should use 'replace'
|
|
39
|
+
// For now, default to replace for all direct matches
|
|
40
|
+
// The 'append' behavior should be handled by specialized logic in pseudo-selector handling
|
|
41
|
+
return 'replace';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if a value can be treated as a selector
|
|
45
|
+
* Extracted from multiple pseudo-selector checks
|
|
46
|
+
*/
|
|
47
|
+
export function isSelector(value) {
|
|
48
|
+
// Avoid `instanceof` (module identity can diverge under Vite/Vitest).
|
|
49
|
+
// All selector nodes set `isSelector = true` on the base Selector class.
|
|
50
|
+
return !!value && typeof value === 'object' && value.isSelector === true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Filters components to get only non-combinator selectors
|
|
54
|
+
* This pattern appears in many complex selector algorithms
|
|
55
|
+
*/
|
|
56
|
+
export function getNonCombinatorComponents(selector) {
|
|
57
|
+
return selector.value.filter(c => !isNode(c, 'Combinator'));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Filters components to get only combinators
|
|
61
|
+
* Used in complex selector matching algorithms
|
|
62
|
+
*/
|
|
63
|
+
export function getCombinatorComponents(selector) {
|
|
64
|
+
return selector.value.filter(c => isNode(c, 'Combinator'));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Checks if two selectors match using component-level logic
|
|
68
|
+
* Preserves the exact original matching semantics from multiple locations
|
|
69
|
+
*/
|
|
70
|
+
export function componentsMatch(a, b) {
|
|
71
|
+
// Exact string match first (fast path)
|
|
72
|
+
if (a.valueOf() === b.valueOf()) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
// Handle compound selector equivalence (order-independent)
|
|
76
|
+
if (isNode(a, 'CompoundSelector') && isNode(b, 'CompoundSelector')) {
|
|
77
|
+
return areCompoundSelectorsEquivalent(a, b);
|
|
78
|
+
}
|
|
79
|
+
// Handle compound vs simple: compound contains simple (improved structural matching)
|
|
80
|
+
if (isNode(a, 'CompoundSelector') && isNode(b, 'SimpleSelector')) {
|
|
81
|
+
return a.value.some(comp => comp.valueOf() === b.valueOf());
|
|
82
|
+
}
|
|
83
|
+
// Handle simple vs compound: compound contains simple (improved structural matching)
|
|
84
|
+
if (isNode(a, 'SimpleSelector') && isNode(b, 'CompoundSelector')) {
|
|
85
|
+
return b.value.some(comp => comp.valueOf() === a.valueOf());
|
|
86
|
+
}
|
|
87
|
+
// Handle pseudo-selector equivalence
|
|
88
|
+
if (isNode(a, 'PseudoSelector') && isNode(b, 'PseudoSelector')) {
|
|
89
|
+
return a.value.name === b.value.name
|
|
90
|
+
&& areSelectorArgumentsEquivalent(a.value.arg, b.value.arg);
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Checks pseudo-selector equivalence including argument matching
|
|
96
|
+
* Handles all pseudo-selectors with selector arguments, not just specific ones
|
|
97
|
+
* Extracted from find-extendable-locations.ts with preserved original logic
|
|
98
|
+
*/
|
|
99
|
+
export function arePseudoSelectorsEquivalent(a, b) {
|
|
100
|
+
if (!isNode(a, 'PseudoSelector') || !isNode(b, 'PseudoSelector')) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (a.value.name !== b.value.name) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const aArg = a.value.arg;
|
|
107
|
+
const bArg = b.value.arg;
|
|
108
|
+
if (!aArg && !bArg) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (!aArg || !bArg) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// If both have selector arguments, check equivalence
|
|
115
|
+
if (isSelector(aArg) && isSelector(bArg)) {
|
|
116
|
+
return areSelectorArgumentsEquivalent(aArg, bArg);
|
|
117
|
+
}
|
|
118
|
+
// For non-selector arguments, use string comparison
|
|
119
|
+
return String(aArg) === String(bArg);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Checks equivalence of selector arguments in pseudo-selectors
|
|
123
|
+
* Preserves complex original logic for :is(), :where(), etc.
|
|
124
|
+
*/
|
|
125
|
+
export function areSelectorArgumentsEquivalent(a, b) {
|
|
126
|
+
// Handle selector lists (order-independent)
|
|
127
|
+
if (isNode(a, 'SelectorList') && isNode(b, 'SelectorList')) {
|
|
128
|
+
if (a.value.length !== b.value.length) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return a.value.every(aItem => b.value.some(bItem => componentsMatch(aItem, bItem)));
|
|
132
|
+
}
|
|
133
|
+
// Handle compound selectors
|
|
134
|
+
if (isNode(a, 'CompoundSelector') && isNode(b, 'CompoundSelector')) {
|
|
135
|
+
return areCompoundSelectorsEquivalent(a, b);
|
|
136
|
+
}
|
|
137
|
+
// Default comparison
|
|
138
|
+
return componentsMatch(a, b);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Efficient compound selector equivalence check (order-independent)
|
|
142
|
+
* Preserves exact original algorithm from find-extendable-locations.ts
|
|
143
|
+
*/
|
|
144
|
+
/**
|
|
145
|
+
* True when find's components appear in target in order (subsequence). Enables .a.c.b to match .a.b.
|
|
146
|
+
*/
|
|
147
|
+
function compoundContainsCompoundSubsequence(target, find) {
|
|
148
|
+
if (find.value.length > target.value.length) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const eq = (t, f) => isNode(f, 'PseudoSelector') && f.value.arg && isSelector(f.value.arg)
|
|
152
|
+
? arePseudoSelectorsEquivalent(t, f)
|
|
153
|
+
: t.valueOf() === f.valueOf();
|
|
154
|
+
let tIdx = 0;
|
|
155
|
+
for (const fComp of find.value) {
|
|
156
|
+
let found = false;
|
|
157
|
+
while (tIdx < target.value.length) {
|
|
158
|
+
if (eq(target.value[tIdx], fComp)) {
|
|
159
|
+
tIdx++;
|
|
160
|
+
found = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
tIdx++;
|
|
164
|
+
}
|
|
165
|
+
if (!found) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
export function areCompoundSelectorsEquivalent(a, b) {
|
|
172
|
+
if (a.value.length !== b.value.length) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
// Expand both compounds to handle :is() pseudo-selectors (preserving original expansion logic)
|
|
176
|
+
const aExpanded = expandCompoundWithPseudoSelectors(a);
|
|
177
|
+
const bExpanded = expandCompoundWithPseudoSelectors(b);
|
|
178
|
+
// Check if any expanded form of a matches any expanded form of b
|
|
179
|
+
return aExpanded.some(aComp => bExpanded.some(bComp =>
|
|
180
|
+
// All components must match
|
|
181
|
+
aComp.value.length === bComp.value.length
|
|
182
|
+
&& aComp.value.every(aCompItem => bComp.value.some(bCompItem => isNode(aCompItem, 'PseudoSelector') && aCompItem.value.arg && isSelector(aCompItem.value.arg) && isNode(bCompItem, 'PseudoSelector')
|
|
183
|
+
? arePseudoSelectorsEquivalent(aCompItem, bCompItem)
|
|
184
|
+
: aCompItem.valueOf() === bCompItem.valueOf()))));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Expands compound selectors by handling :is() pseudo-selectors
|
|
188
|
+
* Preserves exact original expansion algorithm - only handles :is() specially
|
|
189
|
+
*/
|
|
190
|
+
export function expandCompoundWithPseudoSelectors(compound) {
|
|
191
|
+
const expansions = [compound];
|
|
192
|
+
// Only expand :is() pseudo-selectors (preserving original logic)
|
|
193
|
+
compound.value.forEach((component, index) => {
|
|
194
|
+
if (isNode(component, 'PseudoSelector') && component.value.name === ':is' && component.value.arg && isSelector(component.value.arg)) {
|
|
195
|
+
const arg = component.value.arg;
|
|
196
|
+
// Handle :is() with compound selector argument
|
|
197
|
+
if (isNode(arg, 'CompoundSelector')) {
|
|
198
|
+
// Create new expansions by replacing :is() with its contents
|
|
199
|
+
const newExpansions = [];
|
|
200
|
+
expansions.forEach((expansion) => {
|
|
201
|
+
const newComponents = [...expansion.value];
|
|
202
|
+
newComponents.splice(index, 1, ...arg.value); // Replace :is() with its contents
|
|
203
|
+
newExpansions.push(new CompoundSelector(newComponents));
|
|
204
|
+
});
|
|
205
|
+
expansions.push(...newExpansions);
|
|
206
|
+
}
|
|
207
|
+
else if (isNode(arg, 'SimpleSelector')) {
|
|
208
|
+
// Handle :is() with simple selector argument
|
|
209
|
+
const newExpansions = [];
|
|
210
|
+
expansions.forEach((expansion) => {
|
|
211
|
+
const newComponents = [...expansion.value];
|
|
212
|
+
newComponents.splice(index, 1, arg); // Replace :is() with the simple selector
|
|
213
|
+
newExpansions.push(new CompoundSelector(newComponents));
|
|
214
|
+
});
|
|
215
|
+
expansions.push(...newExpansions);
|
|
216
|
+
}
|
|
217
|
+
else if (isNode(arg, 'SelectorList')) {
|
|
218
|
+
// Handle :is() with selector list argument
|
|
219
|
+
const newExpansions = [];
|
|
220
|
+
const listArg = arg;
|
|
221
|
+
expansions.forEach((expansion) => {
|
|
222
|
+
listArg.value.forEach((listItem) => {
|
|
223
|
+
const newComponents = [...expansion.value];
|
|
224
|
+
if (isNode(listItem, 'CompoundSelector')) {
|
|
225
|
+
newComponents.splice(index, 1, ...listItem.value);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
newComponents.splice(index, 1, listItem);
|
|
229
|
+
}
|
|
230
|
+
newExpansions.push(new CompoundSelector(newComponents));
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
expansions.push(...newExpansions);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return expansions;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Expands complex selectors containing :is() pseudo-selectors into equivalent selector lists
|
|
241
|
+
* This handles cases like: a :is(b, c) -> a b, a c
|
|
242
|
+
*/
|
|
243
|
+
export function expandComplexSelectorWithIs(complexSelector) {
|
|
244
|
+
// Look for :is() pseudo-selectors in the complex selector
|
|
245
|
+
let hasIsSelector = false;
|
|
246
|
+
let isIndex = -1;
|
|
247
|
+
let isArg = null;
|
|
248
|
+
let isFromBareIsCompound = false;
|
|
249
|
+
let isFromAmpersandSelector = false;
|
|
250
|
+
for (let i = 0; i < complexSelector.value.length; i++) {
|
|
251
|
+
const component = complexSelector.value[i];
|
|
252
|
+
if (isNode(component, 'PseudoSelector') && component.value.name === ':is' && component.value.arg && isSelector(component.value.arg)) {
|
|
253
|
+
hasIsSelector = true;
|
|
254
|
+
isIndex = i;
|
|
255
|
+
isArg = component.value.arg;
|
|
256
|
+
break; // Handle first :is() found for now
|
|
257
|
+
}
|
|
258
|
+
// Also support the common case where `:is(...)` is wrapped in a single-item CompoundSelector
|
|
259
|
+
// (e.g. `:is(.a, .b) .c`) so matching can expand alternatives.
|
|
260
|
+
if (isNode(component, 'CompoundSelector') && component.value.length === 1) {
|
|
261
|
+
const only = component.value[0];
|
|
262
|
+
if (isNode(only, 'PseudoSelector') && only.value.name === ':is' && only.value.arg && isSelector(only.value.arg)) {
|
|
263
|
+
hasIsSelector = true;
|
|
264
|
+
isIndex = i;
|
|
265
|
+
isArg = only.value.arg;
|
|
266
|
+
isFromBareIsCompound = true;
|
|
267
|
+
break; // Handle first :is() found for now
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Also support the case where `:is(...)` is carried inside an implicit ampersand's resolved selector.
|
|
271
|
+
// This shows up as a ComplexSelector beginning with Ampersand(selector=:is(...)).
|
|
272
|
+
if (isNode(component, 'Ampersand')) {
|
|
273
|
+
const sel = component.getResolvedSelector();
|
|
274
|
+
if (sel && isNode(sel, 'PseudoSelector') && sel.value.name === ':is' && sel.value.arg && isSelector(sel.value.arg)) {
|
|
275
|
+
hasIsSelector = true;
|
|
276
|
+
isIndex = i;
|
|
277
|
+
isArg = sel.value.arg;
|
|
278
|
+
isFromAmpersandSelector = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
if (sel && isNode(sel, 'CompoundSelector') && sel.value.length === 1) {
|
|
282
|
+
const only = sel.value[0];
|
|
283
|
+
if (isNode(only, 'PseudoSelector') && only.value.name === ':is' && only.value.arg && isSelector(only.value.arg)) {
|
|
284
|
+
hasIsSelector = true;
|
|
285
|
+
isIndex = i;
|
|
286
|
+
isArg = only.value.arg;
|
|
287
|
+
isFromAmpersandSelector = true;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!hasIsSelector || !isArg) {
|
|
294
|
+
return [complexSelector]; // No :is() found, return original
|
|
295
|
+
}
|
|
296
|
+
const results = [];
|
|
297
|
+
// Get the list of alternatives from :is()
|
|
298
|
+
const alternatives = isNode(isArg, 'SelectorList') ? isArg.value : [isArg];
|
|
299
|
+
// For each alternative, create a new complex selector
|
|
300
|
+
alternatives.forEach((alternative) => {
|
|
301
|
+
const newComponents = [...complexSelector.value];
|
|
302
|
+
if (isFromAmpersandSelector) {
|
|
303
|
+
// Inline the resolved alternative directly so we do not reintroduce synthetic
|
|
304
|
+
// ampersand nodes while expanding match candidates.
|
|
305
|
+
if (isNode(alternative, 'ComplexSelector')) {
|
|
306
|
+
newComponents.splice(isIndex, 1, ...alternative.value);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
newComponents[isIndex] = alternative;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else if (isFromBareIsCompound) {
|
|
313
|
+
// The original `:is(...)` lived inside a CompoundSelector position. Replace that slot with the
|
|
314
|
+
// alternative selector's components where possible.
|
|
315
|
+
if (isNode(alternative, 'ComplexSelector')) {
|
|
316
|
+
newComponents.splice(isIndex, 1, ...alternative.value);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
newComponents[isIndex] = alternative;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
newComponents[isIndex] = alternative; // Replace :is() with the alternative
|
|
324
|
+
}
|
|
325
|
+
results.push(new ComplexSelector(newComponents).inherit(complexSelector));
|
|
326
|
+
});
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Expands any selector that might contain :is() into equivalent forms for comparison
|
|
331
|
+
*/
|
|
332
|
+
export function expandSelectorWithIs(selector) {
|
|
333
|
+
if (isNode(selector, 'ComplexSelector')) {
|
|
334
|
+
return expandComplexSelectorWithIs(selector);
|
|
335
|
+
}
|
|
336
|
+
// For other types, check if they need expansion
|
|
337
|
+
if (isNode(selector, 'CompoundSelector')) {
|
|
338
|
+
const expansions = expandCompoundWithPseudoSelectors(selector);
|
|
339
|
+
return expansions.length > 1 ? expansions : [selector];
|
|
340
|
+
}
|
|
341
|
+
return [selector]; // No expansion needed
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Creates a standardized path representation for selector tree navigation
|
|
345
|
+
* Eliminates duplicate path building logic
|
|
346
|
+
*/
|
|
347
|
+
export function buildSelectorPath(basePath, ...segments) {
|
|
348
|
+
return [...basePath, ...segments];
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Checks if two complex selectors are equivalent using the original algorithm
|
|
352
|
+
* Preserves exact combinator and component matching logic from find-extendable-locations.ts
|
|
353
|
+
*/
|
|
354
|
+
export function areComplexSelectorsEquivalent(a, b) {
|
|
355
|
+
if (a.value.length !== b.value.length) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
// Check each component matches
|
|
359
|
+
for (let i = 0; i < a.value.length; i++) {
|
|
360
|
+
const aComp = a.value[i];
|
|
361
|
+
const bComp = b.value[i];
|
|
362
|
+
if (!aComp || !bComp) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
// Both must be same type
|
|
366
|
+
if (isNode(aComp, 'Combinator') && isNode(bComp, 'Combinator')) {
|
|
367
|
+
if (aComp.value !== bComp.value) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else if (!isNode(aComp, 'Combinator') && !isNode(bComp, 'Combinator')) {
|
|
372
|
+
// Both are selectors - check equivalence
|
|
373
|
+
if (isNode(aComp, 'CompoundSelector') && isNode(bComp, 'CompoundSelector')) {
|
|
374
|
+
if (!areCompoundSelectorsEquivalent(aComp, bComp)) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else if (isNode(aComp, 'PseudoSelector') && aComp.value.name === ':is' && aComp.value.arg && isSelector(aComp.value.arg)) {
|
|
379
|
+
// Allow `:is(.a, .b)` to match `.a` (or any selector in its arg list) for complex selector equivalence.
|
|
380
|
+
const arg = aComp.value.arg;
|
|
381
|
+
if (isNode(arg, 'SelectorList')) {
|
|
382
|
+
const matchesAny = arg.value.some(sel => sel.valueOf() === bComp.valueOf());
|
|
383
|
+
if (!matchesAny) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
if (arg.valueOf() !== bComp.valueOf()) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (isNode(bComp, 'PseudoSelector') && bComp.value.name === ':is' && bComp.value.arg && isSelector(bComp.value.arg)) {
|
|
394
|
+
// Symmetric case: allow `.a` to match `:is(.a, .b)`
|
|
395
|
+
const arg = bComp.value.arg;
|
|
396
|
+
if (isNode(arg, 'SelectorList')) {
|
|
397
|
+
const matchesAny = arg.value.some(sel => sel.valueOf() === aComp.valueOf());
|
|
398
|
+
if (!matchesAny) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
if (arg.valueOf() !== aComp.valueOf()) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else if (aComp.valueOf() !== bComp.valueOf()) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// One is combinator, other is not
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Checks if two selectors are structurally equal (same type and content)
|
|
421
|
+
* This is different from valueOf() comparison which might do normalization
|
|
422
|
+
*/
|
|
423
|
+
export function isStructurallyEqual(a, b) {
|
|
424
|
+
// For pseudo-selectors, compare name and arguments first (before basic selector check)
|
|
425
|
+
if (isNode(a, 'PseudoSelector') && isNode(b, 'PseudoSelector')) {
|
|
426
|
+
if (a.value.name !== b.value.name) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const aArg = a.value.arg;
|
|
430
|
+
const bArg = b.value.arg;
|
|
431
|
+
// Both have no args
|
|
432
|
+
if (!aArg && !bArg) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
// One has arg, other doesn't
|
|
436
|
+
if (!aArg || !bArg) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
// Both have args - compare them recursively
|
|
440
|
+
if (isSelector(aArg) && isSelector(bArg)) {
|
|
441
|
+
return isStructurallyEqual(aArg, bArg);
|
|
442
|
+
}
|
|
443
|
+
// Fallback to valueOf comparison for other arg types (non-selector nodes)
|
|
444
|
+
return aArg.valueOf() === bArg.valueOf();
|
|
445
|
+
}
|
|
446
|
+
// For basic selectors (div, .foo, #bar) and other simple selectors, use valueOf comparison
|
|
447
|
+
if (isNode(a, 'SimpleSelector') && isNode(b, 'SimpleSelector')) {
|
|
448
|
+
return a.valueOf() === b.valueOf();
|
|
449
|
+
}
|
|
450
|
+
// For other selector types, use valueOf as fallback
|
|
451
|
+
// This handles compound, complex, and selector list comparisons
|
|
452
|
+
if (isNode(a, 'CompoundSelector') || isNode(a, 'ComplexSelector') || isNode(a, 'SelectorList')) {
|
|
453
|
+
return a.valueOf() === b.valueOf();
|
|
454
|
+
}
|
|
455
|
+
// Default fallback
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
function inferMatchScope(path, matchedNode) {
|
|
459
|
+
if (path.includes('arg')) {
|
|
460
|
+
return 'isArgument';
|
|
461
|
+
}
|
|
462
|
+
if (isNode(matchedNode, 'SelectorList')) {
|
|
463
|
+
return 'selectorList';
|
|
464
|
+
}
|
|
465
|
+
return 'root';
|
|
466
|
+
}
|
|
467
|
+
function withMatchScope(location) {
|
|
468
|
+
if (location.matchScope) {
|
|
469
|
+
return location;
|
|
470
|
+
}
|
|
471
|
+
location.matchScope = inferMatchScope(location.path, location.matchedNode);
|
|
472
|
+
return location;
|
|
473
|
+
}
|
|
474
|
+
// Performance optimization: Pre-allocated result cache
|
|
475
|
+
const EXACT_MATCH_CACHE = new WeakMap();
|
|
476
|
+
// General search result cache: WeakMap<target, Map<find, ExtendSearchResult>>
|
|
477
|
+
const SEARCH_RESULT_CACHE = new WeakMap();
|
|
478
|
+
const EMPTY_LOCATIONS = [];
|
|
479
|
+
/**
|
|
480
|
+
* Enhanced selector matching with 7-layer optimization system from matchSelectors
|
|
481
|
+
* Recursively searches a selector tree to find all locations where a target selector appears
|
|
482
|
+
* This is designed specifically for extend use cases with maximum performance
|
|
483
|
+
*
|
|
484
|
+
* @param target - The selector tree to search within
|
|
485
|
+
* @param find - The selector pattern to find
|
|
486
|
+
* @returns ExtendSearchResult with all found locations and performance optimizations
|
|
487
|
+
*/
|
|
488
|
+
export function findExtendableLocations(target, find) {
|
|
489
|
+
// Check general search result cache first
|
|
490
|
+
let targetCache = SEARCH_RESULT_CACHE.get(target);
|
|
491
|
+
if (targetCache) {
|
|
492
|
+
const cached = targetCache.get(find);
|
|
493
|
+
if (cached) {
|
|
494
|
+
return cached;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
targetCache = new Map();
|
|
499
|
+
SEARCH_RESULT_CACHE.set(target, targetCache);
|
|
500
|
+
}
|
|
501
|
+
const locations = [];
|
|
502
|
+
const metrics = { fastRejections: 0, fastPathHits: 0, fullSearches: 0 };
|
|
503
|
+
// OPTIMIZATION 1: Exact match cache for identical selectors
|
|
504
|
+
const targetValue = target.valueOf();
|
|
505
|
+
const findValue = find.valueOf();
|
|
506
|
+
if (targetValue === findValue) {
|
|
507
|
+
const cached = EXACT_MATCH_CACHE.get(target);
|
|
508
|
+
if (cached) {
|
|
509
|
+
const result = { locations: cached, hasMatches: cached.length > 0, hasWholeMatch: true, metrics };
|
|
510
|
+
targetCache.set(find, result);
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
// Cache the exact match result
|
|
514
|
+
const exactLocation = withMatchScope({
|
|
515
|
+
path: [],
|
|
516
|
+
matchedNode: target,
|
|
517
|
+
extensionType: 'replace'
|
|
518
|
+
});
|
|
519
|
+
EXACT_MATCH_CACHE.set(target, [exactLocation]);
|
|
520
|
+
const result = { locations: [exactLocation], hasMatches: true, hasWholeMatch: true, metrics };
|
|
521
|
+
targetCache.set(find, result);
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
// OPTIMIZATION 2: KeySet fast rejection - bail early for impossible matches
|
|
525
|
+
if (target.keySet && find.keySet
|
|
526
|
+
&& target.keySet.isDisjointFrom(find.keySet)
|
|
527
|
+
&& target.canFastReject && find.canFastReject) {
|
|
528
|
+
metrics.fastRejections++;
|
|
529
|
+
const result = { locations: EMPTY_LOCATIONS, hasMatches: false, hasWholeMatch: false, metrics };
|
|
530
|
+
targetCache.set(find, result);
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
// OPTIMIZATION 3: KeySet subset rejection for partial matching
|
|
534
|
+
if (find.canFastReject && target.keySet && find.keySet
|
|
535
|
+
&& !find.keySet.isSubsetOf(target.keySet)) {
|
|
536
|
+
metrics.fastRejections++;
|
|
537
|
+
const result = { locations: EMPTY_LOCATIONS, hasMatches: false, hasWholeMatch: false, metrics };
|
|
538
|
+
targetCache.set(find, result);
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
// OPTIMIZATION 4: Fast path for common selector patterns - runs first and skips slow path when successful
|
|
542
|
+
// Special case: Handle SelectorList in find parameter regardless of canFastReject
|
|
543
|
+
if (isNode(find, 'SelectorList')) {
|
|
544
|
+
// Check if target matches any item in the find list
|
|
545
|
+
for (let i = 0; i < find.value.length; i++) {
|
|
546
|
+
const listItem = find.value[i];
|
|
547
|
+
const result = findExtendableLocations(target, listItem);
|
|
548
|
+
if (result.hasMatches) {
|
|
549
|
+
targetCache.set(find, result);
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const result = { locations: EMPTY_LOCATIONS, hasMatches: false, hasWholeMatch: false, metrics };
|
|
554
|
+
targetCache.set(find, result);
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
if (target.canFastReject && find.canFastReject) {
|
|
558
|
+
const fastPathResult = tryFastPathExtendMatch(target, find, []);
|
|
559
|
+
if (fastPathResult && fastPathResult.length > 0) {
|
|
560
|
+
metrics.fastPathHits++;
|
|
561
|
+
const hasWholeMatch = fastPathResult.some(loc => loc.path.length === 0 && loc.matchedNode === target);
|
|
562
|
+
const result = { locations: fastPathResult, hasMatches: true, hasWholeMatch, metrics };
|
|
563
|
+
targetCache.set(find, result);
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Full recursive search with optimizations - only when fast path fails
|
|
568
|
+
metrics.fullSearches++;
|
|
569
|
+
searchWithinSelector(target, find, [], locations);
|
|
570
|
+
const hasWholeMatch = locations.some(loc => loc.path.length === 0 && loc.matchedNode === target);
|
|
571
|
+
const result = {
|
|
572
|
+
locations,
|
|
573
|
+
hasMatches: locations.length > 0,
|
|
574
|
+
hasWholeMatch,
|
|
575
|
+
metrics
|
|
576
|
+
};
|
|
577
|
+
targetCache.set(find, result);
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Whether a ruleset's selector matches an extend target. Encapsulates all extend matching
|
|
582
|
+
* semantics (keySet subset, valueOf early exit, partial vs exact). Extend-roots should
|
|
583
|
+
* only decide which rulesets are visible; they hand off to this to determine matches.
|
|
584
|
+
*/
|
|
585
|
+
export function selectorMatchesExtendTarget(selector, target, partial) {
|
|
586
|
+
const keySet = target.keySet instanceof Set ? target.keySet : (target.keySet ? new Set(target.keySet) : undefined);
|
|
587
|
+
if (keySet?.size && 'keySet' in selector && selector.keySet) {
|
|
588
|
+
for (const k of keySet) {
|
|
589
|
+
if (!selector.keySet.has(k)) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const targetValue = target.valueOf();
|
|
595
|
+
if (typeof selector.valueOf === 'function' && selector.valueOf() === targetValue) {
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
if (isNode(selector, 'SelectorList')) {
|
|
599
|
+
return selector.value.some((item) => {
|
|
600
|
+
const comparison = selectorCompare(item, target);
|
|
601
|
+
return partial ? comparison.locations.length > 0 : comparison.hasWholeMatch;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const comparison = selectorCompare(selector, target);
|
|
605
|
+
return partial ? comparison.locations.length > 0 : comparison.hasWholeMatch;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* OPTIMIZATION 4: Fast path extend matching for common patterns
|
|
609
|
+
* Handles the most frequent selector types in typical stylesheets with optimized logic
|
|
610
|
+
* Now comprehensive enough to skip slow path for most common cases
|
|
611
|
+
*/
|
|
612
|
+
function tryFastPathExtendMatch(target, find, basePath) {
|
|
613
|
+
// Fast path 1: Exact match (most common case)
|
|
614
|
+
if (target.valueOf() === find.valueOf()) {
|
|
615
|
+
return [withMatchScope({
|
|
616
|
+
path: [...basePath],
|
|
617
|
+
matchedNode: target,
|
|
618
|
+
extensionType: determineExtensionType(target, basePath)
|
|
619
|
+
})];
|
|
620
|
+
}
|
|
621
|
+
// Fast path 2: Simple selector to simple selector (.foo === .foo)
|
|
622
|
+
if (isNode(target, 'SimpleSelector') && isNode(find, 'SimpleSelector')) {
|
|
623
|
+
// Handle pseudo-selectors with selector arguments using enhanced equivalence
|
|
624
|
+
if (isNode(target, 'PseudoSelector') && isNode(find, 'PseudoSelector')
|
|
625
|
+
&& target.value.name === find.value.name
|
|
626
|
+
&& target.value.arg && isSelector(target.value.arg)
|
|
627
|
+
&& find.value.arg && isSelector(find.value.arg)) {
|
|
628
|
+
// Same pseudo-selector name with selector args - check if args are equivalent
|
|
629
|
+
if (areSelectorArgumentsEquivalent(target.value.arg, find.value.arg)) {
|
|
630
|
+
return [withMatchScope({
|
|
631
|
+
path: [...basePath],
|
|
632
|
+
matchedNode: target,
|
|
633
|
+
extensionType: determineExtensionType(target, basePath)
|
|
634
|
+
})];
|
|
635
|
+
}
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
if (target.valueOf() === find.valueOf()) {
|
|
639
|
+
return [withMatchScope({
|
|
640
|
+
path: [...basePath],
|
|
641
|
+
matchedNode: target,
|
|
642
|
+
extensionType: determineExtensionType(target, basePath)
|
|
643
|
+
})];
|
|
644
|
+
}
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
// Fast path 3: Compound selector containing simple target (.foo.bar contains .foo)
|
|
648
|
+
if (isNode(target, 'CompoundSelector') && isNode(find, 'SimpleSelector') && target.value.length <= 4) {
|
|
649
|
+
// Skip pseudo-selectors with Selector arguments
|
|
650
|
+
if (isNode(find, 'PseudoSelector') && find.value.arg && isSelector(find.value.arg)) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
const findVal = find.valueOf();
|
|
654
|
+
const locations = [];
|
|
655
|
+
for (let i = 0; i < target.value.length; i++) {
|
|
656
|
+
if (target.value[i].valueOf() === findVal) {
|
|
657
|
+
// Found exact match - this enables partial replacement
|
|
658
|
+
const remainderComponents = target.value.filter((_, idx) => idx !== i);
|
|
659
|
+
const remainders = remainderComponents.length === 0
|
|
660
|
+
? []
|
|
661
|
+
: remainderComponents.length === 1
|
|
662
|
+
? [remainderComponents[0]]
|
|
663
|
+
: [new CompoundSelector(remainderComponents).inherit(target)];
|
|
664
|
+
locations.push(withMatchScope({
|
|
665
|
+
path: [...basePath, i],
|
|
666
|
+
matchedNode: target,
|
|
667
|
+
extensionType: determineExtensionType(target, basePath),
|
|
668
|
+
isPartialMatch: remainders.length > 0,
|
|
669
|
+
remainders
|
|
670
|
+
}));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return locations;
|
|
674
|
+
}
|
|
675
|
+
// Fast path 4: Small compound to compound matching (.a.b === .b.a)
|
|
676
|
+
if (isNode(target, 'CompoundSelector') && isNode(find, 'CompoundSelector')
|
|
677
|
+
&& target.value.length <= 4 && find.value.length <= 4) {
|
|
678
|
+
return trySmallCompoundExtendMatch(target, find, basePath);
|
|
679
|
+
}
|
|
680
|
+
// Fast path 5: When find parameter is a selector list (legacy match-selector behavior)
|
|
681
|
+
// Handles matchSelectors(target=".a", find=".a,.b") → should match because .a is in the list
|
|
682
|
+
if (isNode(find, 'SelectorList')) {
|
|
683
|
+
// Check if target matches any item in the find list
|
|
684
|
+
for (let i = 0; i < find.value.length; i++) {
|
|
685
|
+
const listItem = find.value[i];
|
|
686
|
+
const result = tryFastPathExtendMatch(target, listItem, basePath);
|
|
687
|
+
if (result && result.length > 0) {
|
|
688
|
+
// Found a match with one of the list items
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return []; // No matches found in list
|
|
693
|
+
}
|
|
694
|
+
// Fast path 6: Small selector list containing target
|
|
695
|
+
if (isNode(target, 'SelectorList') && target.value.length <= 3) {
|
|
696
|
+
const locations = [];
|
|
697
|
+
for (let i = 0; i < target.value.length; i++) {
|
|
698
|
+
const childResult = tryFastPathExtendMatch(target.value[i], find, [...basePath, i]);
|
|
699
|
+
if (childResult) {
|
|
700
|
+
locations.push(...childResult);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return locations.length > 0 ? locations : [];
|
|
704
|
+
}
|
|
705
|
+
// Fast path 7: Complex selector patterns with partial match support
|
|
706
|
+
if (isNode(target, 'ComplexSelector') && target.value.length <= 7) {
|
|
707
|
+
// First check for exact complex selector matches
|
|
708
|
+
if (isNode(find, 'ComplexSelector')) {
|
|
709
|
+
const eq = areComplexSelectorsEquivalent(target, find);
|
|
710
|
+
if (eq) {
|
|
711
|
+
return [withMatchScope({
|
|
712
|
+
path: [...basePath],
|
|
713
|
+
matchedNode: target,
|
|
714
|
+
extensionType: determineExtensionType(target, basePath)
|
|
715
|
+
})];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// Try partial complex matching
|
|
719
|
+
if (isNode(find, 'ComplexSelector')) {
|
|
720
|
+
const partialResult = tryPartialComplexMatch(target, find, basePath);
|
|
721
|
+
if (partialResult && partialResult.length > 0) {
|
|
722
|
+
return partialResult;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// Try backtracking match for complex :is() scenarios
|
|
726
|
+
if (isNode(find, 'ComplexSelector')) {
|
|
727
|
+
const backtrackResult = tryBacktrackingComplexMatch(target, find, basePath);
|
|
728
|
+
if (backtrackResult) {
|
|
729
|
+
return backtrackResult;
|
|
730
|
+
}
|
|
731
|
+
// Try sequential complex matching with partial compound support
|
|
732
|
+
const sequentialResult = trySequentialComplexMatch(target, find, basePath);
|
|
733
|
+
if (sequentialResult) {
|
|
734
|
+
return sequentialResult;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// Try individual component matching
|
|
738
|
+
const locations = [];
|
|
739
|
+
for (let i = 0; i < target.value.length; i++) {
|
|
740
|
+
const component = target.value[i];
|
|
741
|
+
if (component && !isNode(component, 'Combinator')) {
|
|
742
|
+
const childResult = tryFastPathExtendMatch(component, find, [...basePath, i]);
|
|
743
|
+
if (childResult) {
|
|
744
|
+
locations.push(...childResult);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Post-process: when find matches one component of a multi-component complex selector,
|
|
749
|
+
// that is always a partial match (full mode should reject it). Mark ALL such component
|
|
750
|
+
// matches as partial, not just position 0.
|
|
751
|
+
if (locations.length > 0 && target.value.length > 1) {
|
|
752
|
+
for (const location of locations) {
|
|
753
|
+
const lastSeg = location.path[location.path.length - 1];
|
|
754
|
+
if (typeof lastSeg === 'number') {
|
|
755
|
+
// Match is inside a component of this complex selector
|
|
756
|
+
location.isPartialMatch = true;
|
|
757
|
+
if (lastSeg === 0) {
|
|
758
|
+
const remainingComponents = target.value.slice(1);
|
|
759
|
+
location.remainders = remainingComponents.length === 1 && !isNode(remainingComponents[0], 'Combinator')
|
|
760
|
+
? [remainingComponents[0]]
|
|
761
|
+
: [new ComplexSelector(remainingComponents).inherit(target)];
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return locations.length > 0 ? locations : null;
|
|
767
|
+
}
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Tries to match partial complex selectors
|
|
772
|
+
*/
|
|
773
|
+
function tryPartialComplexMatch(target, find, basePath) {
|
|
774
|
+
const targetComponents = target.value;
|
|
775
|
+
const findComponents = find.value;
|
|
776
|
+
if (findComponents.length > targetComponents.length) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
// Try to match find at different positions (allow compound superset: .a.c.b contains .a.b)
|
|
780
|
+
for (let startPos = 0; startPos <= targetComponents.length - findComponents.length; startPos++) {
|
|
781
|
+
let matches = true;
|
|
782
|
+
let hasCompoundPartialMatch = false;
|
|
783
|
+
for (let i = 0; i < findComponents.length; i++) {
|
|
784
|
+
const tComp = targetComponents[startPos + i];
|
|
785
|
+
const fComp = findComponents[i];
|
|
786
|
+
if (!tComp || !fComp) {
|
|
787
|
+
matches = false;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
if (isNode(tComp, 'Combinator') && isNode(fComp, 'Combinator')) {
|
|
791
|
+
if (tComp.value !== fComp.value) {
|
|
792
|
+
matches = false;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else if (!isNode(tComp, 'Combinator') && !isNode(fComp, 'Combinator')) {
|
|
797
|
+
let compMatch = componentsMatch(tComp, fComp);
|
|
798
|
+
// Compound superset: target compound can contain find compound as subsequence (.a.c.b contains .a.b)
|
|
799
|
+
if (!compMatch && isNode(tComp, 'CompoundSelector') && isNode(fComp, 'CompoundSelector')) {
|
|
800
|
+
compMatch = compoundContainsCompoundSubsequence(tComp, fComp);
|
|
801
|
+
}
|
|
802
|
+
// Simple in compound: .x in .y.x
|
|
803
|
+
if (!compMatch && isNode(tComp, 'CompoundSelector') && isNode(fComp, 'SimpleSelector')) {
|
|
804
|
+
compMatch = tComp.value.some((c) => c.valueOf() === fComp.valueOf());
|
|
805
|
+
}
|
|
806
|
+
if (compMatch && isNode(tComp, 'CompoundSelector') && isNode(fComp, 'SimpleSelector')) {
|
|
807
|
+
hasCompoundPartialMatch = true;
|
|
808
|
+
}
|
|
809
|
+
if (compMatch && isNode(tComp, 'CompoundSelector') && isNode(fComp, 'CompoundSelector') && tComp.value.length > fComp.value.length) {
|
|
810
|
+
hasCompoundPartialMatch = true;
|
|
811
|
+
}
|
|
812
|
+
if (!compMatch) {
|
|
813
|
+
matches = false;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
matches = false;
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (matches) {
|
|
823
|
+
// Calculate remainders
|
|
824
|
+
const beforeComponents = targetComponents.slice(0, startPos);
|
|
825
|
+
const afterComponents = targetComponents.slice(startPos + findComponents.length);
|
|
826
|
+
const remainders = [];
|
|
827
|
+
if (beforeComponents.length > 0) {
|
|
828
|
+
remainders.push(new ComplexSelector(beforeComponents).inherit(target));
|
|
829
|
+
}
|
|
830
|
+
if (afterComponents.length > 0) {
|
|
831
|
+
remainders.push(new ComplexSelector(afterComponents).inherit(target));
|
|
832
|
+
}
|
|
833
|
+
// Mark as partial if we have remainders OR if there was a compound partial match
|
|
834
|
+
const isPartialMatch = remainders.length > 0 || hasCompoundPartialMatch;
|
|
835
|
+
const loc = {
|
|
836
|
+
path: [...basePath],
|
|
837
|
+
matchedNode: target,
|
|
838
|
+
extensionType: 'replace',
|
|
839
|
+
isPartialMatch,
|
|
840
|
+
remainders: remainders.length > 0 ? remainders : undefined
|
|
841
|
+
};
|
|
842
|
+
// Segment range for §3a: wrap full segment when match spans combinator
|
|
843
|
+
if (remainders.length > 0) {
|
|
844
|
+
loc.complexMatchRange = [startPos, startPos + findComponents.length];
|
|
845
|
+
}
|
|
846
|
+
return [withMatchScope(loc)];
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Optimized compound selector matching for small compounds
|
|
853
|
+
*/
|
|
854
|
+
function trySmallCompoundExtendMatch(target, find, basePath) {
|
|
855
|
+
// Check for exact equivalence (order-independent)
|
|
856
|
+
if (areCompoundSelectorsEquivalent(target, find)) {
|
|
857
|
+
return [withMatchScope({
|
|
858
|
+
path: [...basePath],
|
|
859
|
+
matchedNode: target,
|
|
860
|
+
extensionType: determineExtensionType(target, basePath)
|
|
861
|
+
})];
|
|
862
|
+
}
|
|
863
|
+
// Check for subset matching (find is subset of target)
|
|
864
|
+
if (find.value.length <= target.value.length) {
|
|
865
|
+
const isSubset = find.value.every((findComp) => target.value.some((targetComp) => isNode(findComp, 'PseudoSelector') && findComp.value.arg && isSelector(findComp.value.arg)
|
|
866
|
+
? arePseudoSelectorsEquivalent(targetComp, findComp)
|
|
867
|
+
: targetComp.valueOf() === findComp.valueOf()));
|
|
868
|
+
if (isSubset) {
|
|
869
|
+
// Find contiguous slice [start, end) that matches find in order (for wrap :is(matched, extendWith).rest)
|
|
870
|
+
const n = find.value.length;
|
|
871
|
+
let contiguousStart = null;
|
|
872
|
+
for (let start = 0; start <= target.value.length - n; start++) {
|
|
873
|
+
let match = true;
|
|
874
|
+
for (let j = 0; j < n; j++) {
|
|
875
|
+
const tComp = target.value[start + j];
|
|
876
|
+
const fComp = find.value[j];
|
|
877
|
+
if (!tComp || !fComp) {
|
|
878
|
+
match = false;
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
const eq = isNode(fComp, 'PseudoSelector') && fComp.value.arg && isSelector(fComp.value.arg)
|
|
882
|
+
? arePseudoSelectorsEquivalent(tComp, fComp)
|
|
883
|
+
: tComp.valueOf() === fComp.valueOf();
|
|
884
|
+
if (!eq) {
|
|
885
|
+
match = false;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (match) {
|
|
890
|
+
contiguousStart = start;
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
// Calculate remainder after removing matched components
|
|
895
|
+
const remainderComponents = target.value.filter((targetComp) => !find.value.some((findComp) => isNode(findComp, 'PseudoSelector') && findComp.value.arg && isSelector(findComp.value.arg)
|
|
896
|
+
? arePseudoSelectorsEquivalent(targetComp, findComp)
|
|
897
|
+
: targetComp.valueOf() === findComp.valueOf()));
|
|
898
|
+
const remainders = remainderComponents.length === 0
|
|
899
|
+
? []
|
|
900
|
+
: remainderComponents.length === 1
|
|
901
|
+
? [remainderComponents[0]]
|
|
902
|
+
: [new CompoundSelector(remainderComponents).inherit(target)];
|
|
903
|
+
const loc = {
|
|
904
|
+
path: [...basePath],
|
|
905
|
+
matchedNode: target,
|
|
906
|
+
extensionType: determineExtensionType(target, basePath),
|
|
907
|
+
isPartialMatch: remainders.length > 0,
|
|
908
|
+
remainders
|
|
909
|
+
};
|
|
910
|
+
// When find is a contiguous slice, record range so we can wrap that slice as :is(find, extendWith)
|
|
911
|
+
if (contiguousStart !== null && remainders.length > 0) {
|
|
912
|
+
loc.contiguousCompoundRange = [contiguousStart, contiguousStart + n];
|
|
913
|
+
loc.matchedNode = new CompoundSelector(find.value.slice()).inherit(target);
|
|
914
|
+
loc.extensionType = 'wrap';
|
|
915
|
+
}
|
|
916
|
+
else if (remainders.length > 0) {
|
|
917
|
+
// Non-contiguous: find leftmost subsequence of target indices that matches find in order
|
|
918
|
+
const matchIndices = [];
|
|
919
|
+
let findIdx = 0;
|
|
920
|
+
for (let i = 0; i < target.value.length && findIdx < find.value.length; i++) {
|
|
921
|
+
const tComp = target.value[i];
|
|
922
|
+
const fComp = find.value[findIdx];
|
|
923
|
+
const eq = isNode(fComp, 'PseudoSelector') && fComp.value.arg && isSelector(fComp.value.arg)
|
|
924
|
+
? arePseudoSelectorsEquivalent(tComp, fComp)
|
|
925
|
+
: tComp.valueOf() === fComp.valueOf();
|
|
926
|
+
if (eq) {
|
|
927
|
+
matchIndices.push(i);
|
|
928
|
+
findIdx++;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (matchIndices.length === find.value.length) {
|
|
932
|
+
loc.compoundMatchIndices = matchIndices;
|
|
933
|
+
loc.matchedNode = new CompoundSelector(find.value.slice()).inherit(target);
|
|
934
|
+
loc.extensionType = 'wrap';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return [withMatchScope(loc)];
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return [];
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Enhanced recursive search with :is() backtracking and optimization layers
|
|
944
|
+
* @param current - Current selector being examined
|
|
945
|
+
* @param target - Target selector to find
|
|
946
|
+
* @param currentPath - Current path in the selector tree
|
|
947
|
+
* @param locations - Array to collect found locations
|
|
948
|
+
*/
|
|
949
|
+
function searchWithinSelector(current, target, currentPath, locations) {
|
|
950
|
+
// OPTIMIZATION 1: Check for exact match
|
|
951
|
+
if (current.valueOf() === target.valueOf()) {
|
|
952
|
+
locations.push(withMatchScope({
|
|
953
|
+
path: [...currentPath],
|
|
954
|
+
matchedNode: current,
|
|
955
|
+
extensionType: determineExtensionType(current, currentPath)
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
958
|
+
// OPTIMIZATION 2: Enhanced recursive search with specialized handlers for each selector type
|
|
959
|
+
if (isNode(current, 'SelectorList')) {
|
|
960
|
+
searchWithinSelectorList(current, target, currentPath, locations);
|
|
961
|
+
}
|
|
962
|
+
else if (isNode(current, 'CompoundSelector')) {
|
|
963
|
+
searchWithinCompoundSelector(current, target, currentPath, locations);
|
|
964
|
+
}
|
|
965
|
+
else if (isNode(current, 'ComplexSelector')) {
|
|
966
|
+
searchWithinComplexSelector(current, target, currentPath, locations);
|
|
967
|
+
}
|
|
968
|
+
else if (isNode(current, 'PseudoSelector')) {
|
|
969
|
+
// OPTIMIZATION 3: Special handling for :is() pseudo-selectors with backtracking
|
|
970
|
+
searchWithinPseudoSelector(current, target, currentPath, locations);
|
|
971
|
+
}
|
|
972
|
+
// SimpleSelector doesn't have nested content to search
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Searches within a selector list
|
|
976
|
+
*/
|
|
977
|
+
function searchWithinSelectorList(selectorList, target, currentPath, locations) {
|
|
978
|
+
selectorList.value.forEach((selector, index) => {
|
|
979
|
+
searchWithinSelector(selector, target, [...currentPath, index], locations);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Enhanced compound selector search with partial matching support
|
|
984
|
+
*/
|
|
985
|
+
function searchWithinCompoundSelector(compound, target, currentPath, locations) {
|
|
986
|
+
// Handle when target is a PseudoSelector - check for equivalent matches
|
|
987
|
+
if (isNode(target, 'PseudoSelector') && target.value.arg && isSelector(target.value.arg)) {
|
|
988
|
+
// Look for matching pseudo-selectors within the compound
|
|
989
|
+
compound.value.forEach((component, index) => {
|
|
990
|
+
if (isNode(component, 'PseudoSelector') && arePseudoSelectorsEquivalent(component, target)) {
|
|
991
|
+
locations.push(withMatchScope({
|
|
992
|
+
path: [...currentPath, index],
|
|
993
|
+
matchedNode: component,
|
|
994
|
+
extensionType: 'replace'
|
|
995
|
+
}));
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
// Standard recursive search through each component
|
|
1000
|
+
compound.value.forEach((component, index) => {
|
|
1001
|
+
searchWithinSelector(component, target, [...currentPath, index], locations);
|
|
1002
|
+
});
|
|
1003
|
+
// OPTIMIZATION 5: Check for partial matches within compound selectors
|
|
1004
|
+
// This enables extending when target is a subset of the compound
|
|
1005
|
+
if (isNode(target, 'SimpleSelector')) {
|
|
1006
|
+
const targetVal = target.valueOf();
|
|
1007
|
+
for (let i = 0; i < compound.value.length; i++) {
|
|
1008
|
+
if (compound.value[i].valueOf() === targetVal) {
|
|
1009
|
+
// Found a component that matches target - create partial match
|
|
1010
|
+
// Use unique path with component index to distinguish duplicate components
|
|
1011
|
+
const remainderComponents = compound.value.filter((_, idx) => idx !== i);
|
|
1012
|
+
const remainders = remainderComponents.length === 0
|
|
1013
|
+
? []
|
|
1014
|
+
: remainderComponents.length === 1
|
|
1015
|
+
? [remainderComponents[0]]
|
|
1016
|
+
: [new CompoundSelector(remainderComponents).inherit(compound)];
|
|
1017
|
+
locations.push(withMatchScope({
|
|
1018
|
+
path: [...currentPath, i],
|
|
1019
|
+
matchedNode: compound.value[i],
|
|
1020
|
+
extensionType: 'replace',
|
|
1021
|
+
isPartialMatch: remainders.length > 0,
|
|
1022
|
+
remainders
|
|
1023
|
+
}));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// OPTIMIZATION 6: Compound-to-compound partial matching
|
|
1028
|
+
if (isNode(target, 'CompoundSelector') && target.value.length <= compound.value.length) {
|
|
1029
|
+
const isSubset = target.value.every(targetComp => compound.value.some(compComp => isNode(targetComp, 'PseudoSelector') && targetComp.value.arg && isSelector(targetComp.value.arg)
|
|
1030
|
+
? arePseudoSelectorsEquivalent(compComp, targetComp)
|
|
1031
|
+
: compComp.valueOf() === targetComp.valueOf()));
|
|
1032
|
+
if (isSubset) {
|
|
1033
|
+
// Calculate remainder after removing matched components
|
|
1034
|
+
const remainderComponents = compound.value.filter(compComp => !target.value.some(targetComp => isNode(targetComp, 'PseudoSelector') && targetComp.value.arg && isSelector(targetComp.value.arg)
|
|
1035
|
+
? arePseudoSelectorsEquivalent(compComp, targetComp)
|
|
1036
|
+
: compComp.valueOf() === targetComp.valueOf()));
|
|
1037
|
+
const remainders = remainderComponents.length === 0
|
|
1038
|
+
? []
|
|
1039
|
+
: remainderComponents.length === 1
|
|
1040
|
+
? [remainderComponents[0]]
|
|
1041
|
+
: [new CompoundSelector(remainderComponents).inherit(compound)];
|
|
1042
|
+
locations.push(withMatchScope({
|
|
1043
|
+
path: [...currentPath],
|
|
1044
|
+
matchedNode: target,
|
|
1045
|
+
extensionType: 'replace',
|
|
1046
|
+
isPartialMatch: remainders.length > 0,
|
|
1047
|
+
remainders
|
|
1048
|
+
}));
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Enhanced complex selector search with combinator-aware optimizations
|
|
1054
|
+
*/
|
|
1055
|
+
function searchWithinComplexSelector(complex, target, currentPath, locations) {
|
|
1056
|
+
const initialLocationCount = locations.length;
|
|
1057
|
+
// If we're searching for a ComplexSelector target, allow full structural equivalence (including `:is(...)`).
|
|
1058
|
+
if (isNode(target, 'ComplexSelector')) {
|
|
1059
|
+
const eq = areComplexSelectorsEquivalent(complex, target);
|
|
1060
|
+
if (eq) {
|
|
1061
|
+
locations.push(withMatchScope({
|
|
1062
|
+
path: [...currentPath],
|
|
1063
|
+
matchedNode: complex,
|
|
1064
|
+
extensionType: determineExtensionType(complex, currentPath)
|
|
1065
|
+
}));
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
complex.value.forEach((component, index) => {
|
|
1069
|
+
// Skip combinators, only search selector components
|
|
1070
|
+
if (!isNode(component, 'Combinator')) {
|
|
1071
|
+
searchWithinSelector(component, target, [...currentPath, index], locations);
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
// Post-process: when find matches one component of a multi-component complex selector,
|
|
1075
|
+
// that is always a partial match (full mode should reject it). Mark ALL such component
|
|
1076
|
+
// matches as partial, not just position 0.
|
|
1077
|
+
if (locations.length > initialLocationCount && complex.value.length > 1) {
|
|
1078
|
+
for (let i = initialLocationCount; i < locations.length; i++) {
|
|
1079
|
+
const location = locations[i];
|
|
1080
|
+
const lastPathSegment = location.path[location.path.length - 1];
|
|
1081
|
+
if (typeof lastPathSegment === 'number') {
|
|
1082
|
+
// Match is inside a component of this complex selector
|
|
1083
|
+
location.isPartialMatch = true;
|
|
1084
|
+
if (lastPathSegment === 0) {
|
|
1085
|
+
const remainingComponents = complex.value.slice(1);
|
|
1086
|
+
if (remainingComponents.length === 1 && !isNode(remainingComponents[0], 'Combinator')) {
|
|
1087
|
+
location.remainders = [remainingComponents[0]];
|
|
1088
|
+
}
|
|
1089
|
+
else if (remainingComponents.length > 0) {
|
|
1090
|
+
location.remainders = [new ComplexSelector(remainingComponents).inherit(complex)];
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
// OPTIMIZATION 8: Complex selector pattern matching
|
|
1097
|
+
// Handle common patterns like descendant, child, sibling selectors efficiently
|
|
1098
|
+
if (isNode(target, 'ComplexSelector')) {
|
|
1099
|
+
// Check for structural matches within complex selector patterns
|
|
1100
|
+
// This enables extending complex selectors that contain the target pattern
|
|
1101
|
+
tryComplexSelectorPatternMatch(complex, target, currentPath, locations);
|
|
1102
|
+
// Try backtracking match for :is() scenarios
|
|
1103
|
+
const backtrackResult = tryBacktrackingComplexMatch(complex, target, currentPath);
|
|
1104
|
+
if (backtrackResult) {
|
|
1105
|
+
locations.push(...backtrackResult);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Attempts to find pattern matches within complex selectors
|
|
1111
|
+
* Handles common CSS combinator patterns with optimized matching
|
|
1112
|
+
*/
|
|
1113
|
+
function tryComplexSelectorPatternMatch(complex, target, currentPath, locations) {
|
|
1114
|
+
// Enhanced pattern matching for cross-boundary matches
|
|
1115
|
+
// Example: .a > .b should match within .a > .b.c
|
|
1116
|
+
if (complex.value.length < target.value.length) {
|
|
1117
|
+
return; // Complex selector must be at least as long as target
|
|
1118
|
+
}
|
|
1119
|
+
const targetComponents = target.value;
|
|
1120
|
+
const complexComponents = complex.value;
|
|
1121
|
+
// Try to match target pattern at different positions within complex selector
|
|
1122
|
+
for (let startPos = 0; startPos <= complexComponents.length - targetComponents.length; startPos++) {
|
|
1123
|
+
let isMatch = true;
|
|
1124
|
+
const remainingComponents = [];
|
|
1125
|
+
// Check if target matches at this position
|
|
1126
|
+
for (let i = 0; i < targetComponents.length; i++) {
|
|
1127
|
+
const targetComp = targetComponents[i];
|
|
1128
|
+
const complexComp = complexComponents[startPos + i];
|
|
1129
|
+
if (!targetComp || !complexComp) {
|
|
1130
|
+
isMatch = false;
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
if (isNode(targetComp, 'Combinator') && isNode(complexComp, 'Combinator')) {
|
|
1134
|
+
// Both are combinators - must match exactly
|
|
1135
|
+
if (targetComp.value !== complexComp.value) {
|
|
1136
|
+
isMatch = false;
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else if (isNode(targetComp, 'Combinator') || isNode(complexComp, 'Combinator')) {
|
|
1141
|
+
// One is combinator, other is not - no match
|
|
1142
|
+
isMatch = false;
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
// Both are selector components
|
|
1147
|
+
if (isNode(complexComp, 'CompoundSelector') && !isNode(targetComp, 'CompoundSelector')) {
|
|
1148
|
+
// Complex component is compound, target is simple
|
|
1149
|
+
// Check if target component appears within the compound
|
|
1150
|
+
const foundInCompound = complexComp.value.some(comp => comp && componentsMatch(comp, targetComp));
|
|
1151
|
+
if (foundInCompound) {
|
|
1152
|
+
// Partial match - calculate remainder
|
|
1153
|
+
const remainderComps = complexComp.value.filter(comp => comp && !componentsMatch(comp, targetComp));
|
|
1154
|
+
if (remainderComps.length > 0) {
|
|
1155
|
+
const remainder = remainderComps.length === 1
|
|
1156
|
+
? remainderComps[0]
|
|
1157
|
+
: CompoundSelector.create(remainderComps).inherit(complexComp);
|
|
1158
|
+
remainingComponents.push(remainder);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
isMatch = false;
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
else if (!componentsMatch(targetComp, complexComp)) {
|
|
1167
|
+
isMatch = false;
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (isMatch) {
|
|
1173
|
+
// Found a match! Add remaining components from complex selector
|
|
1174
|
+
const postMatchComponents = complexComponents.slice(startPos + targetComponents.length);
|
|
1175
|
+
remainingComponents.push(...postMatchComponents);
|
|
1176
|
+
// Create remainder selector if there are remaining components
|
|
1177
|
+
let remainders = [];
|
|
1178
|
+
if (remainingComponents.length > 0) {
|
|
1179
|
+
if (remainingComponents.length === 1 && !isNode(remainingComponents[0], 'Combinator')) {
|
|
1180
|
+
remainders = [remainingComponents[0]];
|
|
1181
|
+
}
|
|
1182
|
+
else if (remainingComponents.length > 1) {
|
|
1183
|
+
remainders = [ComplexSelector.create(remainingComponents).inherit(complex)];
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
locations.push(withMatchScope({
|
|
1187
|
+
path: [...currentPath],
|
|
1188
|
+
matchedNode: target,
|
|
1189
|
+
extensionType: determineExtensionType(complex, currentPath),
|
|
1190
|
+
isPartialMatch: remainders.length > 0,
|
|
1191
|
+
remainders: remainders.length > 0 ? remainders : undefined
|
|
1192
|
+
}));
|
|
1193
|
+
// Only find the first match to avoid duplicates
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Add backtracking support for complex :is() scenarios
|
|
1200
|
+
* This handles cases like :is(.a > .b).d > .c matching .a > .b > .c
|
|
1201
|
+
* IMPORTANT: This must preserve combinator sequences for correct matching
|
|
1202
|
+
*/
|
|
1203
|
+
function trySequentialComplexMatch(target, // what to search within
|
|
1204
|
+
find, // what to find
|
|
1205
|
+
basePath) {
|
|
1206
|
+
// Don't strip combinators - we need to match the exact sequence
|
|
1207
|
+
const targetComponents = target.value;
|
|
1208
|
+
const findComponents = find.value;
|
|
1209
|
+
if (findComponents.length === 0 || targetComponents.length < findComponents.length) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
// Try to find a contiguous subsequence match that preserves combinator structure
|
|
1213
|
+
for (let startIdx = 0; startIdx <= targetComponents.length - findComponents.length; startIdx++) {
|
|
1214
|
+
let matches = true;
|
|
1215
|
+
// Check if the subsequence starting at startIdx matches the find pattern
|
|
1216
|
+
for (let i = 0; i < findComponents.length; i++) {
|
|
1217
|
+
const targetComp = targetComponents[startIdx + i];
|
|
1218
|
+
const findComp = findComponents[i];
|
|
1219
|
+
if (!targetComp || !findComp) {
|
|
1220
|
+
matches = false;
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
// Both must be same type (combinator vs selector)
|
|
1224
|
+
if (isNode(targetComp, 'Combinator') !== isNode(findComp, 'Combinator')) {
|
|
1225
|
+
matches = false;
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
// If both are combinators, they must match exactly
|
|
1229
|
+
if (isNode(targetComp, 'Combinator') && isNode(findComp, 'Combinator')) {
|
|
1230
|
+
if (targetComp.value !== findComp.value) {
|
|
1231
|
+
matches = false;
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
else if (!isNode(targetComp, 'Combinator') && !isNode(findComp, 'Combinator')) {
|
|
1236
|
+
// If both are selectors, use existing selector matching logic
|
|
1237
|
+
// But also check for partial compound matching
|
|
1238
|
+
let componentMatches = areSelectorArgumentsEquivalent(targetComp, findComp);
|
|
1239
|
+
if (!componentMatches) {
|
|
1240
|
+
// Check for partial compound matching: .b should match within .b.c
|
|
1241
|
+
if (isNode(targetComp, 'CompoundSelector') && isNode(findComp, 'SimpleSelector')) {
|
|
1242
|
+
componentMatches = targetComp.value.some(comp => comp.valueOf() === findComp.valueOf());
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (!componentMatches) {
|
|
1246
|
+
matches = false;
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (matches) {
|
|
1252
|
+
// Calculate what remains before and after the match
|
|
1253
|
+
const beforeComponents = targetComponents.slice(0, startIdx);
|
|
1254
|
+
const afterComponents = targetComponents.slice(startIdx + findComponents.length);
|
|
1255
|
+
const remainders = [];
|
|
1256
|
+
if (beforeComponents.length > 0) {
|
|
1257
|
+
remainders.push(new ComplexSelector(beforeComponents).inherit(target));
|
|
1258
|
+
}
|
|
1259
|
+
if (afterComponents.length > 0) {
|
|
1260
|
+
remainders.push(new ComplexSelector(afterComponents).inherit(target));
|
|
1261
|
+
}
|
|
1262
|
+
// Check for compound-level remainders within the matched components
|
|
1263
|
+
for (let i = 0; i < findComponents.length; i++) {
|
|
1264
|
+
const targetComp = targetComponents[startIdx + i];
|
|
1265
|
+
const findComp = findComponents[i];
|
|
1266
|
+
if (!isNode(targetComp, 'Combinator') && !isNode(findComp, 'Combinator')) {
|
|
1267
|
+
if (isNode(targetComp, 'CompoundSelector') && isNode(findComp, 'SimpleSelector')) {
|
|
1268
|
+
// Check if there's a partial match leaving compound remainders
|
|
1269
|
+
const matchingComponent = targetComp.value.find(comp => comp.valueOf() === findComp.valueOf());
|
|
1270
|
+
if (matchingComponent) {
|
|
1271
|
+
// Calculate remainder components within this compound
|
|
1272
|
+
const compoundRemainders = targetComp.value.filter(comp => comp.valueOf() !== findComp.valueOf());
|
|
1273
|
+
if (compoundRemainders.length > 0) {
|
|
1274
|
+
if (compoundRemainders.length === 1) {
|
|
1275
|
+
remainders.push(compoundRemainders[0]);
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
remainders.push(new CompoundSelector(compoundRemainders).inherit(targetComp));
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
const isPartialMatch = remainders.length > 0;
|
|
1286
|
+
return [{
|
|
1287
|
+
path: [...basePath],
|
|
1288
|
+
matchedNode: find,
|
|
1289
|
+
extensionType: determineExtensionType(target, basePath),
|
|
1290
|
+
isPartialMatch,
|
|
1291
|
+
remainders: remainders.length > 0 ? remainders : undefined
|
|
1292
|
+
}];
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
function tryBacktrackingComplexMatch(target, // what to search within
|
|
1298
|
+
find, // what to find
|
|
1299
|
+
basePath) {
|
|
1300
|
+
// Extract non-combinator components
|
|
1301
|
+
const targetComponents = target.value.filter(c => !isNode(c, 'Combinator'));
|
|
1302
|
+
const findComponents = find.value.filter(c => !isNode(c, 'Combinator'));
|
|
1303
|
+
if (findComponents.length === 0) {
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
// Special case: Check if target has a compound with :is() that can expand to match find
|
|
1307
|
+
for (let i = 0; i < targetComponents.length; i++) {
|
|
1308
|
+
const comp = targetComponents[i];
|
|
1309
|
+
if (isNode(comp, 'CompoundSelector')) {
|
|
1310
|
+
// Look for :is() pseudo-selectors in the compound
|
|
1311
|
+
const isPseudos = comp.value.filter(v => isNode(v, 'PseudoSelector') && v.value.name === ':is' && v.value.arg && isSelector(v.value.arg));
|
|
1312
|
+
for (const isPseudo of isPseudos) {
|
|
1313
|
+
const isArg = isPseudo.value.arg;
|
|
1314
|
+
// If :is() contains a complex selector
|
|
1315
|
+
if (isNode(isArg, 'ComplexSelector')) {
|
|
1316
|
+
// Get the :is() content components
|
|
1317
|
+
const isArgComponents = isArg.value.filter(c => !isNode(c, 'Combinator'));
|
|
1318
|
+
// Try to match the find pattern
|
|
1319
|
+
if (isArgComponents.length >= 2) {
|
|
1320
|
+
// Get the last component from :is() (e.g., .b from .a > .b)
|
|
1321
|
+
const lastIsComponent = isArgComponents[isArgComponents.length - 1];
|
|
1322
|
+
// Get other components in the compound (e.g., .d)
|
|
1323
|
+
const otherCompoundComponents = comp.value.filter(v => v !== isPseudo);
|
|
1324
|
+
// Check if find starts with the :is() pattern (improved structural matching)
|
|
1325
|
+
// Only check the prefix components, allowing structural compound matching for the last component
|
|
1326
|
+
let matchesIsPattern = true;
|
|
1327
|
+
for (let j = 0; j < isArgComponents.length - 1; j++) {
|
|
1328
|
+
if (j >= findComponents.length
|
|
1329
|
+
|| !componentsMatch(isArgComponents[j], findComponents[j])) {
|
|
1330
|
+
matchesIsPattern = false;
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (matchesIsPattern) {
|
|
1335
|
+
// Check if the last :is() component with compound additions matches the next target component
|
|
1336
|
+
const compoundWithIsLast = otherCompoundComponents.length > 0
|
|
1337
|
+
? new CompoundSelector([lastIsComponent, ...otherCompoundComponents])
|
|
1338
|
+
: lastIsComponent;
|
|
1339
|
+
const nextTargetIdx = isArgComponents.length - 1;
|
|
1340
|
+
// Special compound matching for backtracking: allow compound to match simple if simple is contained
|
|
1341
|
+
let compoundMatches = false;
|
|
1342
|
+
if (nextTargetIdx < findComponents.length) {
|
|
1343
|
+
const findComp = findComponents[nextTargetIdx];
|
|
1344
|
+
if (isNode(compoundWithIsLast, 'CompoundSelector') && isNode(findComp, 'SimpleSelector')) {
|
|
1345
|
+
// In improved structural semantics: compound matches simple if simple is contained
|
|
1346
|
+
const containsTarget = compoundWithIsLast.value.some(comp => comp.valueOf() === findComp.valueOf());
|
|
1347
|
+
if (containsTarget) {
|
|
1348
|
+
compoundMatches = true;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
compoundMatches = componentsMatch(compoundWithIsLast, findComp);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (compoundMatches) {
|
|
1356
|
+
// Check if remaining selector components match remaining target
|
|
1357
|
+
const targetRemaining = targetComponents.slice(i + 1);
|
|
1358
|
+
const findRemaining = findComponents.slice(isArgComponents.length);
|
|
1359
|
+
if (targetRemaining.length === findRemaining.length) {
|
|
1360
|
+
let allMatch = true;
|
|
1361
|
+
for (let k = 0; k < targetRemaining.length; k++) {
|
|
1362
|
+
if (!componentsMatch(targetRemaining[k], findRemaining[k])) {
|
|
1363
|
+
allMatch = false;
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (allMatch) {
|
|
1368
|
+
// We have a match!
|
|
1369
|
+
const location = {
|
|
1370
|
+
path: [...basePath],
|
|
1371
|
+
matchedNode: target,
|
|
1372
|
+
extensionType: 'replace',
|
|
1373
|
+
isPartialMatch: true,
|
|
1374
|
+
remainders: [] // Calculate proper remainders if needed
|
|
1375
|
+
};
|
|
1376
|
+
return [withMatchScope(location)];
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Enhanced pseudo-selector search with :is() backtracking optimization
|
|
1390
|
+
*/
|
|
1391
|
+
function searchWithinPseudoSelector(pseudo, target, currentPath, locations) {
|
|
1392
|
+
const arg = pseudo.value.arg;
|
|
1393
|
+
if (!arg || !isSelector(arg)) {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const argSelector = arg;
|
|
1397
|
+
// OPTIMIZATION 7: Special handling for :is() pseudo-selectors
|
|
1398
|
+
// Implements sophisticated right-to-left backtracking algorithm from matchSelectors
|
|
1399
|
+
if (pseudo.value.name === ':is') {
|
|
1400
|
+
if (isNode(argSelector, 'SelectorList')) {
|
|
1401
|
+
// Check if target matches any alternative in the :is() selector list
|
|
1402
|
+
argSelector.value.forEach((alternative, altIndex) => {
|
|
1403
|
+
const itemPath = [...currentPath, 'arg', altIndex];
|
|
1404
|
+
// Direct structural match: use determineExtensionType so we get 'wrap' when inside a compound (not just 'append')
|
|
1405
|
+
if (isStructurallyEqual(alternative, target)) {
|
|
1406
|
+
locations.push(withMatchScope({
|
|
1407
|
+
path: itemPath,
|
|
1408
|
+
matchedNode: alternative,
|
|
1409
|
+
extensionType: determineExtensionType(alternative, itemPath)
|
|
1410
|
+
}));
|
|
1411
|
+
}
|
|
1412
|
+
// Recursive search within each alternative
|
|
1413
|
+
searchWithinSelector(alternative, target, itemPath, locations);
|
|
1414
|
+
});
|
|
1415
|
+
// Additional optimization: Check if target could be added as new alternative
|
|
1416
|
+
// This enables extending :is(.a, .b) with .c to become :is(.a, .b, .c)
|
|
1417
|
+
const canExtendAsList = !argSelector.value.some(alt => isStructurallyEqual(alt, target));
|
|
1418
|
+
if (canExtendAsList) {
|
|
1419
|
+
locations.push(withMatchScope({
|
|
1420
|
+
path: [...currentPath, 'arg'],
|
|
1421
|
+
matchedNode: argSelector,
|
|
1422
|
+
extensionType: 'append', // Append new alternative to :is() list
|
|
1423
|
+
isPartialMatch: false
|
|
1424
|
+
}));
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
else {
|
|
1428
|
+
// Single argument in :is() - check for direct match
|
|
1429
|
+
if (isStructurallyEqual(argSelector, target)) {
|
|
1430
|
+
locations.push(withMatchScope({
|
|
1431
|
+
path: [...currentPath, 'arg'],
|
|
1432
|
+
matchedNode: argSelector,
|
|
1433
|
+
extensionType: 'append', // Will convert single arg to SelectorList and append
|
|
1434
|
+
isPartialMatch: false
|
|
1435
|
+
}));
|
|
1436
|
+
// Don't do recursive search since we found the direct match
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
// Only do recursive search if no direct match found
|
|
1440
|
+
searchWithinSelector(argSelector, target, [...currentPath, 'arg'], locations);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
// Standard recursive search for other pseudo-selectors
|
|
1445
|
+
searchWithinSelector(argSelector, target, [...currentPath, 'arg'], locations);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Normalizes a selector to handle :is() equivalences
|
|
1450
|
+
* This is the single source of truth for :is() expansion logic
|
|
1451
|
+
*
|
|
1452
|
+
* Examples:
|
|
1453
|
+
* - :is(.a) -> .a
|
|
1454
|
+
* - a :is(b, c) -> a b, a c (as SelectorList)
|
|
1455
|
+
* - :is(.foo, .bar) -> .foo, .bar (as SelectorList)
|
|
1456
|
+
*/
|
|
1457
|
+
function normalizeSelector(selector) {
|
|
1458
|
+
if (isNode(selector, 'PseudoSelector') && selector.value.name === ':is' && selector.value.arg) {
|
|
1459
|
+
const arg = selector.value.arg;
|
|
1460
|
+
if (isNode(arg, 'SimpleSelector')) {
|
|
1461
|
+
return arg;
|
|
1462
|
+
}
|
|
1463
|
+
if (isNode(arg, 'SelectorList')) {
|
|
1464
|
+
return arg;
|
|
1465
|
+
}
|
|
1466
|
+
return arg;
|
|
1467
|
+
}
|
|
1468
|
+
if (isNode(selector, 'ComplexSelector')) {
|
|
1469
|
+
const expanded = expandComplexSelectorWithIs(selector);
|
|
1470
|
+
if (expanded.length > 1) {
|
|
1471
|
+
return new SelectorList(expanded);
|
|
1472
|
+
}
|
|
1473
|
+
if (expanded.length === 1) {
|
|
1474
|
+
return expanded[0];
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (isNode(selector, 'SelectorList')) {
|
|
1478
|
+
const normalizedSelectors = [];
|
|
1479
|
+
for (const sel of selector.value) {
|
|
1480
|
+
const normalized = normalizeSelector(sel);
|
|
1481
|
+
if (isNode(normalized, 'SelectorList')) {
|
|
1482
|
+
normalizedSelectors.push(...normalized.value);
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
normalizedSelectors.push(normalized);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (normalizedSelectors.length === 1) {
|
|
1489
|
+
return normalizedSelectors[0];
|
|
1490
|
+
}
|
|
1491
|
+
return new SelectorList(normalizedSelectors);
|
|
1492
|
+
}
|
|
1493
|
+
return selector;
|
|
1494
|
+
}
|
|
1495
|
+
export function normalizeSelectorForExtend(selector) {
|
|
1496
|
+
return normalizeSelector(selector);
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Legacy matchSelectors function for backward compatibility
|
|
1500
|
+
* Maps to the new findExtendableLocations API
|
|
1501
|
+
*/
|
|
1502
|
+
export function matchSelectors(target, find, partial = false) {
|
|
1503
|
+
const normalizedTarget = normalizeSelector(target);
|
|
1504
|
+
const normalizedFind = normalizeSelector(find);
|
|
1505
|
+
if (normalizedTarget.valueOf() === normalizedFind.valueOf()) {
|
|
1506
|
+
return {
|
|
1507
|
+
hasMatch: true,
|
|
1508
|
+
hasFullMatch: true,
|
|
1509
|
+
hasPartialMatch: false,
|
|
1510
|
+
matched: [find],
|
|
1511
|
+
remainders: []
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
const searchResult = findExtendableLocations(normalizedTarget, normalizedFind);
|
|
1515
|
+
if (!searchResult.hasMatches) {
|
|
1516
|
+
return {
|
|
1517
|
+
hasMatch: false,
|
|
1518
|
+
hasFullMatch: false,
|
|
1519
|
+
hasPartialMatch: false,
|
|
1520
|
+
matched: [],
|
|
1521
|
+
remainders: []
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
const hasAnyPartialMatch = searchResult.locations.some((loc) => loc.isPartialMatch);
|
|
1525
|
+
const hasAnyFullMatch = searchResult.locations.some((loc) => !loc.isPartialMatch);
|
|
1526
|
+
const isPartialMatch = partial && (hasAnyPartialMatch || searchResult.locations.some((loc) => loc.remainders && loc.remainders.length > 0));
|
|
1527
|
+
return {
|
|
1528
|
+
hasMatch: true,
|
|
1529
|
+
hasFullMatch: hasAnyFullMatch && !isPartialMatch,
|
|
1530
|
+
hasPartialMatch: isPartialMatch,
|
|
1531
|
+
matched: hasAnyFullMatch && !isPartialMatch ? [find] : [],
|
|
1532
|
+
remainders: searchResult.locations[0]?.remainders || []
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
export function combineKeys(a, b) {
|
|
1536
|
+
if (a instanceof Set) {
|
|
1537
|
+
if (b instanceof Set) {
|
|
1538
|
+
return a.union(b);
|
|
1539
|
+
}
|
|
1540
|
+
else {
|
|
1541
|
+
return (new Set(a)).add(b);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
if (b instanceof Set) {
|
|
1546
|
+
return (new Set(b)).add(a);
|
|
1547
|
+
}
|
|
1548
|
+
else {
|
|
1549
|
+
return new Set([a, b]);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
export function selectorCompare(a, b, forwardSearch, backwardSearch) {
|
|
1554
|
+
const normalizedA = normalizeSelectorForExtend(a);
|
|
1555
|
+
const normalizedB = normalizeSelectorForExtend(b);
|
|
1556
|
+
if (isNode(normalizedA, 'SelectorList') && isNode(normalizedB, 'SelectorList')) {
|
|
1557
|
+
const aItems = normalizedA.value.map(item => normalizeSelectorForExtend(item).valueOf()).slice().sort();
|
|
1558
|
+
const bItems = normalizedB.value.map(item => normalizeSelectorForExtend(item).valueOf()).slice().sort();
|
|
1559
|
+
const equivalent = aItems.length === bItems.length && aItems.every((v, i) => v === bItems[i]);
|
|
1560
|
+
if (equivalent) {
|
|
1561
|
+
return {
|
|
1562
|
+
isEquivalent: true,
|
|
1563
|
+
hasWholeMatch: true,
|
|
1564
|
+
hasPartialMatch: false,
|
|
1565
|
+
locations: []
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const forward = forwardSearch ?? findExtendableLocations(normalizedA, normalizedB);
|
|
1570
|
+
const backward = backwardSearch ?? findExtendableLocations(normalizedB, normalizedA);
|
|
1571
|
+
return {
|
|
1572
|
+
isEquivalent: forward.hasWholeMatch && backward.hasWholeMatch,
|
|
1573
|
+
hasWholeMatch: forward.hasWholeMatch,
|
|
1574
|
+
hasPartialMatch: forward.hasMatches && !forward.hasWholeMatch,
|
|
1575
|
+
locations: forward.locations
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
//# sourceMappingURL=selector-match-core.js.map
|