UNPKG

@stylable/core

Version:

CSS for Components

944 lines (943 loc) 43.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScopeContext = exports.InferredSelector = exports.StylableTransformer = exports.transformerDiagnostics = void 0; const is_vendor_prefixed_1 = __importDefault(require("is-vendor-prefixed")); const postcss = __importStar(require("postcss")); const diagnostics_1 = require("./diagnostics"); const functions_1 = require("./functions"); const native_reserved_lists_1 = require("./native-reserved-lists"); const selector_1 = require("./helpers/selector"); const eql_1 = require("./helpers/eql"); const css_selector_parser_1 = require("@tokey/css-selector-parser"); const rule_1 = require("./helpers/rule"); const resolve_1 = require("./helpers/resolve"); const features_1 = require("./features"); const stylable_resolver_1 = require("./stylable-resolver"); const css_custom_property_1 = require("./helpers/css-custom-property"); const postcss_ast_extension_1 = require("./deprecated/postcss-ast-extension"); exports.transformerDiagnostics = { UNKNOWN_PSEUDO_ELEMENT: (0, diagnostics_1.createDiagnosticReporter)('12001', 'error', (name) => `unknown pseudo element "${name}"`), }; class StylableTransformer { constructor(options) { this.directiveNodes = []; this.containerInferredSelectorMap = new Map(); this.diagnostics = options.diagnostics; this.keepValues = options.keepValues || false; this.fileProcessor = options.fileProcessor; this.replaceValueHook = options.replaceValueHook; this.postProcessor = options.postProcessor; this.experimentalSelectorInference = options.experimentalSelectorInference === true; this.resolver = new stylable_resolver_1.StylableResolver(options.fileProcessor, options.requireModule, options.moduleResolver, options.resolverCache || new Map()); this.mode = options.mode || 'production'; this.defaultStVarOverride = options.stVarOverride || {}; this.getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.resolver, this.diagnostics); this.evaluator = new functions_1.StylableEvaluator({ valueHook: this.replaceValueHook, getResolvedSymbols: this.getResolvedSymbols, }); } transform(meta) { meta.exports = { classes: {}, vars: {}, stVars: {}, keyframes: {}, layers: {}, containers: {}, }; meta.transformedScopes = null; meta.targetAst = meta.sourceAst.clone(); const context = { meta, diagnostics: this.diagnostics, resolver: this.resolver, evaluator: this.evaluator, getResolvedSymbols: this.getResolvedSymbols, }; features_1.STImport.hooks.transformInit({ context }); features_1.STGlobal.hooks.transformInit({ context }); if (!this.experimentalSelectorInference) { meta.transformedScopes = validateScopes(this, meta); } this.transformAst(meta.targetAst, meta, meta.exports); meta.transformDiagnostics = this.diagnostics; const result = { meta, exports: meta.exports }; return this.postProcessor ? this.postProcessor(result, this) : result; } transformAst(ast, meta, metaExports, stVarOverride = this.defaultStVarOverride, path = [], mixinTransform = false, inferredSelectorMixin) { if (meta.type !== 'stylable') { return; } const { evaluator } = this; const prevStVarOverride = evaluator.stVarOverride; evaluator.stVarOverride = stVarOverride; const transformContext = { meta, diagnostics: this.diagnostics, resolver: this.resolver, evaluator, getResolvedSymbols: this.getResolvedSymbols, passedThrough: path.slice(), inferredSelectorMixin, }; const transformResolveOptions = { context: transformContext, }; prepareAST(transformContext, ast, this.experimentalSelectorInference); const cssClassResolve = features_1.CSSClass.hooks.transformResolve(transformResolveOptions); const stVarResolve = features_1.STVar.hooks.transformResolve(transformResolveOptions); const keyframesResolve = features_1.CSSKeyframes.hooks.transformResolve(transformResolveOptions); const layerResolve = features_1.CSSLayer.hooks.transformResolve(transformResolveOptions); const containsResolve = features_1.CSSContains.hooks.transformResolve(transformResolveOptions); const cssVarsMapping = features_1.CSSCustomProperty.hooks.transformResolve(transformResolveOptions); const handleAtRule = (atRule) => { const { name } = atRule; if (name === 'media') { features_1.CSSMedia.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: {}, transformer: this, }); } else if (name === 'property') { features_1.CSSCustomProperty.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: cssVarsMapping, transformer: this, }); } else if (name === 'keyframes') { features_1.CSSKeyframes.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: keyframesResolve, transformer: this, }); } else if (name === 'layer') { features_1.CSSLayer.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: layerResolve, transformer: this, }); } else if (name === 'import') { features_1.CSSLayer.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: layerResolve, transformer: this, }); } else if (name === 'container') { features_1.CSSContains.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: containsResolve, transformer: this, }); } else if (name === 'st-scope') { features_1.STScope.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: containsResolve, transformer: this, }); } else if (name === 'custom-selector') { features_1.STCustomSelector.hooks.transformAtRuleNode({ context: transformContext, atRule, resolved: containsResolve, transformer: this, }); } }; const handleDeclaration = (decl) => { if ((0, css_custom_property_1.validateCustomPropertyName)(decl.prop)) { features_1.CSSCustomProperty.hooks.transformDeclaration({ context: transformContext, decl, resolved: cssVarsMapping, }); } else if (decl.prop === `animation` || decl.prop === `animation-name`) { features_1.CSSKeyframes.hooks.transformDeclaration({ context: transformContext, decl, resolved: keyframesResolve, }); } else if (decl.prop === 'container' || decl.prop === 'container-name') { features_1.CSSContains.hooks.transformDeclaration({ context: transformContext, decl, resolved: containsResolve, }); } if (decl.prop.startsWith('-st-')) { if (this.mode === 'production') { this.directiveNodes.push(decl); } return; } decl.value = this.evaluator.evaluateValue(transformContext, { value: decl.value, meta, node: decl, }).outputValue; }; ast.walk((node) => { if (node.type === 'rule') { if ((0, rule_1.isChildOfAtRule)(node, 'keyframes')) { return; } // get context inferred selector let currentParent = node.parent; while (currentParent && !this.containerInferredSelectorMap.has(currentParent)) { currentParent = currentParent.parent; } // transform selector const { selector, inferredSelector } = this.scopeSelector(meta, node.selector, node, currentParent && this.containerInferredSelectorMap.get(currentParent), inferredSelectorMixin); // save results this.containerInferredSelectorMap.set(node, inferredSelector); node.selector = selector; } else if (node.type === 'atrule') { handleAtRule(node); } else if (node.type === 'decl') { handleDeclaration(node); } }); if (!mixinTransform && meta.targetAst && this.mode === 'development') { features_1.CSSClass.addDevRules(transformContext); } const lastPassParams = { context: transformContext, ast, transformer: this, path, }; if (this.experimentalSelectorInference) { features_1.STScope.hooks.transformLastPass(lastPassParams); } features_1.STMixin.hooks.transformLastPass(lastPassParams); if (!mixinTransform) { features_1.STGlobal.hooks.transformLastPass(lastPassParams); for (const node of this.directiveNodes) { node.remove(); } } if (metaExports) { features_1.CSSClass.hooks.transformJSExports({ exports: metaExports, resolved: cssClassResolve, }); features_1.STVar.hooks.transformJSExports({ exports: metaExports, resolved: stVarResolve, }); features_1.CSSKeyframes.hooks.transformJSExports({ exports: metaExports, resolved: keyframesResolve, }); features_1.CSSLayer.hooks.transformJSExports({ exports: metaExports, resolved: layerResolve, }); features_1.CSSContains.hooks.transformJSExports({ exports: metaExports, resolved: containsResolve, }); features_1.CSSCustomProperty.hooks.transformJSExports({ exports: metaExports, resolved: cssVarsMapping, }); } // restore evaluator state this.evaluator.stVarOverride = prevStVarOverride; } resolveSelectorElements(meta, selector) { return this.scopeSelector(meta, selector).elements; } scopeSelector(originMeta, selector, selectorNode, inferredNestSelector, inferredMixinSelector, unwrapGlobals = false) { const context = this.createSelectorContext(originMeta, (0, selector_1.parseSelectorWithCache)(selector, { clone: true }), selectorNode || postcss.rule({ selector }), selector, inferredNestSelector, inferredMixinSelector); const targetSelectorAst = this.scopeSelectorAst(context); if (unwrapGlobals) { features_1.STGlobal.unwrapPseudoGlobals(targetSelectorAst); } return { targetSelectorAst, selector: (0, selector_1.stringifySelector)(targetSelectorAst), elements: context.elements, inferredSelector: context.inferredMultipleSelectors, }; } createSelectorContext(meta, selectorAst, selectorNode, selectorStr, selectorNest, selectorMixin) { return new ScopeContext(meta, this.resolver, selectorAst, selectorNode, this.scopeSelectorAst.bind(this), this, selectorNest, selectorMixin, undefined, selectorStr); } createInferredSelector(meta, { name, type }) { const resolvedSymbols = this.getResolvedSymbols(meta); const resolved = resolvedSymbols[type][name]; return new InferredSelector(this, resolved); } scopeSelectorAst(context) { // group compound selectors: .a.b .c:hover, a .c:hover -> [[[.a.b], [.c:hover]], [[.a], [.c:hover]]] const selectorList = (0, css_selector_parser_1.groupCompoundSelectors)(context.selectorAst); // loop over selectors for (const selector of selectorList) { context.elements.push([]); context.selectorIndex++; context.selector = selector; // loop over nodes for (const node of [...selector.nodes]) { if (node.type !== `compound_selector`) { if (node.type === 'combinator') { if (this.experimentalSelectorInference || context.isNested) { context.setNextSelectorScope(context.inferredSelectorContext, node); } } continue; } context.compoundSelector = node; // loop over each node in a compound selector for (const compoundNode of node.nodes) { if (compoundNode.type === 'universal' && this.experimentalSelectorInference) { context.setNextSelectorScope([ { _kind: 'css', meta: context.originMeta, symbol: features_1.CSSType.createSymbol({ name: '*' }), }, ], node); } context.node = compoundNode; // transform node this.handleCompoundNode(context); } } // add inferred selector end to multiple selector context.inferredMultipleSelectors.add(context.inferredSelector); if (selectorList.length - 1 > context.selectorIndex) { // reset current anchor for all except last selector context.inferredSelector = new InferredSelector(this, context.inferredSelectorStart); } } // backwards compatibility for elements - empty selector still have an empty first target if (selectorList.length === 0) { context.elements.push([]); } const targetAst = (0, css_selector_parser_1.splitCompoundSelectors)(selectorList); context.splitSelectors.duplicateSelectors(targetAst); for (let i = 0; i < targetAst.length; i++) { context.selectorAst[i] = targetAst[i]; } return targetAst; } handleCompoundNode(context) { const { inferredSelector, node, originMeta } = context; const transformerContext = { meta: originMeta, diagnostics: this.diagnostics, resolver: this.resolver, evaluator: this.evaluator, getResolvedSymbols: this.getResolvedSymbols, }; if (node.type === 'class') { features_1.CSSClass.hooks.transformSelectorNode({ context: transformerContext, selectorContext: context, node, }); } else if (node.type === 'type') { features_1.CSSType.hooks.transformSelectorNode({ context: transformerContext, selectorContext: context, node, }); } else if (node.type === 'pseudo_element') { if (node.value === ``) { // partial psuedo elemennt: `.x::` // ToDo: currently the transformer corrects the css without warning, // should stylable warn? return; } const inferredElement = inferredSelector.getPseudoElements({ isFirstInSelector: context.isFirstInSelector(node), name: node.value, experimentalSelectorInference: this.experimentalSelectorInference, })[node.value]; if (inferredElement) { context.setNextSelectorScope(inferredElement.inferred, node, node.value); if (context.transform) { context.transformIntoMultiSelector(node, inferredElement.selectors); } } else { // first definition of a part in the extends/alias chain context.setNextSelectorScope([], node, node.value); if (!native_reserved_lists_1.nativePseudoElements.includes(node.value) && !(0, is_vendor_prefixed_1.default)(node.value) && !context.isDuplicateStScopeDiagnostic()) { this.diagnostics.report(exports.transformerDiagnostics.UNKNOWN_PSEUDO_ELEMENT(node.value), { node: context.ruleOrAtRule, word: node.value, }); } } } else if (node.type === 'pseudo_class') { const isCustomSelector = features_1.STCustomSelector.hooks.transformSelectorNode({ context: transformerContext, selectorContext: context, node, }); if (!isCustomSelector) { features_1.CSSPseudoClass.hooks.transformSelectorNode({ context: transformerContext, selectorContext: context, node, }); } } else if (node.type === `nesting`) { context.setNextSelectorScope(context.inferredSelectorNest, node, node.value); } else if (node.type === 'attribute') { features_1.STMixin.hooks.transformSelectorNode({ context: transformerContext, selectorContext: context, node, }); } } } exports.StylableTransformer = StylableTransformer; function validateScopes(transformer, meta) { const transformedScopes = {}; for (const scope of meta.scopes) { const len = transformer.diagnostics.reports.length; const rule = postcss.rule({ selector: scope.params }); const context = transformer.createSelectorContext(meta, (0, selector_1.parseSelectorWithCache)(rule.selector, { clone: true }), rule, rule.selector); transformedScopes[rule.selector] = (0, css_selector_parser_1.groupCompoundSelectors)(transformer.scopeSelectorAst(context)); const ruleReports = transformer.diagnostics.reports.splice(len); for (const { code, message, severity, word } of ruleReports) { transformer.diagnostics.report({ code, message, severity, }, { node: scope, word: word || scope.params, }); } } return transformedScopes; } function removeInitialCompoundMarker(selector, meta, structureMode) { let hadCompoundStart = false; const compoundedSelector = (0, css_selector_parser_1.groupCompoundSelectors)(selector); const first = compoundedSelector.nodes.find(({ type }) => type === `compound_selector`); if (first) { const matchNode = structureMode ? (node) => node.type === 'nesting' : (node) => node.type === 'class' && node.value === meta.root; for (let i = 0; i < first.nodes.length; i++) { const node = first.nodes[i]; if (node.type === 'comment') { continue; } if (matchNode(node)) { hadCompoundStart = true; first.nodes.splice(i, 1); } break; } } return { selector: (0, css_selector_parser_1.splitCompoundSelectors)(compoundedSelector), hadCompoundStart }; } class InferredSelector { constructor(api, resolve) { this.api = api; this.resolveSet = new Set(); if (resolve) { this.add(resolve); } } isEmpty() { return this.resolveSet.size === 0; } set(resolve) { if (resolve === this) { return; } this.resolveSet.clear(); this.add(resolve); } clone() { return new InferredSelector(this.api, this); } /** * Adds to the set of inferred resolved CSS * Assumes passes CSSResolved from the same meta/symbol are * the same from the same cached transform process to dedupe them. */ add(resolve) { if (resolve instanceof InferredSelector) { resolve.resolveSet.forEach((resolve) => this.add(resolve)); } else { this.resolveSet.add(resolve); } } /** * Takes a CSS part resolve and use it extend the current set of inferred resolved. * Used to expand the resolved mapped selector with the part definition * e.g. part can add nested states/parts that override the inferred mapped selector. */ addPartOverride(partResolve) { const newSet = new Set(); for (const resolve of this.resolveSet) { newSet.add([partResolve, ...resolve]); } if (!this.resolveSet.size) { newSet.add([partResolve]); } this.resolveSet = newSet; } getPseudoClasses({ name: searchedName } = {}) { const collectedStates = {}; const resolvedCount = {}; const expectedIntersectionCount = this.resolveSet.size; // ToDo: dec for any types const addInferredState = (name, meta, state) => { const existing = collectedStates[name]; if (!existing) { collectedStates[name] = { meta, state }; resolvedCount[name] = 1; } else { const isStatesEql = (0, eql_1.isEqual)(existing.state, state); if (isStatesEql && // states from same meta (existing.meta === meta || // global states typeof state === 'string' || state?.type === 'template')) { resolvedCount[name]++; } } }; // infer states from multiple resolved selectors for (const resolvedContext of this.resolveSet.values()) { const resolvedFoundNames = new Set(); resolved: for (const { symbol, meta } of resolvedContext) { const states = symbol[`-st-states`]; if (!states) { continue; } if (searchedName) { if (Object.hasOwnProperty.call(states, searchedName)) { // track state addInferredState(searchedName, meta, states[searchedName]); break resolved; } } else { // get all states for (const [name, state] of Object.entries(states)) { if (!resolvedFoundNames.has(name)) { // track state resolvedFoundNames.add(name); addInferredState(name, meta, state); } } } } } // strict: remove states that do not exist on ALL resolved selectors return expectedIntersectionCount > 1 ? Object.entries(collectedStates).reduce((resultStates, [name, InferredState]) => { if (resolvedCount[name] >= expectedIntersectionCount) { resultStates[name] = InferredState; } return resultStates; }, {}) : collectedStates; } getPseudoElements({ isFirstInSelector, experimentalSelectorInference, name, }) { const collectedElements = {}; const resolvedCount = {}; const checked = {}; const expectedIntersectionCount = this.resolveSet.size; // ToDo: dec for any types const addInferredElement = (name, inferred, selectors) => { const item = (collectedElements[name] || (collectedElements[name] = { inferred: new InferredSelector(this.api), selectors: [], })); // check inferred matching if (!item.inferred.matchedElement(inferred)) { // ToDo: bailout fast return; } // add match resolvedCount[name]++; item.inferred.add(inferred); item.selectors.push(...selectors); }; // infer elements from multiple resolved selectors for (const resolvedContext of this.resolveSet.values()) { /** * search for elements in each resolved selector. * start at 1 for legacy flat mode to prefer inherited elements over local */ const startIndex = resolvedContext.length === 1 || (resolvedContext[0] && (features_1.STStructure.isStructureMode(resolvedContext[0].meta) || resolvedContext[0].symbol._kind === 'part')) ? 0 : 1; resolved: for (let i = startIndex; i < resolvedContext.length; i++) { const { symbol, meta } = resolvedContext[i]; const structureMode = features_1.STStructure.isStructureMode(meta); if (symbol._kind !== 'part' && (symbol.alias || (!structureMode && !symbol['-st-root']))) { // non-root & alias classes don't have parts: bailout continue; } if (name) { const cacheContext = symbol._kind === 'part' ? symbol.id : symbol.name; const uniqueId = meta.source + '::' + cacheContext; resolvedCount[name] ?? (resolvedCount[name] = 0); checked[name] || (checked[name] = new Map()); if (checked[name].has(uniqueId)) { if (checked[name].get(uniqueId)) { resolvedCount[name]++; } continue; } // get part symbol const partDef = features_1.STStructure.getPart(symbol, name); // save to cache checked[name].set(uniqueId, !!partDef); if (!partDef) { continue; } if (Array.isArray(partDef.mapTo)) { // prefer custom selector const selectorList = (0, selector_1.cloneSelector)(partDef.mapTo); const selectorStr = (0, selector_1.stringifySelector)(partDef.mapTo); selectorList.forEach((selector) => { const r = removeInitialCompoundMarker(selector, meta, structureMode); selector.nodes = r.selector.nodes; selector.before = ''; if (!r.hadCompoundStart && !isFirstInSelector) { selector.nodes.unshift((0, selector_1.createCombinatorSelector)({ combinator: 'space' })); } }); const internalContext = this.api.createSelectorContext(meta, selectorList, postcss.rule({ selector: selectorStr }), selectorStr); internalContext.isStandaloneSelector = isFirstInSelector; if (!structureMode && experimentalSelectorInference) { internalContext.inferredSelectorStart.set(this.api.createInferredSelector(meta, { name: 'root', type: 'class', })); internalContext.inferredSelector.set(internalContext.inferredSelectorStart); } const customAstSelectors = this.api.scopeSelectorAst(internalContext); const inferred = customAstSelectors.length === 1 || experimentalSelectorInference ? internalContext.inferredMultipleSelectors : new InferredSelector(this.api, [ { _kind: 'css', meta, symbol: features_1.CSSType.createSymbol({ name: '*' }), }, ]); // add part resolve to inferred resolve set if (structureMode) { inferred.addPartOverride({ _kind: 'css', meta, symbol: partDef }); } addInferredElement(name, inferred, customAstSelectors); break resolved; } else { // matching class part const resolvedPart = this.api.getResolvedSymbols(meta).class[name]; const resolvedBaseSymbol = (0, resolve_1.getOriginDefinition)(resolvedPart); const nodes = []; // insert descendant combinator before internal custom element if (!resolvedBaseSymbol.symbol[`-st-root`] && !isFirstInSelector) { nodes.push((0, selector_1.createCombinatorSelector)({ combinator: 'space' })); } // create part class const classNode = {}; features_1.CSSClass.namespaceClass(resolvedBaseSymbol.meta, resolvedBaseSymbol.symbol, classNode); nodes.push(classNode); addInferredElement(name, new InferredSelector(this.api, resolvedPart), [ { type: 'selector', after: '', before: '', end: 0, start: 0, nodes }, ]); break resolved; } } else { // ToDo: implement get all elements } } } // strict: remove elements that do not exist on ALL resolved selectors return expectedIntersectionCount > 1 ? Object.entries(collectedElements).reduce((resultElements, [name, InferredElement]) => { if (resolvedCount[name] >= expectedIntersectionCount) { resultElements[name] = InferredElement; } return resultElements; }, {}) : collectedElements; } matchedElement(inferred) { for (const target of this.resolveSet) { const targetBaseElementSymbol = (0, resolve_1.getOriginDefinition)(target); for (const tested of inferred.resolveSet) { const testedBaseElementSymbol = (0, resolve_1.getOriginDefinition)(tested); if (targetBaseElementSymbol !== testedBaseElementSymbol) { return false; } } } return true; } // function to temporarily handle single resolved selector type while refactoring // ToDo: remove temporarily single resolve getSingleResolve() { if (this.resolveSet.size !== 1) { return []; } return this.resolveSet.values().next().value; } } exports.InferredSelector = InferredSelector; class SelectorMultiplier { constructor() { this.dupIndicesPerSelector = []; } addSplitPoint(selectorIndex, nodeIndex, selectors) { var _a; if (selectors.length) { (_a = this.dupIndicesPerSelector)[selectorIndex] || (_a[selectorIndex] = []); this.dupIndicesPerSelector[selectorIndex].push([nodeIndex, selectors]); } } duplicateSelectors(targetSelectors) { // iterate top level selector for (const [selectorIndex, insertionPoints] of Object.entries(this.dupIndicesPerSelector)) { const duplicationList = [targetSelectors[Number(selectorIndex)]]; // iterate insertion points for (const [nodeIndex, selectors] of insertionPoints) { // collect the duplicate selectors to be multiplied by following insertion points const added = []; // iterate selectors for insertion point for (const replaceSelector of selectors) { // duplicate selectors and replace selector at insertion point for (const originSelector of duplicationList) { const dupSelector = { ...originSelector, nodes: [...originSelector.nodes] }; dupSelector.nodes[nodeIndex] = replaceSelector; added.push(dupSelector); } } // add the duplicated selectors from insertion point to // the list of selector to be duplicated for following insertion // points and to the target selector list for (const addedSelector of added) { duplicationList.push(addedSelector); targetSelectors.push(addedSelector); } } } } } class ScopeContext { constructor(originMeta, resolver, selectorAst, ruleOrAtRule, scopeSelectorAst, transformer, inferredSelectorNest, inferredSelectorMixin, inferredSelectorContext, selectorStr) { this.originMeta = originMeta; this.resolver = resolver; this.selectorAst = selectorAst; this.ruleOrAtRule = ruleOrAtRule; this.scopeSelectorAst = scopeSelectorAst; this.transformer = transformer; this.inferredSelectorMixin = inferredSelectorMixin; this.transform = true; // source multi-selector input this.selectorStr = ''; this.selectorIndex = -1; this.elements = []; this.selectorAstResolveMap = new Map(); // store selector duplication points this.splitSelectors = new SelectorMultiplier(); // selector is not a continuation of another selector this.isStandaloneSelector = true; // combined type of the multiple selectors this.inferredMultipleSelectors = new InferredSelector(this.transformer); this.isNested = !!(ruleOrAtRule.parent && // top level ruleOrAtRule.parent.type !== 'root' && // directly in @st-scope !features_1.STScope.isStScopeStatement(ruleOrAtRule.parent)); /* resolve default selector context for initial selector and selector following a combinator. Currently set to stylesheet root for top level selectors and selectors directly nested under @st-scope. But will change in the future to a universal selector once experimentalSelectorInference will be the default behavior */ const inferredContext = inferredSelectorContext || (this.isNested || transformer.experimentalSelectorInference ? new InferredSelector(transformer, [ { _kind: 'css', meta: originMeta, symbol: features_1.CSSType.createSymbol({ name: '*' }), }, ]) : transformer.createInferredSelector(originMeta, { name: originMeta.root, type: 'class', })); // set selector data this.selectorStr = selectorStr || (0, selector_1.stringifySelector)(selectorAst); this.inferredSelectorContext = new InferredSelector(this.transformer, inferredContext); this.inferredSelectorStart = new InferredSelector(this.transformer, inferredContext); this.inferredSelectorNest = inferredSelectorNest || this.inferredSelectorContext.clone(); this.inferredSelector = new InferredSelector(this.transformer, this.inferredSelectorContext); } get experimentalSelectorInference() { return this.transformer.experimentalSelectorInference; } setNextSelectorScope(resolved, node, name) { if (name && this.selectorIndex !== undefined && this.selectorIndex !== -1) { this.elements[this.selectorIndex].push({ type: ScopeContext.legacyElementsTypesMapping[node.type] || 'unknown', name, resolved: Array.isArray(resolved) ? resolved : resolved.getSingleResolve(), }); } this.inferredSelector.set(resolved); this.selectorAstResolveMap.set(node, this.inferredSelector.clone()); this.lastInferredSelectorNode = node; } isFirstInSelector(node) { const isFirstNode = this.selectorAst[this.selectorIndex].nodes[0] === node; if (isFirstNode && this.selectorIndex === 0 && !this.isStandaloneSelector) { // force false incase a this context is a splitted part from another selector return false; } return isFirstNode; } createNestedContext(selectorAst, selectorContext) { const ctx = new ScopeContext(this.originMeta, this.resolver, selectorAst, this.ruleOrAtRule, this.scopeSelectorAst, this.transformer, this.inferredSelectorNest, this.inferredSelectorMixin, selectorContext || this.inferredSelectorContext); ctx.transform = this.transform; ctx.selectorAstResolveMap = this.selectorAstResolveMap; return ctx; } transformIntoMultiSelector(node, selectors) { // transform into the first selector Object.assign(node, selectors[0]); // keep track of additional selectors for // duplication at the end of the selector transform selectors.shift(); const selectorNode = this.selectorAst[this.selectorIndex]; const nodeIndex = selectorNode.nodes.indexOf(node); this.splitSelectors.addSplitPoint(this.selectorIndex, nodeIndex, selectors); } isDuplicateStScopeDiagnostic() { if (this.experimentalSelectorInference || this.ruleOrAtRule.type !== 'rule') { // this check is not required when experimentalSelectorInference is on // as @st-scope is not flatten at the beginning of the transformation // and diagnostics on it's selector is only checked once. return false; } // ToDo: should be removed once st-scope transformation moves to the end of the transform process const transformedScope = this.originMeta.transformedScopes?.[(0, postcss_ast_extension_1.getRuleScopeSelector)(this.ruleOrAtRule) || ``]; if (transformedScope && this.selector && this.compoundSelector) { const currentCompoundSelector = (0, selector_1.stringifySelector)(this.compoundSelector); const i = this.selector.nodes.indexOf(this.compoundSelector); for (const stScopeSelectorCompounded of transformedScope) { // if we are in a chunk index that is in the rage of the @st-scope param if (i <= stScopeSelectorCompounded.nodes.length) { for (const scopeNode of stScopeSelectorCompounded.nodes) { const scopeNodeSelector = (0, selector_1.stringifySelector)(scopeNode); // if the two chunks match the error is already reported by the @st-scope validation if (scopeNodeSelector === currentCompoundSelector) { return true; } } } } } return false; } } exports.ScopeContext = ScopeContext; ScopeContext.legacyElementsTypesMapping = { pseudo_element: 'pseudo-element', class: 'class', type: 'element', }; /** * in the process of moving transformations that shouldn't be in the analyzer. * all changes were moved here to be called at the beginning of the transformer, * and should be inlined in the process in the future. */ function prepareAST(context, ast, experimentalSelectorInference) { // ToDo: inline transformations const toRemove = []; ast.walk((node) => { const input = { context, node, toRemove }; features_1.STNamespace.hooks.prepareAST(input); features_1.STImport.hooks.prepareAST(input); if (!experimentalSelectorInference) { features_1.STScope.hooks.prepareAST(input); } features_1.STVar.hooks.prepareAST(input); if (!experimentalSelectorInference) { features_1.STCustomSelector.hooks.prepareAST(input); } features_1.CSSCustomProperty.hooks.prepareAST(input); }); for (const removeOrNode of toRemove) { typeof removeOrNode === 'function' ? removeOrNode() : removeOrNode.remove(); } } //# sourceMappingURL=stylable-transformer.js.map