diff --git a/packages/core/src/features/st-symbol.ts b/packages/core/src/features/st-symbol.ts index ce843baa3..365bfbd3a 100644 --- a/packages/core/src/features/st-symbol.ts +++ b/packages/core/src/features/st-symbol.ts @@ -184,24 +184,3 @@ export function reportRedeclare(context: FeatureContext) { } } } - -/* inheritSymbols/forceSetSymbol are used for creating a copy meta with mixin root */ -export function inheritSymbols(originMeta: StylableMeta, targetMeta: StylableMeta) { - const originData = plugableRecord.getUnsafe(originMeta.data, dataKey); - plugableRecord.set(targetMeta.data, dataKey, createState(originData)); -} -export function forceSetSymbol({ - meta, - symbol, - localName, -}: { - meta: StylableMeta; - symbol: filterSymbols; - localName?: string; -}) { - const { byNS, byNSFlat, byType } = plugableRecord.getUnsafe(meta.data, dataKey); - const name = localName || symbol.name; - byNS.main.push({ name, symbol, safeRedeclare: false, ast: undefined }); - byNSFlat.main[name] = symbol; - byType[symbol._kind][name] = symbol; -} diff --git a/packages/core/src/helpers/rule.ts b/packages/core/src/helpers/rule.ts index 9692e29ba..2eaf400fc 100644 --- a/packages/core/src/helpers/rule.ts +++ b/packages/core/src/helpers/rule.ts @@ -7,7 +7,11 @@ import { matchTypeAndValue, isSimpleSelector, } from './selector'; -import type { Selector, ImmutableSelectorNode } from '@tokey/css-selector-parser'; +import { + Selector, + ImmutableSelectorNode, + groupCompoundSelectors, +} from '@tokey/css-selector-parser'; import { valueMapping } from '../stylable-value-parsers'; import * as postcss from 'postcss'; import { ignoreDeprecationWarn } from './deprecation'; @@ -95,7 +99,7 @@ export function createSubsetAst( const selector = stringifySelector( matchesSelectors.map((selectorNode) => { if (!isRoot) { - fixChunkOrdering(selectorNode, prefixType); + selectorNode = fixChunkOrdering(selectorNode, prefixType); } replaceTargetWithNesting(selectorNode, prefixType); return selectorNode; @@ -145,21 +149,24 @@ function replaceTargetWithNesting(selectorNode: Selector, prefixType: ImmutableS } function fixChunkOrdering(selectorNode: Selector, prefixType: ImmutableSelectorNode) { - let startChunkIndex = 0; - let moved = false; - walkSelector(selectorNode, (node, index, nodes) => { - if (node.type === `combinator`) { - startChunkIndex = index + 1; - moved = false; - } else if (matchTypeAndValue(node, prefixType)) { - if (index > 0 && !moved) { - moved = true; - nodes.splice(index, 1); - nodes.splice(startChunkIndex, 0, node); + const compound = groupCompoundSelectors(selectorNode, { + deep: true, + splitPseudoElements: false, + }); + walkSelector(compound, (node) => { + if (node.type === `compound_selector`) { + const simpleNodes = node.nodes; + for (let i = 1; i < simpleNodes.length; i++) { + const childNode = simpleNodes[i]; + if (matchTypeAndValue(childNode, prefixType)) { + const chunk = simpleNodes.splice(i, simpleNodes.length - i); + simpleNodes.unshift(...chunk); + break; + } } } - return undefined; }); + return compound; } function containsMatchInFirstChunk( @@ -171,6 +178,14 @@ function containsMatchInFirstChunk( if (node.type === `combinator`) { return walkSelector.stopAll; } else if (node.type === 'pseudo_class') { + // TODO: support nested match :is(.mixin) + // if (node.nodes) { + // for (const innerSelectorNode of node.nodes) { + // if (containsMatchInFirstChunk(prefixType, innerSelectorNode)) { + // isMatch = true; + // } + // } + // } return walkSelector.skipNested; } else if (matchTypeAndValue(node, prefixType)) { isMatch = true; diff --git a/packages/core/src/stylable-meta.ts b/packages/core/src/stylable-meta.ts index 683c48046..67b51c7eb 100644 --- a/packages/core/src/stylable-meta.ts +++ b/packages/core/src/stylable-meta.ts @@ -64,7 +64,6 @@ export class StylableMeta { public mappedKeyframes: Record = {}; public customSelectors: Record = {}; public urls: string[] = []; - public parent?: StylableMeta; public transformDiagnostics: Diagnostics | null = null; public transformedScopes: Record | null = null; public scopes: postcss.AtRule[] = []; diff --git a/packages/core/src/stylable-mixins.ts b/packages/core/src/stylable-mixins.ts index f5f3b3040..7e139e04a 100644 --- a/packages/core/src/stylable-mixins.ts +++ b/packages/core/src/stylable-mixins.ts @@ -223,9 +223,7 @@ function createMixinRootFromCSSResolve( cssVarsMapping ); - const mixinMeta: StylableMeta = isRootMixin - ? resolvedClass.meta - : createInheritedMeta(resolvedClass); + const mixinMeta: StylableMeta = resolvedClass.meta; const symbolName = isRootMixin ? 'default' : mix.mixin.type; transformer.transformAst( @@ -234,7 +232,8 @@ function createMixinRootFromCSSResolve( undefined, resolvedArgs, path.concat(symbolName + ' from ' + meta.source), - true + true, + resolvedClass.symbol.name ); fixRelativeUrls(mixinRoot, mixinMeta.source, meta.source); @@ -334,28 +333,16 @@ function handleLocalClassMixin( transformer.transformAst( mixinRoot, - isRootMixin ? meta : createInheritedMeta({ meta, symbol: mix.ref, _kind: 'css' }), + meta, undefined, resolvedArgs, path.concat(mix.mixin.type + ' from ' + meta.source), - true + true, + mix.ref.name ); mergeRules(mixinRoot, rule); } -function createInheritedMeta({ meta, symbol }: CSSResolve) { - const mixinMeta: StylableMeta = Object.create(meta); - mixinMeta.data = { ...meta.data }; - mixinMeta.parent = meta; - STSymbol.inheritSymbols(meta, mixinMeta); - STSymbol.forceSetSymbol({ - meta: mixinMeta, - symbol: STSymbol.getAll(mixinMeta)[symbol.name], // ToDo: check as an alternative: `symbol`; - localName: meta.root, - }); - return mixinMeta; -} - function getMixinDeclaration(rule: postcss.Rule): postcss.Declaration | undefined { return ( rule.nodes && diff --git a/packages/core/src/stylable-transformer.ts b/packages/core/src/stylable-transformer.ts index 1b427c8ba..c9a00874f 100644 --- a/packages/core/src/stylable-transformer.ts +++ b/packages/core/src/stylable-transformer.ts @@ -169,7 +169,8 @@ export class StylableTransformer { metaExports?: StylableExports, tsVarOverride?: Record, path: string[] = [], - mixinTransform = false + mixinTransform = false, + topNestClassName = `` ) { this.evaluator.tsVarOverride = tsVarOverride; const transformContext = { @@ -191,7 +192,7 @@ export class StylableTransformer { if (isChildOfAtRule(rule, 'keyframes')) { return; } - rule.selector = this.scopeRule(meta, rule); + rule.selector = this.scopeRule(meta, rule, topNestClassName); }); ast.walkAtRules((atRule) => { @@ -302,8 +303,8 @@ export class StylableTransformer { public resolveSelectorElements(meta: StylableMeta, selector: string): ResolvedElement[][] { return this.scopeSelector(meta, selector).elements; } - public scopeRule(meta: StylableMeta, rule: postcss.Rule): string { - return this.scopeSelector(meta, rule.selector, rule).selector; + public scopeRule(meta: StylableMeta, rule: postcss.Rule, topNestClassName?: string): string { + return this.scopeSelector(meta, rule.selector, rule, topNestClassName).selector; } public scope(name: string, ns: string, delimiter: string = this.delimiter) { return namespace(name, ns, delimiter); @@ -311,13 +312,15 @@ export class StylableTransformer { public scopeSelector( originMeta: StylableMeta, selector: string, - rule?: postcss.Rule + rule?: postcss.Rule, + topNestClassName?: string ): { selector: string; elements: ResolvedElement[][]; targetSelectorAst: SelectorList } { const context = new ScopeContext( originMeta, this.resolver, parseSelectorWithCache(selector, { clone: true }), - rule || postcss.rule({ selector }) + rule || postcss.rule({ selector }), + topNestClassName ); const targetSelectorAst = this.scopeSelectorAst(context); return { @@ -377,7 +380,7 @@ export class StylableTransformer { return outputAst; } private handleCompoundNode(context: Required) { - const { currentAnchor, node, originMeta } = context; + const { currentAnchor, node, originMeta, topNestClassName } = context; const resolvedSymbols = this.getResolvedSymbols(originMeta); const transformerContext = { meta: originMeta, @@ -540,7 +543,10 @@ export class StylableTransformer { * the general `st-symbol` feature because the actual symbol can * be a type-element symbol that is actually an imported root in a mixin */ - const origin = STSymbol.get(originMeta, originMeta.root) as ClassSymbol; + const origin = STSymbol.get( + originMeta, + topNestClassName || originMeta.root + ) as ClassSymbol; context.setCurrentAnchor({ name: origin.name, type: 'class', @@ -746,7 +752,8 @@ export class ScopeContext { public originMeta: StylableMeta, public resolver: StylableResolver, public selectorAst: SelectorList, - public rule: postcss.Rule + public rule: postcss.Rule, + public topNestClassName: string = `` ) {} public initRootAnchor(anchor: ScopeAnchor) { this.currentAnchor = anchor; @@ -780,7 +787,13 @@ export class ScopeContext { } } public createNestedContext(selectorAst: SelectorList) { - const ctx = new ScopeContext(this.originMeta, this.resolver, selectorAst, this.rule); + const ctx = new ScopeContext( + this.originMeta, + this.resolver, + selectorAst, + this.rule, + this.topNestClassName + ); Object.assign(ctx, this); ctx.selectorAst = selectorAst; diff --git a/packages/core/test/helpers/rule.spec.ts b/packages/core/test/helpers/rule.spec.ts index 7269945d8..e9da550f9 100644 --- a/packages/core/test/helpers/rule.spec.ts +++ b/packages/core/test/helpers/rule.spec.ts @@ -62,7 +62,7 @@ describe(`helpers/rule`, () => { { selector: '& .y' }, { selector: '&:not(.x)' }, { selector: '& &.x:hover' }, - { selector: '&.x.y' }, + { selector: '&.y.x' }, { selector: '&&' }, // TODO: check if possible { selector: '&' }, ]; diff --git a/packages/core/test/mixins/css-mixins.spec.ts b/packages/core/test/mixins/css-mixins.spec.ts index 6aef3260c..695132cc8 100644 --- a/packages/core/test/mixins/css-mixins.spec.ts +++ b/packages/core/test/mixins/css-mixins.spec.ts @@ -7,6 +7,8 @@ import { matchAllRulesAndDeclarations, matchRuleAndDeclaration, testInlineExpects, + testStylableCore, + shouldReportNoDiagnostics, } from '@stylable/core-test-kit'; import { processorWarnings } from '@stylable/core'; @@ -184,6 +186,48 @@ describe('CSS Mixins', () => { testInlineExpects(result); }); + it('should reorder selector to context', () => { + const { sheets } = testStylableCore({ + '/mixin.st.css': ` + .root { + -st-states: x; + } + .mixin {-st-states: mix-state;} + .root:x.mixin:mix-state { + z-index: 1; + } + .root:x.mixin:mix-state[attr].y { + z-index: 1; + } + .mixin:is(.y.mixin:mix-state) { + z-index: 1; + } + .x.mixin[a] .y.mixin[b] { + z-index: 1; + } + :is(.x.mixin:is(.y.mixin)) { + z-index: 1; + } + + `, + 'entry.st.css': ` + @st-import [mixin] from "./mixin.st.css"; + + /* + @rule[1] .entry__y.mixin--mix-state.mixin__root.mixin--x + @rule[2] .entry__y.mixin--mix-state[attr].mixin__y.mixin__root.mixin--x + @rule[3] .entry__y:is(.entry__y.mixin--mix-state.mixin__y) + @rule[4] .entry__y[a].mixin__x .entry__y[b].mixin__y + */ + .y { + -st-mixin: mixin; + } + `, + }); + //@TODO-rule[5] :is(.entry__y:is(.entry__y.mixin__y).mixin__x) + shouldReportNoDiagnostics(sheets[`/entry.st.css`].meta); + }); + it.skip('mixin with multiple rules in keyframes', () => { // const result = generateStylableRoot({ // entry: `/entry.st.css`,