@spiracss/stylelint-plugin 0.2.2 → 0.3.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/README.md +14 -18
- package/dist/cjs/helpers.d.ts +117 -34
- package/dist/cjs/helpers.d.ts.map +1 -1
- package/dist/cjs/helpers.js +297 -171
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.js +96 -40
- package/dist/cjs/rules/spiracss-class-structure.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.messages.d.ts +10 -8
- package/dist/cjs/rules/spiracss-class-structure.messages.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.messages.js +59 -26
- package/dist/cjs/rules/spiracss-class-structure.messages.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.options.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.options.js +71 -56
- package/dist/cjs/rules/spiracss-class-structure.options.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.patterns.d.ts +4 -4
- package/dist/cjs/rules/spiracss-class-structure.patterns.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.patterns.js +24 -27
- package/dist/cjs/rules/spiracss-class-structure.patterns.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.selectors.d.ts +3 -4
- package/dist/cjs/rules/spiracss-class-structure.selectors.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.selectors.js +34 -60
- package/dist/cjs/rules/spiracss-class-structure.selectors.js.map +1 -1
- package/dist/cjs/rules/spiracss-class-structure.types.d.ts +32 -12
- package/dist/cjs/rules/spiracss-class-structure.types.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.js +207 -116
- package/dist/cjs/rules/spiracss-interaction-properties.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.messages.d.ts +4 -3
- package/dist/cjs/rules/spiracss-interaction-properties.messages.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.messages.js +11 -12
- package/dist/cjs/rules/spiracss-interaction-properties.messages.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.options.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.options.js +12 -14
- package/dist/cjs/rules/spiracss-interaction-properties.options.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-properties.types.d.ts +9 -5
- package/dist/cjs/rules/spiracss-interaction-properties.types.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.js +21 -21
- package/dist/cjs/rules/spiracss-interaction-scope.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.messages.d.ts +4 -3
- package/dist/cjs/rules/spiracss-interaction-scope.messages.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.messages.js +9 -11
- package/dist/cjs/rules/spiracss-interaction-scope.messages.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.options.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.options.js +35 -41
- package/dist/cjs/rules/spiracss-interaction-scope.options.js.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.types.d.ts +14 -14
- package/dist/cjs/rules/spiracss-interaction-scope.types.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.utils.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-interaction-scope.utils.js +31 -11
- package/dist/cjs/rules/spiracss-interaction-scope.utils.js.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.js +31 -28
- package/dist/cjs/rules/spiracss-keyframes-naming.js.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.messages.d.ts +6 -4
- package/dist/cjs/rules/spiracss-keyframes-naming.messages.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.messages.js +12 -25
- package/dist/cjs/rules/spiracss-keyframes-naming.messages.js.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.options.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.options.js +46 -24
- package/dist/cjs/rules/spiracss-keyframes-naming.options.js.map +1 -1
- package/dist/cjs/rules/spiracss-keyframes-naming.types.d.ts +21 -11
- package/dist/cjs/rules/spiracss-keyframes-naming.types.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-page-layer.constants.d.ts +2 -0
- package/dist/cjs/rules/spiracss-page-layer.constants.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.constants.js +5 -0
- package/dist/cjs/rules/spiracss-page-layer.constants.js.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.d.ts +5 -0
- package/dist/cjs/rules/spiracss-page-layer.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.js +243 -0
- package/dist/cjs/rules/spiracss-page-layer.js.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.messages.d.ts +7 -0
- package/dist/cjs/rules/spiracss-page-layer.messages.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.messages.js +15 -0
- package/dist/cjs/rules/spiracss-page-layer.messages.js.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.options.d.ts +4 -0
- package/dist/cjs/rules/spiracss-page-layer.options.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.options.js +59 -0
- package/dist/cjs/rules/spiracss-page-layer.options.js.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.types.d.ts +19 -0
- package/dist/cjs/rules/spiracss-page-layer.types.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-page-layer.types.js +3 -0
- package/dist/cjs/rules/spiracss-page-layer.types.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.constants.d.ts +2 -0
- package/dist/cjs/rules/spiracss-property-placement.constants.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.constants.js +5 -0
- package/dist/cjs/rules/spiracss-property-placement.constants.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.d.ts +5 -0
- package/dist/cjs/rules/spiracss-property-placement.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.js +646 -0
- package/dist/cjs/rules/spiracss-property-placement.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.messages.d.ts +19 -0
- package/dist/cjs/rules/spiracss-property-placement.messages.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.messages.js +110 -0
- package/dist/cjs/rules/spiracss-property-placement.messages.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.options.d.ts +4 -0
- package/dist/cjs/rules/spiracss-property-placement.options.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.options.js +74 -0
- package/dist/cjs/rules/spiracss-property-placement.options.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.selectors.d.ts +41 -0
- package/dist/cjs/rules/spiracss-property-placement.selectors.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.selectors.js +983 -0
- package/dist/cjs/rules/spiracss-property-placement.selectors.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.types.d.ts +28 -0
- package/dist/cjs/rules/spiracss-property-placement.types.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.types.js +3 -0
- package/dist/cjs/rules/spiracss-property-placement.types.js.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.values.d.ts +19 -0
- package/dist/cjs/rules/spiracss-property-placement.values.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-property-placement.values.js +176 -0
- package/dist/cjs/rules/spiracss-property-placement.values.js.map +1 -0
- package/dist/cjs/rules/spiracss-pseudo-nesting.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-pseudo-nesting.js +18 -18
- package/dist/cjs/rules/spiracss-pseudo-nesting.js.map +1 -1
- package/dist/cjs/rules/spiracss-pseudo-nesting.messages.d.ts +6 -0
- package/dist/cjs/rules/spiracss-pseudo-nesting.messages.d.ts.map +1 -0
- package/dist/cjs/rules/spiracss-pseudo-nesting.messages.js +10 -0
- package/dist/cjs/rules/spiracss-pseudo-nesting.messages.js.map +1 -0
- package/dist/cjs/rules/spiracss-pseudo-nesting.types.d.ts +1 -1
- package/dist/cjs/rules/spiracss-pseudo-nesting.types.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.alias.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.alias.js +59 -11
- package/dist/cjs/rules/spiracss-rel-comments.alias.js.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.js +123 -44
- package/dist/cjs/rules/spiracss-rel-comments.js.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.messages.d.ts +6 -5
- package/dist/cjs/rules/spiracss-rel-comments.messages.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.messages.js +16 -12
- package/dist/cjs/rules/spiracss-rel-comments.messages.js.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.options.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.options.js +84 -34
- package/dist/cjs/rules/spiracss-rel-comments.options.js.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.selectors.d.ts.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.selectors.js +4 -5
- package/dist/cjs/rules/spiracss-rel-comments.selectors.js.map +1 -1
- package/dist/cjs/rules/spiracss-rel-comments.types.d.ts +32 -16
- package/dist/cjs/rules/spiracss-rel-comments.types.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +2 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/cache.d.ts +3 -0
- package/dist/cjs/utils/cache.d.ts.map +1 -1
- package/dist/cjs/utils/cache.js +30 -6
- package/dist/cjs/utils/cache.js.map +1 -1
- package/dist/cjs/utils/constants.d.ts +1 -0
- package/dist/cjs/utils/constants.d.ts.map +1 -1
- package/dist/cjs/utils/constants.js +14 -1
- package/dist/cjs/utils/constants.js.map +1 -1
- package/dist/cjs/utils/formatting.d.ts +5 -1
- package/dist/cjs/utils/formatting.d.ts.map +1 -1
- package/dist/cjs/utils/formatting.js +16 -7
- package/dist/cjs/utils/formatting.js.map +1 -1
- package/dist/cjs/utils/messages.d.ts +25 -0
- package/dist/cjs/utils/messages.d.ts.map +1 -0
- package/dist/cjs/utils/messages.js +174 -0
- package/dist/cjs/utils/messages.js.map +1 -0
- package/dist/cjs/utils/naming.d.ts +1 -2
- package/dist/cjs/utils/naming.d.ts.map +1 -1
- package/dist/cjs/utils/naming.js +1 -9
- package/dist/cjs/utils/naming.js.map +1 -1
- package/dist/cjs/utils/normalize.d.ts +1 -0
- package/dist/cjs/utils/normalize.d.ts.map +1 -1
- package/dist/cjs/utils/normalize.js +53 -8
- package/dist/cjs/utils/normalize.js.map +1 -1
- package/dist/cjs/utils/option-schema.d.ts +11 -8
- package/dist/cjs/utils/option-schema.d.ts.map +1 -1
- package/dist/cjs/utils/option-schema.js +12 -9
- package/dist/cjs/utils/option-schema.js.map +1 -1
- package/dist/cjs/utils/options.d.ts +34 -0
- package/dist/cjs/utils/options.d.ts.map +1 -0
- package/dist/cjs/utils/options.js +41 -0
- package/dist/cjs/utils/options.js.map +1 -0
- package/dist/cjs/utils/rule-docs.d.ts +2 -0
- package/dist/cjs/utils/rule-docs.d.ts.map +1 -0
- package/dist/cjs/utils/rule-docs.js +22 -0
- package/dist/cjs/utils/rule-docs.js.map +1 -0
- package/dist/cjs/utils/section.d.ts.map +1 -1
- package/dist/cjs/utils/section.js +32 -39
- package/dist/cjs/utils/section.js.map +1 -1
- package/dist/cjs/utils/selector-policy.d.ts +16 -0
- package/dist/cjs/utils/selector-policy.d.ts.map +1 -0
- package/dist/cjs/utils/selector-policy.js +57 -0
- package/dist/cjs/utils/selector-policy.js.map +1 -0
- package/dist/cjs/utils/selector.d.ts +4 -1
- package/dist/cjs/utils/selector.d.ts.map +1 -1
- package/dist/cjs/utils/selector.js +62 -37
- package/dist/cjs/utils/selector.js.map +1 -1
- package/dist/cjs/utils/stylelint.d.ts +0 -1
- package/dist/cjs/utils/stylelint.d.ts.map +1 -1
- package/dist/cjs/utils/stylelint.js +18 -4
- package/dist/cjs/utils/stylelint.js.map +1 -1
- package/dist/esm/helpers.d.ts +117 -34
- package/dist/esm/helpers.d.ts.map +1 -1
- package/dist/esm/helpers.js +297 -171
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.js +101 -45
- package/dist/esm/rules/spiracss-class-structure.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.messages.d.ts +10 -8
- package/dist/esm/rules/spiracss-class-structure.messages.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.messages.js +59 -23
- package/dist/esm/rules/spiracss-class-structure.messages.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.options.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.options.js +73 -58
- package/dist/esm/rules/spiracss-class-structure.options.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.patterns.d.ts +4 -4
- package/dist/esm/rules/spiracss-class-structure.patterns.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.patterns.js +25 -28
- package/dist/esm/rules/spiracss-class-structure.patterns.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.selectors.d.ts +3 -4
- package/dist/esm/rules/spiracss-class-structure.selectors.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.selectors.js +34 -59
- package/dist/esm/rules/spiracss-class-structure.selectors.js.map +1 -1
- package/dist/esm/rules/spiracss-class-structure.types.d.ts +32 -12
- package/dist/esm/rules/spiracss-class-structure.types.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.js +203 -112
- package/dist/esm/rules/spiracss-interaction-properties.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.messages.d.ts +4 -3
- package/dist/esm/rules/spiracss-interaction-properties.messages.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.messages.js +11 -9
- package/dist/esm/rules/spiracss-interaction-properties.messages.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.options.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.options.js +13 -15
- package/dist/esm/rules/spiracss-interaction-properties.options.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-properties.types.d.ts +9 -5
- package/dist/esm/rules/spiracss-interaction-properties.types.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.js +22 -22
- package/dist/esm/rules/spiracss-interaction-scope.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.messages.d.ts +4 -3
- package/dist/esm/rules/spiracss-interaction-scope.messages.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.messages.js +9 -8
- package/dist/esm/rules/spiracss-interaction-scope.messages.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.options.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.options.js +37 -43
- package/dist/esm/rules/spiracss-interaction-scope.options.js.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.types.d.ts +14 -14
- package/dist/esm/rules/spiracss-interaction-scope.types.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.utils.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-interaction-scope.utils.js +31 -11
- package/dist/esm/rules/spiracss-interaction-scope.utils.js.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.js +32 -29
- package/dist/esm/rules/spiracss-keyframes-naming.js.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.messages.d.ts +6 -4
- package/dist/esm/rules/spiracss-keyframes-naming.messages.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.messages.js +12 -22
- package/dist/esm/rules/spiracss-keyframes-naming.messages.js.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.options.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.options.js +47 -25
- package/dist/esm/rules/spiracss-keyframes-naming.options.js.map +1 -1
- package/dist/esm/rules/spiracss-keyframes-naming.types.d.ts +21 -11
- package/dist/esm/rules/spiracss-keyframes-naming.types.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-page-layer.constants.d.ts +2 -0
- package/dist/esm/rules/spiracss-page-layer.constants.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.constants.js +2 -0
- package/dist/esm/rules/spiracss-page-layer.constants.js.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.d.ts +5 -0
- package/dist/esm/rules/spiracss-page-layer.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.js +237 -0
- package/dist/esm/rules/spiracss-page-layer.js.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.messages.d.ts +7 -0
- package/dist/esm/rules/spiracss-page-layer.messages.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.messages.js +12 -0
- package/dist/esm/rules/spiracss-page-layer.messages.js.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.options.d.ts +4 -0
- package/dist/esm/rules/spiracss-page-layer.options.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.options.js +55 -0
- package/dist/esm/rules/spiracss-page-layer.options.js.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.types.d.ts +19 -0
- package/dist/esm/rules/spiracss-page-layer.types.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-page-layer.types.js +2 -0
- package/dist/esm/rules/spiracss-page-layer.types.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.constants.d.ts +2 -0
- package/dist/esm/rules/spiracss-property-placement.constants.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.constants.js +2 -0
- package/dist/esm/rules/spiracss-property-placement.constants.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.d.ts +5 -0
- package/dist/esm/rules/spiracss-property-placement.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.js +640 -0
- package/dist/esm/rules/spiracss-property-placement.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.messages.d.ts +19 -0
- package/dist/esm/rules/spiracss-property-placement.messages.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.messages.js +107 -0
- package/dist/esm/rules/spiracss-property-placement.messages.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.options.d.ts +4 -0
- package/dist/esm/rules/spiracss-property-placement.options.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.options.js +70 -0
- package/dist/esm/rules/spiracss-property-placement.options.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.selectors.d.ts +41 -0
- package/dist/esm/rules/spiracss-property-placement.selectors.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.selectors.js +970 -0
- package/dist/esm/rules/spiracss-property-placement.selectors.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.types.d.ts +28 -0
- package/dist/esm/rules/spiracss-property-placement.types.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.types.js +2 -0
- package/dist/esm/rules/spiracss-property-placement.types.js.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.values.d.ts +19 -0
- package/dist/esm/rules/spiracss-property-placement.values.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-property-placement.values.js +169 -0
- package/dist/esm/rules/spiracss-property-placement.values.js.map +1 -0
- package/dist/esm/rules/spiracss-pseudo-nesting.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-pseudo-nesting.js +18 -18
- package/dist/esm/rules/spiracss-pseudo-nesting.js.map +1 -1
- package/dist/esm/rules/spiracss-pseudo-nesting.messages.d.ts +6 -0
- package/dist/esm/rules/spiracss-pseudo-nesting.messages.d.ts.map +1 -0
- package/dist/esm/rules/spiracss-pseudo-nesting.messages.js +7 -0
- package/dist/esm/rules/spiracss-pseudo-nesting.messages.js.map +1 -0
- package/dist/esm/rules/spiracss-pseudo-nesting.types.d.ts +1 -1
- package/dist/esm/rules/spiracss-pseudo-nesting.types.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.alias.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.alias.js +59 -11
- package/dist/esm/rules/spiracss-rel-comments.alias.js.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.js +127 -48
- package/dist/esm/rules/spiracss-rel-comments.js.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.messages.d.ts +6 -5
- package/dist/esm/rules/spiracss-rel-comments.messages.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.messages.js +16 -9
- package/dist/esm/rules/spiracss-rel-comments.messages.js.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.options.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.options.js +86 -36
- package/dist/esm/rules/spiracss-rel-comments.options.js.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.selectors.d.ts.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.selectors.js +4 -5
- package/dist/esm/rules/spiracss-rel-comments.selectors.js.map +1 -1
- package/dist/esm/rules/spiracss-rel-comments.types.d.ts +32 -16
- package/dist/esm/rules/spiracss-rel-comments.types.d.ts.map +1 -1
- package/dist/esm/types.d.ts +2 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/cache.d.ts +3 -0
- package/dist/esm/utils/cache.d.ts.map +1 -1
- package/dist/esm/utils/cache.js +27 -5
- package/dist/esm/utils/cache.js.map +1 -1
- package/dist/esm/utils/constants.d.ts +1 -0
- package/dist/esm/utils/constants.d.ts.map +1 -1
- package/dist/esm/utils/constants.js +13 -0
- package/dist/esm/utils/constants.js.map +1 -1
- package/dist/esm/utils/formatting.d.ts +5 -1
- package/dist/esm/utils/formatting.d.ts.map +1 -1
- package/dist/esm/utils/formatting.js +14 -6
- package/dist/esm/utils/formatting.js.map +1 -1
- package/dist/esm/utils/messages.d.ts +25 -0
- package/dist/esm/utils/messages.d.ts.map +1 -0
- package/dist/esm/utils/messages.js +160 -0
- package/dist/esm/utils/messages.js.map +1 -0
- package/dist/esm/utils/naming.d.ts +1 -2
- package/dist/esm/utils/naming.d.ts.map +1 -1
- package/dist/esm/utils/naming.js +2 -10
- package/dist/esm/utils/naming.js.map +1 -1
- package/dist/esm/utils/normalize.d.ts +1 -0
- package/dist/esm/utils/normalize.d.ts.map +1 -1
- package/dist/esm/utils/normalize.js +51 -7
- package/dist/esm/utils/normalize.js.map +1 -1
- package/dist/esm/utils/option-schema.d.ts +11 -8
- package/dist/esm/utils/option-schema.d.ts.map +1 -1
- package/dist/esm/utils/option-schema.js +12 -9
- package/dist/esm/utils/option-schema.js.map +1 -1
- package/dist/esm/utils/options.d.ts +34 -0
- package/dist/esm/utils/options.d.ts.map +1 -0
- package/dist/esm/utils/options.js +36 -0
- package/dist/esm/utils/options.js.map +1 -0
- package/dist/esm/utils/rule-docs.d.ts +2 -0
- package/dist/esm/utils/rule-docs.d.ts.map +1 -0
- package/dist/esm/utils/rule-docs.js +18 -0
- package/dist/esm/utils/rule-docs.js.map +1 -0
- package/dist/esm/utils/section.d.ts.map +1 -1
- package/dist/esm/utils/section.js +31 -38
- package/dist/esm/utils/section.js.map +1 -1
- package/dist/esm/utils/selector-policy.d.ts +16 -0
- package/dist/esm/utils/selector-policy.d.ts.map +1 -0
- package/dist/esm/utils/selector-policy.js +51 -0
- package/dist/esm/utils/selector-policy.js.map +1 -0
- package/dist/esm/utils/selector.d.ts +4 -1
- package/dist/esm/utils/selector.d.ts.map +1 -1
- package/dist/esm/utils/selector.js +59 -36
- package/dist/esm/utils/selector.js.map +1 -1
- package/dist/esm/utils/stylelint.d.ts +0 -1
- package/dist/esm/utils/stylelint.d.ts.map +1 -1
- package/dist/esm/utils/stylelint.js +17 -2
- package/dist/esm/utils/stylelint.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import { buildSelectorPolicySetsBase } from '../utils/selector-policy.js';
|
|
2
|
+
import { createSharedCacheAccessor } from '../utils/cache.js';
|
|
3
|
+
import { findParentRule } from '../utils/postcss-helpers.js';
|
|
4
|
+
import { classify } from './spiracss-class-structure.patterns.js';
|
|
5
|
+
import { messages } from './spiracss-property-placement.messages.js';
|
|
6
|
+
// Parse @scope prelude: "(start)" or "(start) to (end)" (allow flexible spacing around "to").
|
|
7
|
+
const SCOPE_PARAM_PATTERN = /^\s*\(([^()]*)\)(?:\s*to\s*\(([^()]*)\))?\s*$/i;
|
|
8
|
+
// Allow only simple selectors (no pseudo, attribute, combinator, or grouping).
|
|
9
|
+
const SIMPLE_SCOPE_SELECTOR_PATTERN = /^[^,\s>+~&()\[\]:]+$/;
|
|
10
|
+
// Extract the leading token from @include params.
|
|
11
|
+
const SCOPE_HEAD_PATTERN = /^([^\s(]+)/;
|
|
12
|
+
const ALLOWED_COMBINATORS = new Set(['>', '+', '~']);
|
|
13
|
+
const GLOBAL_PSEUDO = ':global';
|
|
14
|
+
const FUNCTIONAL_PSEUDOS = new Set([':is', ':where', ':not']);
|
|
15
|
+
const LEADING_COMBINATOR_PATTERN = /^[>+~]/;
|
|
16
|
+
const TRAILING_COMBINATOR_PATTERN = /[>+~]$/;
|
|
17
|
+
// Guard against selector explosion during nested resolution.
|
|
18
|
+
const MAX_RESOLVED_SELECTORS = 1000;
|
|
19
|
+
const isTagNode = (node) => node.type === 'tag';
|
|
20
|
+
const isIdNode = (node) => node.type === 'id';
|
|
21
|
+
const normalizeCombinator = (value) => value.trim() || ' ';
|
|
22
|
+
const externalClassCache = new WeakMap();
|
|
23
|
+
const getExternalClassCache = (options) => {
|
|
24
|
+
const cached = externalClassCache.get(options);
|
|
25
|
+
if (cached)
|
|
26
|
+
return cached;
|
|
27
|
+
const entry = {
|
|
28
|
+
classSet: new Set(options.external.classes),
|
|
29
|
+
prefixes: options.external.prefixes
|
|
30
|
+
};
|
|
31
|
+
externalClassCache.set(options, entry);
|
|
32
|
+
return entry;
|
|
33
|
+
};
|
|
34
|
+
const isExternalClass = (name, options) => {
|
|
35
|
+
if (name.startsWith('u-'))
|
|
36
|
+
return true;
|
|
37
|
+
const cached = getExternalClassCache(options);
|
|
38
|
+
if (cached.classSet.has(name))
|
|
39
|
+
return true;
|
|
40
|
+
return cached.prefixes.some((prefix) => name.startsWith(prefix));
|
|
41
|
+
};
|
|
42
|
+
const isAllowedAttribute = (name, policy) => {
|
|
43
|
+
const lowered = name.toLowerCase();
|
|
44
|
+
if (lowered.startsWith('data-')) {
|
|
45
|
+
if (policy.dataVariantEnabled && policy.variantKeys.has(lowered))
|
|
46
|
+
return true;
|
|
47
|
+
if (policy.dataStateEnabled && lowered === policy.stateKey)
|
|
48
|
+
return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (lowered.startsWith('aria-')) {
|
|
52
|
+
if (!policy.dataStateEnabled)
|
|
53
|
+
return false;
|
|
54
|
+
return policy.ariaKeys.has(lowered);
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
58
|
+
export const buildPolicySets = (policy) => ({
|
|
59
|
+
...buildSelectorPolicySetsBase(policy),
|
|
60
|
+
modifiersAllowed: !(policy.variant.mode === 'data' && policy.state.mode === 'data')
|
|
61
|
+
});
|
|
62
|
+
const normalizeStrippedSelector = (selectorText) => {
|
|
63
|
+
let normalized = selectorText.replace(/\s+/g, ' ').trim();
|
|
64
|
+
if (!normalized)
|
|
65
|
+
return null;
|
|
66
|
+
// Strip dangling combinators left after removing :global(...) segments.
|
|
67
|
+
// Descendant combinators are treated as root; only explicit child/sibling intent is preserved.
|
|
68
|
+
const match = normalized.match(LEADING_COMBINATOR_PATTERN);
|
|
69
|
+
const leadingCombinator = match ? match[0] : null;
|
|
70
|
+
while (LEADING_COMBINATOR_PATTERN.test(normalized)) {
|
|
71
|
+
normalized = normalized.slice(1).trim();
|
|
72
|
+
}
|
|
73
|
+
while (TRAILING_COMBINATOR_PATTERN.test(normalized)) {
|
|
74
|
+
normalized = normalized.slice(0, -1).trim();
|
|
75
|
+
}
|
|
76
|
+
return normalized.length > 0 ? { selector: normalized, leadingCombinator } : null;
|
|
77
|
+
};
|
|
78
|
+
const getGlobalSelectorCache = createSharedCacheAccessor();
|
|
79
|
+
const scanSelectorGlobalFlags = (selector, cache = new WeakMap()) => {
|
|
80
|
+
const cached = cache.get(selector);
|
|
81
|
+
if (cached)
|
|
82
|
+
return cached;
|
|
83
|
+
const nodes = selector.nodes ?? [];
|
|
84
|
+
let hasLocal = false;
|
|
85
|
+
let hasGlobal = false;
|
|
86
|
+
let hasGlobalOutsideNegation = false;
|
|
87
|
+
let hasBareGlobal = false;
|
|
88
|
+
let bareIndex = null;
|
|
89
|
+
let lastIndex = null;
|
|
90
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
91
|
+
const node = nodes[index];
|
|
92
|
+
if (node.type === 'comment' || node.type === 'combinator')
|
|
93
|
+
continue;
|
|
94
|
+
lastIndex = index;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
98
|
+
const node = nodes[index];
|
|
99
|
+
if (node.type === 'comment' || node.type === 'combinator')
|
|
100
|
+
continue;
|
|
101
|
+
if (node.type === 'pseudo') {
|
|
102
|
+
const value = typeof node.value === 'string' ? node.value.toLowerCase() : '';
|
|
103
|
+
if (value === GLOBAL_PSEUDO) {
|
|
104
|
+
hasGlobal = true;
|
|
105
|
+
hasGlobalOutsideNegation = true;
|
|
106
|
+
const nestedSelectors = Array.isArray(node.nodes)
|
|
107
|
+
? node.nodes.filter((child) => child.type === 'selector')
|
|
108
|
+
: [];
|
|
109
|
+
if (nestedSelectors.length === 0) {
|
|
110
|
+
hasBareGlobal = true;
|
|
111
|
+
bareIndex = index;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const selectorNodes = Array.isArray(node.nodes)
|
|
117
|
+
? node.nodes.filter((child) => child.type === 'selector')
|
|
118
|
+
: [];
|
|
119
|
+
if (FUNCTIONAL_PSEUDOS.has(value)) {
|
|
120
|
+
const isNegation = value === ':not';
|
|
121
|
+
let hasNestedLocal = false;
|
|
122
|
+
selectorNodes.forEach((selectorNode) => {
|
|
123
|
+
const nested = scanSelectorGlobalFlags(selectorNode, cache);
|
|
124
|
+
if (nested.hasLocal)
|
|
125
|
+
hasNestedLocal = true;
|
|
126
|
+
if (nested.hasGlobal)
|
|
127
|
+
hasGlobal = true;
|
|
128
|
+
if (!isNegation && nested.hasGlobalOutsideNegation) {
|
|
129
|
+
hasGlobalOutsideNegation = true;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (hasNestedLocal)
|
|
133
|
+
hasLocal = true;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (selectorNodes.length > 0) {
|
|
137
|
+
selectorNodes.forEach((selectorNode) => {
|
|
138
|
+
const nested = scanSelectorGlobalFlags(selectorNode, cache);
|
|
139
|
+
if (nested.hasGlobal)
|
|
140
|
+
hasGlobal = true;
|
|
141
|
+
if (nested.hasGlobalOutsideNegation)
|
|
142
|
+
hasGlobalOutsideNegation = true;
|
|
143
|
+
});
|
|
144
|
+
hasLocal = true;
|
|
145
|
+
}
|
|
146
|
+
// Non-global pseudos do not make the selector local by themselves.
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
hasLocal = true;
|
|
150
|
+
}
|
|
151
|
+
let result;
|
|
152
|
+
if (hasBareGlobal && bareIndex !== null) {
|
|
153
|
+
result = {
|
|
154
|
+
hasLocal,
|
|
155
|
+
hasGlobal,
|
|
156
|
+
hasGlobalOutsideNegation,
|
|
157
|
+
hasBareGlobal: true,
|
|
158
|
+
bareIndex,
|
|
159
|
+
lastIndex
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
result = {
|
|
164
|
+
hasLocal,
|
|
165
|
+
hasGlobal,
|
|
166
|
+
hasGlobalOutsideNegation,
|
|
167
|
+
hasBareGlobal: false,
|
|
168
|
+
bareIndex: null,
|
|
169
|
+
lastIndex
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
cache.set(selector, result);
|
|
173
|
+
return result;
|
|
174
|
+
};
|
|
175
|
+
const isSelectorGlobalOnly = (selector, cache) => {
|
|
176
|
+
const scan = scanSelectorGlobalFlags(selector, cache);
|
|
177
|
+
return scan.hasGlobalOutsideNegation && !scan.hasLocal;
|
|
178
|
+
};
|
|
179
|
+
const getRightmostSegment = (nodes) => {
|
|
180
|
+
let start = 0;
|
|
181
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
182
|
+
if (nodes[index].type === 'combinator') {
|
|
183
|
+
start = index + 1;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return nodes.slice(start);
|
|
188
|
+
};
|
|
189
|
+
const isGlobalOnlySegment = (segment, cache) => {
|
|
190
|
+
let hasNodes = false;
|
|
191
|
+
let hasGlobalReference = false;
|
|
192
|
+
for (const node of segment) {
|
|
193
|
+
if (node.type === 'comment' || node.type === 'combinator')
|
|
194
|
+
continue;
|
|
195
|
+
hasNodes = true;
|
|
196
|
+
if (node.type === 'class' ||
|
|
197
|
+
node.type === 'tag' ||
|
|
198
|
+
node.type === 'id' ||
|
|
199
|
+
node.type === 'attribute' ||
|
|
200
|
+
node.type === 'nesting' ||
|
|
201
|
+
node.type === 'universal' ||
|
|
202
|
+
node.type === 'string') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
if (node.type === 'pseudo') {
|
|
206
|
+
const value = typeof node.value === 'string' ? node.value.toLowerCase() : '';
|
|
207
|
+
const selectorNodes = Array.isArray(node.nodes)
|
|
208
|
+
? node.nodes.filter((child) => child.type === 'selector')
|
|
209
|
+
: [];
|
|
210
|
+
if (value === GLOBAL_PSEUDO) {
|
|
211
|
+
if (selectorNodes.length === 0)
|
|
212
|
+
return false;
|
|
213
|
+
hasGlobalReference = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (FUNCTIONAL_PSEUDOS.has(value)) {
|
|
217
|
+
if (selectorNodes.length === 0)
|
|
218
|
+
return false;
|
|
219
|
+
if (value === ':not')
|
|
220
|
+
return false;
|
|
221
|
+
if (!selectorNodes.every((selector) => isSelectorGlobalOnly(selector, cache))) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
hasGlobalReference = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (selectorNodes.length > 0)
|
|
228
|
+
return false;
|
|
229
|
+
// Other pseudos are allowed as long as the segment is otherwise global-only.
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return hasNodes && hasGlobalReference;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Tracks local/global nodes inside a selector.
|
|
238
|
+
*
|
|
239
|
+
* CSS Modules :global semantics:
|
|
240
|
+
* - :global(.foo) → only .foo is global; nodes after it are local
|
|
241
|
+
* - :global .foo → .foo and all subsequent nodes are global (bare :global)
|
|
242
|
+
*/
|
|
243
|
+
const scanSelectorGlobalState = (selector, cache) => {
|
|
244
|
+
const nodes = selector.nodes ?? [];
|
|
245
|
+
const scan = scanSelectorGlobalFlags(selector, cache);
|
|
246
|
+
let rightmostGlobal = false;
|
|
247
|
+
if (scan.hasBareGlobal &&
|
|
248
|
+
scan.bareIndex !== null &&
|
|
249
|
+
scan.lastIndex !== null &&
|
|
250
|
+
scan.lastIndex > scan.bareIndex) {
|
|
251
|
+
rightmostGlobal = true;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const rightmostSegment = getRightmostSegment(nodes);
|
|
255
|
+
rightmostGlobal = isGlobalOnlySegment(rightmostSegment, cache);
|
|
256
|
+
}
|
|
257
|
+
// Keep the explicit branch to help TypeScript narrow hasBareGlobal/bareIndex.
|
|
258
|
+
if (scan.hasBareGlobal && scan.bareIndex !== null) {
|
|
259
|
+
return {
|
|
260
|
+
hasLocal: scan.hasLocal,
|
|
261
|
+
hasGlobal: scan.hasGlobal,
|
|
262
|
+
hasGlobalOutsideNegation: scan.hasGlobalOutsideNegation,
|
|
263
|
+
hasBareGlobal: true,
|
|
264
|
+
bareIndex: scan.bareIndex,
|
|
265
|
+
rightmostGlobal
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
hasLocal: scan.hasLocal,
|
|
270
|
+
hasGlobal: scan.hasGlobal,
|
|
271
|
+
hasGlobalOutsideNegation: scan.hasGlobalOutsideNegation,
|
|
272
|
+
hasBareGlobal: false,
|
|
273
|
+
bareIndex: null,
|
|
274
|
+
rightmostGlobal
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
const analyzeGlobalSelector = (selectorText, cache, cacheSize) => {
|
|
278
|
+
const globalCache = getGlobalSelectorCache(cacheSize);
|
|
279
|
+
const cached = globalCache.get(selectorText);
|
|
280
|
+
if (cached) {
|
|
281
|
+
// Keep parse-error tracking consistent even when global analysis is cached.
|
|
282
|
+
cache.parse(selectorText);
|
|
283
|
+
return cached.value;
|
|
284
|
+
}
|
|
285
|
+
const selectors = cache.parse(selectorText);
|
|
286
|
+
if (selectors.length === 0) {
|
|
287
|
+
const value = {
|
|
288
|
+
stripped: null,
|
|
289
|
+
strippedSelectors: [],
|
|
290
|
+
globalOnly: false,
|
|
291
|
+
selectorCount: 0
|
|
292
|
+
};
|
|
293
|
+
globalCache.set(selectorText, { value });
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
const selectorCount = selectors.length;
|
|
297
|
+
const flagsCache = new WeakMap();
|
|
298
|
+
let allGlobalOnly = selectors.length > 0;
|
|
299
|
+
const stripped = [];
|
|
300
|
+
selectors.forEach((sel) => {
|
|
301
|
+
const scan = scanSelectorGlobalState(sel, flagsCache);
|
|
302
|
+
let selectorGlobalOnly = scan.rightmostGlobal ||
|
|
303
|
+
(scan.hasGlobalOutsideNegation && !scan.hasLocal);
|
|
304
|
+
if (!scan.hasGlobal) {
|
|
305
|
+
const normalized = normalizeStrippedSelector(sel.toString());
|
|
306
|
+
if (normalized)
|
|
307
|
+
stripped.push(normalized);
|
|
308
|
+
allGlobalOnly = false;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (scan.rightmostGlobal) {
|
|
312
|
+
// Treat selectors whose rightmost target is global as out-of-scope.
|
|
313
|
+
allGlobalOnly = allGlobalOnly && selectorGlobalOnly;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (scan.hasBareGlobal) {
|
|
317
|
+
const cloned = sel.clone();
|
|
318
|
+
const nodes = cloned.nodes ?? [];
|
|
319
|
+
nodes.splice(scan.bareIndex);
|
|
320
|
+
const normalized = normalizeStrippedSelector(cloned.toString());
|
|
321
|
+
if (normalized)
|
|
322
|
+
stripped.push(normalized);
|
|
323
|
+
if (!normalized)
|
|
324
|
+
selectorGlobalOnly = true;
|
|
325
|
+
allGlobalOnly = allGlobalOnly && selectorGlobalOnly;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const cloned = sel.clone();
|
|
329
|
+
const globalOnlySelectors = new WeakSet();
|
|
330
|
+
let hasEmptySelectorList = false;
|
|
331
|
+
let hasGlobalOnlySelectorList = false;
|
|
332
|
+
cloned.walkPseudos((pseudo) => {
|
|
333
|
+
const value = typeof pseudo.value === 'string' ? pseudo.value.toLowerCase() : '';
|
|
334
|
+
if (value === GLOBAL_PSEUDO) {
|
|
335
|
+
const parent = pseudo.parent;
|
|
336
|
+
if (parent && parent.type === 'selector') {
|
|
337
|
+
const parentSelector = parent;
|
|
338
|
+
const scan = scanSelectorGlobalState(parentSelector, flagsCache);
|
|
339
|
+
if (scan.rightmostGlobal || (scan.hasGlobalOutsideNegation && !scan.hasLocal)) {
|
|
340
|
+
globalOnlySelectors.add(parentSelector);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Drop :global(...) entirely so global context doesn't affect placement checks.
|
|
344
|
+
pseudo.remove();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
cloned.walkPseudos((pseudo) => {
|
|
348
|
+
const selectorNodes = Array.isArray(pseudo.nodes)
|
|
349
|
+
? pseudo.nodes.filter((child) => child.type === 'selector')
|
|
350
|
+
: [];
|
|
351
|
+
if (selectorNodes.length === 0)
|
|
352
|
+
return;
|
|
353
|
+
let removedGlobalOnly = false;
|
|
354
|
+
selectorNodes.forEach((selector) => {
|
|
355
|
+
if (globalOnlySelectors.has(selector)) {
|
|
356
|
+
selector.remove();
|
|
357
|
+
removedGlobalOnly = true;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
const value = typeof pseudo.value === 'string' ? pseudo.value.toLowerCase() : '';
|
|
361
|
+
selectorNodes.forEach((selector) => {
|
|
362
|
+
const hasMeaningfulNodes = (selector.nodes ?? []).some((node) => node.type !== 'comment' && node.type !== 'combinator');
|
|
363
|
+
if (!hasMeaningfulNodes)
|
|
364
|
+
selector.remove();
|
|
365
|
+
});
|
|
366
|
+
const remainingSelectors = Array.isArray(pseudo.nodes)
|
|
367
|
+
? pseudo.nodes.filter((child) => child.type === 'selector')
|
|
368
|
+
: [];
|
|
369
|
+
if (remainingSelectors.length === 0) {
|
|
370
|
+
if (FUNCTIONAL_PSEUDOS.has(value)) {
|
|
371
|
+
if (value !== ':not' && removedGlobalOnly) {
|
|
372
|
+
hasGlobalOnlySelectorList = true;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
pseudo.remove();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
hasEmptySelectorList = true;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const pseudoNodes = (pseudo.nodes ?? []);
|
|
382
|
+
const hasContent = pseudoNodes.some((node) => {
|
|
383
|
+
if (node.type === 'comment')
|
|
384
|
+
return false;
|
|
385
|
+
if (node.type !== 'selector')
|
|
386
|
+
return true;
|
|
387
|
+
return (node.nodes ?? []).some((child) => child.type !== 'comment' && child.type !== 'combinator');
|
|
388
|
+
});
|
|
389
|
+
if (!hasContent)
|
|
390
|
+
pseudo.remove();
|
|
391
|
+
});
|
|
392
|
+
if (hasGlobalOnlySelectorList) {
|
|
393
|
+
selectorGlobalOnly = true;
|
|
394
|
+
allGlobalOnly = allGlobalOnly && selectorGlobalOnly;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (hasEmptySelectorList) {
|
|
398
|
+
// Treat non-functional pseudos with empty selector lists as unverified:
|
|
399
|
+
// skip placement, but keep @at-root/@extend checks in scope.
|
|
400
|
+
allGlobalOnly = false;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const normalized = normalizeStrippedSelector(cloned.toString());
|
|
404
|
+
if (normalized)
|
|
405
|
+
stripped.push(normalized);
|
|
406
|
+
if (!normalized)
|
|
407
|
+
selectorGlobalOnly = true;
|
|
408
|
+
allGlobalOnly = allGlobalOnly && selectorGlobalOnly;
|
|
409
|
+
});
|
|
410
|
+
const strippedText = stripped.length > 0 ? stripped.map((item) => item.selector).join(', ') : null;
|
|
411
|
+
const value = {
|
|
412
|
+
stripped: allGlobalOnly ? null : strippedText,
|
|
413
|
+
strippedSelectors: allGlobalOnly ? [] : stripped,
|
|
414
|
+
globalOnly: allGlobalOnly,
|
|
415
|
+
selectorCount
|
|
416
|
+
};
|
|
417
|
+
globalCache.set(selectorText, { value });
|
|
418
|
+
return value;
|
|
419
|
+
};
|
|
420
|
+
export const stripGlobalSelector = (selectorText, cache, cacheSize, options) => {
|
|
421
|
+
const analysis = analyzeGlobalSelector(selectorText, cache, cacheSize);
|
|
422
|
+
// `null` means the selector is either global-only or unverified after stripping :global.
|
|
423
|
+
// Use stripGlobalSelectorForRoot() to keep unverified selectors in-scope for root anchoring.
|
|
424
|
+
// Use isGlobalOnlySelector() to distinguish global-only (skip @at-root/@extend).
|
|
425
|
+
if (!options?.preserveCombinator)
|
|
426
|
+
return analysis.stripped;
|
|
427
|
+
if (analysis.strippedSelectors.length === 0)
|
|
428
|
+
return null;
|
|
429
|
+
return analysis.strippedSelectors
|
|
430
|
+
.map((item) => item.leadingCombinator ? `${item.leadingCombinator} ${item.selector}` : item.selector)
|
|
431
|
+
.join(', ');
|
|
432
|
+
};
|
|
433
|
+
export const stripGlobalSelectorForRoot = (selectorText, cache, cacheSize, options) => {
|
|
434
|
+
// Expect a single selector (splitSelectors output), not a selector list with commas.
|
|
435
|
+
// For unverified selectors, return the original text to keep root anchoring in-scope.
|
|
436
|
+
const analysis = analyzeGlobalSelector(selectorText, cache, cacheSize);
|
|
437
|
+
if (analysis.globalOnly)
|
|
438
|
+
return null;
|
|
439
|
+
if (!options?.preserveCombinator) {
|
|
440
|
+
return analysis.stripped ?? selectorText;
|
|
441
|
+
}
|
|
442
|
+
if (analysis.strippedSelectors.length === 0) {
|
|
443
|
+
// Keep unverified selectors in-scope for root anchoring.
|
|
444
|
+
return selectorText;
|
|
445
|
+
}
|
|
446
|
+
return analysis.strippedSelectors
|
|
447
|
+
.map((item) => item.leadingCombinator ? `${item.leadingCombinator} ${item.selector}` : item.selector)
|
|
448
|
+
.join(', ');
|
|
449
|
+
};
|
|
450
|
+
export const isGlobalOnlySelector = (selectorText, cache, cacheSize) => {
|
|
451
|
+
return analyzeGlobalSelector(selectorText, cache, cacheSize).globalOnly;
|
|
452
|
+
};
|
|
453
|
+
export const splitSelectors = (selector, cache) => {
|
|
454
|
+
const selectors = cache.parse(selector);
|
|
455
|
+
return selectors.map((sel) => sel.toString().trim()).filter((text) => text.length > 0);
|
|
456
|
+
};
|
|
457
|
+
const combineSelectors = (parent, child) => {
|
|
458
|
+
const trimmedChild = child.trim();
|
|
459
|
+
if (!trimmedChild)
|
|
460
|
+
return null;
|
|
461
|
+
if (trimmedChild.includes('&')) {
|
|
462
|
+
return trimmedChild.replace(/&/g, parent);
|
|
463
|
+
}
|
|
464
|
+
return `${parent} ${trimmedChild}`.trim();
|
|
465
|
+
};
|
|
466
|
+
export const resolveSelectors = (rule, cache, resolvedCache, reportExplosion) => {
|
|
467
|
+
const cached = resolvedCache.get(rule);
|
|
468
|
+
if (cached !== undefined)
|
|
469
|
+
return cached;
|
|
470
|
+
const chain = [rule];
|
|
471
|
+
let current = findParentRule(rule);
|
|
472
|
+
let resolved = null;
|
|
473
|
+
while (current) {
|
|
474
|
+
const cachedCurrent = resolvedCache.get(current);
|
|
475
|
+
if (cachedCurrent !== undefined) {
|
|
476
|
+
resolved = cachedCurrent;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
chain.push(current);
|
|
480
|
+
current = findParentRule(current);
|
|
481
|
+
}
|
|
482
|
+
while (chain.length > 0) {
|
|
483
|
+
const currentRule = chain.pop();
|
|
484
|
+
if (typeof currentRule.selector !== 'string') {
|
|
485
|
+
resolved = [];
|
|
486
|
+
resolvedCache.set(currentRule, resolved);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const currentSelectors = splitSelectors(currentRule.selector, cache);
|
|
490
|
+
if (!resolved) {
|
|
491
|
+
resolved = currentSelectors;
|
|
492
|
+
resolvedCache.set(currentRule, resolved);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (resolved.length === 0 || currentSelectors.length === 0) {
|
|
496
|
+
resolved = [];
|
|
497
|
+
resolvedCache.set(currentRule, resolved);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (resolved.length * currentSelectors.length > MAX_RESOLVED_SELECTORS) {
|
|
501
|
+
reportExplosion?.(currentRule.selector, MAX_RESOLVED_SELECTORS);
|
|
502
|
+
resolved = [];
|
|
503
|
+
resolvedCache.set(currentRule, resolved);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
// Expand selectors with a hard cap to keep worst-case cost bounded.
|
|
507
|
+
const combined = [];
|
|
508
|
+
let exceeded = false;
|
|
509
|
+
resolved.forEach((parent) => {
|
|
510
|
+
if (exceeded)
|
|
511
|
+
return;
|
|
512
|
+
currentSelectors.forEach((child) => {
|
|
513
|
+
if (exceeded)
|
|
514
|
+
return;
|
|
515
|
+
const combinedSelector = combineSelectors(parent, child);
|
|
516
|
+
if (!combinedSelector)
|
|
517
|
+
return;
|
|
518
|
+
combined.push(combinedSelector);
|
|
519
|
+
if (combined.length > MAX_RESOLVED_SELECTORS) {
|
|
520
|
+
exceeded = true;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
if (exceeded) {
|
|
525
|
+
reportExplosion?.(currentRule.selector, MAX_RESOLVED_SELECTORS);
|
|
526
|
+
resolved = [];
|
|
527
|
+
resolvedCache.set(currentRule, resolved);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
resolved = combined;
|
|
531
|
+
resolvedCache.set(currentRule, resolved);
|
|
532
|
+
}
|
|
533
|
+
return resolvedCache.get(rule) ?? [];
|
|
534
|
+
};
|
|
535
|
+
const collectSegments = (selector, segments, combinators) => {
|
|
536
|
+
let current = [];
|
|
537
|
+
selector.nodes.forEach((node) => {
|
|
538
|
+
if (node.type === 'comment')
|
|
539
|
+
return;
|
|
540
|
+
if (node.type === 'combinator') {
|
|
541
|
+
segments.push(current);
|
|
542
|
+
current = [];
|
|
543
|
+
combinators.push(normalizeCombinator(node.value));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
current.push(node);
|
|
547
|
+
});
|
|
548
|
+
// Keep empty segments to preserve combinator alignment; invalid selectors are rejected later.
|
|
549
|
+
segments.push(current);
|
|
550
|
+
};
|
|
551
|
+
const analyzeRootSegment = (segment, segmentCount, combinatorCount, selectorText) => {
|
|
552
|
+
const tags = segment.filter(isTagNode);
|
|
553
|
+
const ids = segment.filter(isIdNode);
|
|
554
|
+
if (tags.length === 0 && ids.length === 0)
|
|
555
|
+
return { status: 'ok' };
|
|
556
|
+
const hasOtherNodes = segment.some((node) => node.type !== 'tag' && node.type !== 'id');
|
|
557
|
+
const isBodyTag = tags.length === 1 && tags[0].value.toLowerCase() === 'body';
|
|
558
|
+
const isBody = isBodyTag && ids.length === 0;
|
|
559
|
+
const isIdRoot = ids.length === 1 && tags.length === 0;
|
|
560
|
+
if (isBodyTag && ids.length > 0) {
|
|
561
|
+
return { status: 'error', message: messages.pageRootNoChildren(selectorText) };
|
|
562
|
+
}
|
|
563
|
+
if (isBody || isIdRoot) {
|
|
564
|
+
if (hasOtherNodes || segmentCount > 1 || combinatorCount > 0) {
|
|
565
|
+
return { status: 'error', message: messages.pageRootNoChildren(selectorText) };
|
|
566
|
+
}
|
|
567
|
+
return { status: 'ok', kind: 'page-root' };
|
|
568
|
+
}
|
|
569
|
+
return { status: 'skip' };
|
|
570
|
+
};
|
|
571
|
+
const analyzePseudoBase = (pseudo, options, policy, patterns, classifyOptions) => {
|
|
572
|
+
if (!('nodes' in pseudo) || !Array.isArray(pseudo.nodes))
|
|
573
|
+
return null;
|
|
574
|
+
const selectorNodes = pseudo.nodes.filter((node) => node.type === 'selector');
|
|
575
|
+
if (selectorNodes.length === 0)
|
|
576
|
+
return null;
|
|
577
|
+
let resolvedKind = null;
|
|
578
|
+
let resolvedClass = null;
|
|
579
|
+
let resolvedHasBase = false;
|
|
580
|
+
for (const sel of selectorNodes) {
|
|
581
|
+
let localKind = null;
|
|
582
|
+
let localClass = null;
|
|
583
|
+
let localHasBase = false;
|
|
584
|
+
let invalid = false;
|
|
585
|
+
sel.nodes.forEach((node) => {
|
|
586
|
+
if (invalid)
|
|
587
|
+
return;
|
|
588
|
+
if (node.type === 'comment')
|
|
589
|
+
return;
|
|
590
|
+
if (node.type === 'combinator') {
|
|
591
|
+
invalid = true;
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (node.type === 'class') {
|
|
595
|
+
const className = node.value;
|
|
596
|
+
if (isExternalClass(className, options)) {
|
|
597
|
+
invalid = true;
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const kind = classify(className, classifyOptions, patterns);
|
|
601
|
+
if (kind === 'external' || kind === 'invalid') {
|
|
602
|
+
invalid = true;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (kind === 'modifier') {
|
|
606
|
+
if (!policy.modifiersAllowed)
|
|
607
|
+
invalid = true;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (localHasBase) {
|
|
611
|
+
if (localClass !== className || localKind !== kind)
|
|
612
|
+
invalid = true;
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
localKind = kind;
|
|
616
|
+
localClass = className;
|
|
617
|
+
localHasBase = true;
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (node.type === 'attribute') {
|
|
621
|
+
const name = typeof node.attribute === 'string' ? node.attribute : '';
|
|
622
|
+
if (!name || !isAllowedAttribute(name, policy))
|
|
623
|
+
invalid = true;
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (node.type === 'pseudo') {
|
|
627
|
+
invalid = true;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (node.type === 'nesting' || node.type === 'tag' || node.type === 'id') {
|
|
631
|
+
invalid = true;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
invalid = true;
|
|
635
|
+
});
|
|
636
|
+
if (invalid)
|
|
637
|
+
return null;
|
|
638
|
+
if (resolvedHasBase) {
|
|
639
|
+
if (!localHasBase)
|
|
640
|
+
return null;
|
|
641
|
+
if (resolvedClass !== localClass || resolvedKind !== localKind)
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
else if (localHasBase) {
|
|
645
|
+
resolvedHasBase = true;
|
|
646
|
+
resolvedClass = localClass;
|
|
647
|
+
resolvedKind = localKind;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return { baseKind: resolvedKind, baseClass: resolvedClass };
|
|
651
|
+
};
|
|
652
|
+
// Analyze a selector segment and return either the resolved kind only ("kind")
|
|
653
|
+
// or the kind + base class ("base") for selector-family keys.
|
|
654
|
+
const analyzeSegmentInfo = (segment, options, policy, patterns, classifyOptions, mode) => {
|
|
655
|
+
let baseKind = null;
|
|
656
|
+
let baseClass = null;
|
|
657
|
+
// Only enforce single base-class segments when building family keys.
|
|
658
|
+
const countBaseClasses = mode === 'base';
|
|
659
|
+
let baseCount = 0;
|
|
660
|
+
let pseudoKind = null;
|
|
661
|
+
let pseudoClass = null;
|
|
662
|
+
for (const node of segment) {
|
|
663
|
+
if (node.type === 'nesting')
|
|
664
|
+
return null;
|
|
665
|
+
if (node.type === 'universal' || node.type === 'tag' || node.type === 'id')
|
|
666
|
+
return null;
|
|
667
|
+
if (node.type === 'attribute') {
|
|
668
|
+
const name = typeof node.attribute === 'string' ? node.attribute : '';
|
|
669
|
+
if (!name || !isAllowedAttribute(name, policy))
|
|
670
|
+
return null;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (node.type === 'pseudo') {
|
|
674
|
+
const pseudoValue = typeof node.value === 'string' ? node.value.toLowerCase() : '';
|
|
675
|
+
if (!FUNCTIONAL_PSEUDOS.has(pseudoValue))
|
|
676
|
+
return null;
|
|
677
|
+
const parsed = analyzePseudoBase(node, options, policy, patterns, classifyOptions);
|
|
678
|
+
if (!parsed)
|
|
679
|
+
return null;
|
|
680
|
+
if (parsed.baseKind) {
|
|
681
|
+
if (!pseudoClass) {
|
|
682
|
+
pseudoClass = parsed.baseClass;
|
|
683
|
+
pseudoKind = parsed.baseKind;
|
|
684
|
+
}
|
|
685
|
+
else if (pseudoKind !== parsed.baseKind ||
|
|
686
|
+
(mode === 'base' && pseudoClass !== parsed.baseClass)) {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (node.type !== 'class')
|
|
693
|
+
continue;
|
|
694
|
+
const className = node.value;
|
|
695
|
+
if (isExternalClass(className, options))
|
|
696
|
+
return null;
|
|
697
|
+
const kind = classify(className, classifyOptions, patterns);
|
|
698
|
+
if (kind === 'external' || kind === 'invalid')
|
|
699
|
+
return null;
|
|
700
|
+
if (kind === 'modifier') {
|
|
701
|
+
if (!policy.modifiersAllowed)
|
|
702
|
+
return null;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (kind === 'block' || kind === 'element') {
|
|
706
|
+
if (countBaseClasses)
|
|
707
|
+
baseCount += 1;
|
|
708
|
+
if (!baseKind) {
|
|
709
|
+
baseKind = kind;
|
|
710
|
+
baseClass = className;
|
|
711
|
+
}
|
|
712
|
+
else if (baseKind !== kind) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (!baseKind || !baseClass)
|
|
718
|
+
return null;
|
|
719
|
+
if (pseudoKind && baseKind !== pseudoKind)
|
|
720
|
+
return null;
|
|
721
|
+
if (mode === 'base') {
|
|
722
|
+
// Family keys require exactly one base class per segment.
|
|
723
|
+
if (baseCount > 1)
|
|
724
|
+
return null;
|
|
725
|
+
if (pseudoClass && baseClass !== pseudoClass)
|
|
726
|
+
return null;
|
|
727
|
+
return { kind: baseKind, baseClass };
|
|
728
|
+
}
|
|
729
|
+
// For kind-only mode, multiple base classes of the same kind are allowed.
|
|
730
|
+
return { kind: baseKind };
|
|
731
|
+
};
|
|
732
|
+
const analyzeSegmentKind = (segment, options, policy, patterns, classifyOptions) => {
|
|
733
|
+
const info = analyzeSegmentInfo(segment, options, policy, patterns, classifyOptions, 'kind');
|
|
734
|
+
return info ? info.kind : null;
|
|
735
|
+
};
|
|
736
|
+
const analyzeSegmentBase = (segment, options, policy, patterns, classifyOptions) => {
|
|
737
|
+
const info = analyzeSegmentInfo(segment, options, policy, patterns, classifyOptions, 'base');
|
|
738
|
+
return info && 'baseClass' in info ? info : null;
|
|
739
|
+
};
|
|
740
|
+
const calculateElementDepth = (segmentKinds, combinators) => {
|
|
741
|
+
// Track consecutive element chains joined by child combinators.
|
|
742
|
+
let maxDepth = 0;
|
|
743
|
+
let currentDepth = 0;
|
|
744
|
+
for (let i = 1; i < segmentKinds.length; i += 1) {
|
|
745
|
+
const kind = segmentKinds[i];
|
|
746
|
+
const combinator = combinators[i - 1];
|
|
747
|
+
if (kind !== 'element') {
|
|
748
|
+
currentDepth = 0;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (combinator === '>' && segmentKinds[i - 1] === 'element') {
|
|
752
|
+
currentDepth += 1;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
currentDepth = 1;
|
|
756
|
+
}
|
|
757
|
+
if (currentDepth > maxDepth)
|
|
758
|
+
maxDepth = currentDepth;
|
|
759
|
+
}
|
|
760
|
+
return maxDepth;
|
|
761
|
+
};
|
|
762
|
+
const parseSelectorSegments = (selectorText, cache) => {
|
|
763
|
+
const parsed = cache.parse(selectorText);
|
|
764
|
+
if (parsed.length !== 1)
|
|
765
|
+
return null;
|
|
766
|
+
const segments = [];
|
|
767
|
+
const combinators = [];
|
|
768
|
+
collectSegments(parsed[0], segments, combinators);
|
|
769
|
+
if (segments.length === 0)
|
|
770
|
+
return null;
|
|
771
|
+
const rootCheck = analyzeRootSegment(segments[0], segments.length, combinators.length, selectorText);
|
|
772
|
+
return { segments, combinators, rootCheck };
|
|
773
|
+
};
|
|
774
|
+
const buildSelectorChain = (segments, combinators, resolveSegment, maxElementDepth) => {
|
|
775
|
+
// Only allow direct or sibling combinators; descendant combinator (space) is unsupported.
|
|
776
|
+
if (combinators.some((combinator) => !ALLOWED_COMBINATORS.has(combinator))) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
// Sibling combinators (+, ~) are allowed only at the tail position (after the last segment).
|
|
780
|
+
for (let i = 0; i < combinators.length - 1; i += 1) {
|
|
781
|
+
if (combinators[i] === '+' || combinators[i] === '~')
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
const segmentInfos = [];
|
|
785
|
+
const segmentKinds = [];
|
|
786
|
+
for (const segment of segments) {
|
|
787
|
+
const info = resolveSegment(segment);
|
|
788
|
+
if (!info)
|
|
789
|
+
return null;
|
|
790
|
+
segmentInfos.push(info);
|
|
791
|
+
segmentKinds.push(info.kind);
|
|
792
|
+
}
|
|
793
|
+
if (segmentKinds[0] !== 'block')
|
|
794
|
+
return null;
|
|
795
|
+
const tailCombinator = combinators.length > 0 ? combinators.at(-1) : null;
|
|
796
|
+
if (tailCombinator && (tailCombinator === '+' || tailCombinator === '~')) {
|
|
797
|
+
const lastKind = segmentKinds.at(-1);
|
|
798
|
+
const prevKind = segmentKinds.at(-2);
|
|
799
|
+
if (!lastKind || !prevKind || lastKind !== prevKind)
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
if (segmentKinds.length === 2 &&
|
|
803
|
+
segmentKinds[0] === 'block' &&
|
|
804
|
+
segmentKinds[1] === 'block' &&
|
|
805
|
+
(tailCombinator === '+' || tailCombinator === '~')) {
|
|
806
|
+
// Reject `.block + .block` at the root to avoid block-to-block sibling targeting.
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
const allowSiblingBlockTail = (tailCombinator === '+' || tailCombinator === '~') &&
|
|
810
|
+
segmentKinds.at(-1) === 'block' &&
|
|
811
|
+
segmentKinds.at(-2) === 'block';
|
|
812
|
+
const childBlockScanEnd = allowSiblingBlockTail ? -2 : -1;
|
|
813
|
+
const hasChildBlockBeforeTail = segmentKinds
|
|
814
|
+
.slice(1, childBlockScanEnd)
|
|
815
|
+
.some((kind) => kind === 'block');
|
|
816
|
+
if (hasChildBlockBeforeTail)
|
|
817
|
+
return null;
|
|
818
|
+
const hasChildBlockAtTail = segmentKinds.length > 1 && segmentKinds.at(-1) === 'block';
|
|
819
|
+
if (hasChildBlockAtTail) {
|
|
820
|
+
if (tailCombinator === '+' || tailCombinator === '~' || tailCombinator === '>') {
|
|
821
|
+
// allowed
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const maxDepth = calculateElementDepth(segmentKinds, combinators);
|
|
828
|
+
if (maxDepth > maxElementDepth)
|
|
829
|
+
return null;
|
|
830
|
+
return { segmentInfos, segmentKinds, combinators, tailCombinator, hasChildBlockAtTail };
|
|
831
|
+
};
|
|
832
|
+
// Analyze selectors for placement rules, skipping unverified selectors and
|
|
833
|
+
// rejecting lists that mix incompatible kinds.
|
|
834
|
+
export const analyzeSelectorList = (selectorList, cache, options, policy, patterns, classifyOptions) => {
|
|
835
|
+
if (selectorList.length === 0)
|
|
836
|
+
return { status: 'skip' };
|
|
837
|
+
const normalizedSelectors = selectorList.filter((selector) => selector.length > 0);
|
|
838
|
+
if (normalizedSelectors.length === 0)
|
|
839
|
+
return { status: 'skip' };
|
|
840
|
+
const strippedSelectors = normalizedSelectors.flatMap((selector) => analyzeGlobalSelector(selector, cache, options.cache.selector).strippedSelectors);
|
|
841
|
+
if (strippedSelectors.length === 0)
|
|
842
|
+
return { status: 'skip' };
|
|
843
|
+
let hasPageRootSelector = false;
|
|
844
|
+
let hasKindMismatch = false;
|
|
845
|
+
let hasUnverifiedFamilyKeys = false;
|
|
846
|
+
let expectedKind = null;
|
|
847
|
+
const selectors = [];
|
|
848
|
+
const familyKeys = [];
|
|
849
|
+
for (const { selector: selectorText, leadingCombinator } of strippedSelectors) {
|
|
850
|
+
const analysis = analyzeSelectorWithFamilyKey(selectorText, cache, options, policy, patterns, classifyOptions, leadingCombinator);
|
|
851
|
+
if (analysis.status === 'error')
|
|
852
|
+
return analysis;
|
|
853
|
+
if (analysis.status === 'skip') {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (analysis.selector.kind === 'page-root') {
|
|
857
|
+
hasPageRootSelector = true;
|
|
858
|
+
selectors.push(analysis.selector);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (expectedKind && expectedKind !== analysis.selector.kind) {
|
|
862
|
+
hasKindMismatch = true;
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
expectedKind = analysis.selector.kind;
|
|
866
|
+
}
|
|
867
|
+
selectors.push(analysis.selector);
|
|
868
|
+
if (!analysis.familyKey && analysis.selector.kind === 'child-block') {
|
|
869
|
+
hasUnverifiedFamilyKeys = true;
|
|
870
|
+
}
|
|
871
|
+
if (!hasKindMismatch && analysis.familyKey)
|
|
872
|
+
familyKeys.push(analysis.familyKey);
|
|
873
|
+
}
|
|
874
|
+
if (hasPageRootSelector && normalizedSelectors.length > 1) {
|
|
875
|
+
return {
|
|
876
|
+
status: 'error',
|
|
877
|
+
message: messages.pageRootNoChildren(normalizedSelectors.join(', '))
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
// Reject lists that mix incompatible kinds to avoid bypassing placement checks.
|
|
881
|
+
if (hasKindMismatch) {
|
|
882
|
+
return {
|
|
883
|
+
status: 'error',
|
|
884
|
+
message: messages.selectorKindMismatch(normalizedSelectors.join(', '))
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return selectors.length === 0
|
|
888
|
+
? { status: 'skip' }
|
|
889
|
+
: {
|
|
890
|
+
status: 'ok',
|
|
891
|
+
selectors,
|
|
892
|
+
familyKeys: [...new Set(familyKeys)],
|
|
893
|
+
hasUnverifiedFamilyKeys
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
const buildFamilyKeyFromChain = (chain) => {
|
|
897
|
+
let key = chain.segmentInfos[0].baseClass;
|
|
898
|
+
for (let i = 1; i < chain.segmentInfos.length; i += 1) {
|
|
899
|
+
key += `${chain.combinators[i - 1]}${chain.segmentInfos[i].baseClass}`;
|
|
900
|
+
}
|
|
901
|
+
return key;
|
|
902
|
+
};
|
|
903
|
+
const analyzeSelectorWithFamilyKey = (selectorText, cache, options, policy, patterns, classifyOptions, leadingCombinator = null) => {
|
|
904
|
+
// Only evaluate simple chain selectors; complex patterns are treated as unverified.
|
|
905
|
+
const parsed = parseSelectorSegments(selectorText, cache);
|
|
906
|
+
if (!parsed)
|
|
907
|
+
return { status: 'skip' };
|
|
908
|
+
const { segments, combinators, rootCheck } = parsed;
|
|
909
|
+
if (rootCheck.status !== 'ok')
|
|
910
|
+
return rootCheck;
|
|
911
|
+
if (rootCheck.kind === 'page-root') {
|
|
912
|
+
return {
|
|
913
|
+
status: 'ok',
|
|
914
|
+
selector: { kind: 'page-root', tailCombinator: null },
|
|
915
|
+
familyKey: null
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
// First pass: determine selector kind using relaxed validation (allows multiple
|
|
919
|
+
// base classes of the same kind in a segment, e.g., `.block.block-alt`).
|
|
920
|
+
const chain = buildSelectorChain(segments, combinators, (segment) => {
|
|
921
|
+
const kind = analyzeSegmentKind(segment, options, policy, patterns, classifyOptions);
|
|
922
|
+
return kind ? { kind } : null;
|
|
923
|
+
}, options.element.depth);
|
|
924
|
+
if (!chain)
|
|
925
|
+
return { status: 'skip' };
|
|
926
|
+
let selector;
|
|
927
|
+
if (chain.segmentKinds.length === 1) {
|
|
928
|
+
selector = { kind: 'root', tailCombinator: null };
|
|
929
|
+
}
|
|
930
|
+
else if (chain.hasChildBlockAtTail) {
|
|
931
|
+
selector = { kind: 'child-block', tailCombinator: chain.tailCombinator };
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
selector = { kind: 'element', tailCombinator: chain.tailCombinator };
|
|
935
|
+
}
|
|
936
|
+
if (leadingCombinator && selector.kind === 'root') {
|
|
937
|
+
// Preserve child/sibling intent when the global prefix is stripped away.
|
|
938
|
+
selector = { kind: 'child-block', tailCombinator: leadingCombinator };
|
|
939
|
+
}
|
|
940
|
+
// Second pass: extract family key using strict validation (requires exactly one
|
|
941
|
+
// base class per segment). This may fail even when the first pass succeeded.
|
|
942
|
+
const baseChain = buildSelectorChain(segments, combinators, (segment) => analyzeSegmentBase(segment, options, policy, patterns, classifyOptions), options.element.depth);
|
|
943
|
+
const familyKey = baseChain ? buildFamilyKeyFromChain(baseChain) : null;
|
|
944
|
+
return { status: 'ok', selector, familyKey };
|
|
945
|
+
};
|
|
946
|
+
export const normalizeScopePrelude = (params) => {
|
|
947
|
+
const match = params.match(SCOPE_PARAM_PATTERN);
|
|
948
|
+
if (!match)
|
|
949
|
+
return null;
|
|
950
|
+
const start = match[1]?.trim() ?? '';
|
|
951
|
+
const end = match[2]?.trim() ?? '';
|
|
952
|
+
// Complex prelude selectors are treated as unverified to avoid mis-grouping contexts.
|
|
953
|
+
if (!start || !SIMPLE_SCOPE_SELECTOR_PATTERN.test(start))
|
|
954
|
+
return null;
|
|
955
|
+
if (end && !SIMPLE_SCOPE_SELECTOR_PATTERN.test(end))
|
|
956
|
+
return null;
|
|
957
|
+
return end ? `${start}=>${end}` : start;
|
|
958
|
+
};
|
|
959
|
+
export const normalizeUnverifiedScopePrelude = (params) => {
|
|
960
|
+
const normalized = params.trim().replace(/\s+/g, ' ');
|
|
961
|
+
return normalized.length > 0 ? normalized : null;
|
|
962
|
+
};
|
|
963
|
+
export const parseMixinName = (params) => {
|
|
964
|
+
const trimmed = params.trim();
|
|
965
|
+
if (!trimmed || trimmed.includes('#{'))
|
|
966
|
+
return null;
|
|
967
|
+
const match = trimmed.match(SCOPE_HEAD_PATTERN);
|
|
968
|
+
return match ? match[1] : null;
|
|
969
|
+
};
|
|
970
|
+
//# sourceMappingURL=spiracss-property-placement.selectors.js.map
|