UNPKG

@stylable/core

Version:

CSS for Components

1,046 lines 46.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.removeSTDirective = exports.StylableTransformer = exports.transformerWarnings = void 0; const path_1 = require("path"); const postcss_1 = __importDefault(require("postcss")); const postcss_value_parser_1 = __importDefault(require("postcss-value-parser")); const is_vendor_prefixed_1 = __importDefault(require("is-vendor-prefixed")); const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep")); const custom_values_1 = require("./custom-values"); const functions_1 = require("./functions"); const native_reserved_lists_1 = require("./native-reserved-lists"); const pseudo_states_1 = require("./pseudo-states"); const selector_utils_1 = require("./selector-utils"); const stylable_mixins_1 = require("./stylable-mixins"); const stylable_resolver_1 = require("./stylable-resolver"); const stylable_utils_1 = require("./stylable-utils"); const stylable_value_parsers_1 = require("./stylable-value-parsers"); const { hasOwnProperty } = Object.prototype; const USE_SCOPE_SELECTOR_2 = true; exports.transformerWarnings = { UNKNOWN_PSEUDO_ELEMENT(name) { return `unknown pseudo element "${name}"`; }, IMPORT_ISNT_EXTENDABLE() { return 'import is not extendable'; }, CANNOT_EXTEND_UNKNOWN_SYMBOL(name) { return `cannot extend unknown symbol "${name}"`; }, CANNOT_EXTEND_JS() { return 'JS import is not extendable'; }, KEYFRAME_NAME_RESERVED(name) { return `keyframes "${name}" is reserved`; }, UNKNOWN_IMPORT_ALIAS(name) { return `cannot use alias for unknown import "${name}"`; }, SCOPE_PARAM_NOT_ROOT(name) { return `"@st-scope" parameter "${name}" does not resolve to a stylesheet root`; }, SCOPE_PARAM_NOT_CSS(name) { return `"@st-scope" parameter "${name}" must be a Stylable stylesheet, instead name originated from a JavaScript file`; }, UNKNOWN_SCOPING_PARAM(name) { return `"@st-scope" received an unknown symbol: "${name}"`; }, }; class StylableTransformer { constructor(options) { this.metaParts = new WeakMap(); this.diagnostics = options.diagnostics; this.delimiter = options.delimiter || '__'; this.keepValues = options.keepValues || false; this.fileProcessor = options.fileProcessor; this.replaceValueHook = options.replaceValueHook; this.postProcessor = options.postProcessor; this.resolver = new stylable_resolver_1.StylableResolver(options.fileProcessor, options.requireModule); this.mode = options.mode || 'production'; } transform(meta) { const metaExports = { classes: {}, vars: {}, stVars: {}, keyframes: {}, }; const ast = this.resetTransformProperties(meta); this.resolver.validateImports(meta, this.diagnostics); validateScopes(meta, this.resolver, this.diagnostics); this.transformAst(ast, meta, metaExports); this.transformGlobals(ast, meta); meta.transformDiagnostics = this.diagnostics; const result = { meta, exports: metaExports }; return this.postProcessor ? this.postProcessor(result, this) : result; } transformAst(ast, meta, metaExports, variableOverride, path = [], mixinTransform = false) { const keyframeMapping = this.scopeKeyframes(ast, meta); const cssVarsMapping = this.createCSSVarsMapping(ast, meta); ast.walkRules((rule) => { if (selector_utils_1.isChildOfAtRule(rule, 'keyframes')) { return; } rule.selector = this.scopeRule(meta, rule, metaExports && metaExports.classes); }); ast.walkAtRules(/media$/, (atRule) => { atRule.params = functions_1.evalDeclarationValue(this.resolver, atRule.params, meta, atRule, variableOverride, this.replaceValueHook, this.diagnostics, path.slice(), undefined, undefined); }); ast.walkDecls((decl) => { stylable_utils_1.getDeclStylable(decl).sourceValue = decl.value; if (stylable_utils_1.isCSSVarProp(decl.prop)) { decl.prop = this.getScopedCSSVar(decl, meta, cssVarsMapping); } switch (decl.prop) { case stylable_value_parsers_1.valueMapping.mixin: break; case stylable_value_parsers_1.valueMapping.states: pseudo_states_1.validateStateDefinition(decl, meta, this.resolver, this.diagnostics); break; default: decl.value = functions_1.evalDeclarationValue(this.resolver, decl.value, meta, decl, variableOverride, this.replaceValueHook, this.diagnostics, path.slice(), cssVarsMapping, undefined); } }); if (USE_SCOPE_SELECTOR_2) { if (!mixinTransform && meta.outputAst && this.mode === 'development') { this.addDevRules(meta); } } ast.walkRules((rule) => stylable_mixins_1.appendMixins(this, rule, meta, variableOverride || {}, cssVarsMapping, path)); if (metaExports) { if (USE_SCOPE_SELECTOR_2) { Object.assign(metaExports.classes, this.exportClasses(meta)); } else { this.exportRootClass(meta, metaExports.classes); } this.exportLocalVars(meta, metaExports.stVars, variableOverride); this.exportKeyframes(keyframeMapping, metaExports.keyframes); this.exportCSSVars(cssVarsMapping, metaExports.vars); } } exportLocalVars(meta, stVarsExport, variableOverride) { for (const varSymbol of meta.vars) { const { outputValue, topLevelType } = functions_1.processDeclarationValue(this.resolver, varSymbol.text, meta, varSymbol.node, variableOverride); stVarsExport[varSymbol.name] = topLevelType ? custom_values_1.unbox(topLevelType) : outputValue; } } exportCSSVars(cssVarsMapping, varsExport) { for (const varName of Object.keys(cssVarsMapping)) { varsExport[varName.slice(2)] = cssVarsMapping[varName]; } } exportKeyframes(keyframeMapping, keyframesExport) { Object.assign(keyframesExport, keyframeMapping); } exportRootClass(meta, classesExport) { const classExports = {}; this.handleClass(meta, { type: 'class', name: meta.mappedSymbols[meta.root].name, nodes: [], }, meta.mappedSymbols[meta.root].name, classExports); classesExport[meta.root] = classExports[meta.mappedSymbols[meta.root].name]; } exportClass(meta, name, classSymbol, metaExports) { const scopedName = this.scope(name, meta.namespace); if (metaExports && !metaExports[name]) { const extend = classSymbol ? classSymbol[stylable_value_parsers_1.valueMapping.extends] : undefined; let exportedClasses = scopedName; if (extend && extend !== classSymbol) { let finalSymbol; let finalName; let finalMeta; if (extend._kind === 'class') { finalSymbol = extend; finalName = extend.name; finalMeta = meta; } else if (extend._kind === 'import') { const resolved = this.resolver.deepResolve(extend); if (resolved && resolved._kind === 'css' && resolved.symbol) { if (resolved.symbol._kind === 'class') { finalSymbol = resolved.symbol; finalName = resolved.symbol.name; finalMeta = resolved.meta; } else { const found = stylable_utils_1.findRule(meta.ast, '.' + classSymbol.name); if (found) { this.diagnostics.error(found, exports.transformerWarnings.IMPORT_ISNT_EXTENDABLE(), { word: found.value }); } } } else if (resolved) { const found = stylable_utils_1.findRule(meta.ast, '.' + classSymbol.name); if (found) { if (!resolved.symbol) { this.diagnostics.error(found, exports.transformerWarnings.CANNOT_EXTEND_UNKNOWN_SYMBOL(found.value), { word: found.value }); } else { this.diagnostics.error(found, exports.transformerWarnings.CANNOT_EXTEND_JS(), { word: found.value, }); } } } } if (finalSymbol && finalName && finalMeta && !finalSymbol[stylable_value_parsers_1.valueMapping.root]) { const classExports = {}; this.handleClass(finalMeta, { type: 'class', name: finalName, nodes: [] }, finalName, classExports); if (classExports[finalName]) { exportedClasses += ' ' + classExports[finalName]; } else { console.error(`something went wrong when exporting '${finalName}', ` + `please file an issue in stylable. With specific use case`); } } } metaExports[name] = exportedClasses; } return scopedName; } scopeKeyframes(ast, meta) { ast.walkAtRules(/keyframes$/, (atRule) => { const name = atRule.params; if (~native_reserved_lists_1.reservedKeyFrames.indexOf(name)) { this.diagnostics.error(atRule, exports.transformerWarnings.KEYFRAME_NAME_RESERVED(name), { word: name, }); } atRule.params = this.scope(name, meta.namespace); }); const keyframesExports = {}; Object.keys(meta.mappedKeyframes).forEach((key) => { const res = this.resolver.resolveKeyframes(meta, key); if (res) { keyframesExports[key] = this.scope(res.symbol.alias, res.meta.namespace); } }); ast.walkDecls(/animation$|animation-name$/, (decl) => { const parsed = postcss_value_parser_1.default(decl.value); parsed.nodes.forEach((node) => { const scoped = keyframesExports[node.value]; if (scoped) { node.value = scoped; } }); decl.value = parsed.toString(); }); return keyframesExports; } createCSSVarsMapping(_ast, meta) { const cssVarsMapping = {}; // imported vars for (const imported of meta.imports) { for (const symbolName of Object.keys(imported.named)) { if (stylable_utils_1.isCSSVarProp(symbolName)) { const importedVar = this.resolver.deepResolve(meta.mappedSymbols[symbolName]); if (importedVar && importedVar._kind === 'css' && importedVar.symbol && importedVar.symbol._kind === 'cssVar') { cssVarsMapping[symbolName] = importedVar.symbol.global ? importedVar.symbol.name : stylable_utils_1.generateScopedCSSVar(importedVar.meta.namespace, importedVar.symbol.name.slice(2)); } } } } // locally defined vars for (const localVarName of Object.keys(meta.cssVars)) { const cssVar = meta.cssVars[localVarName]; if (!cssVarsMapping[localVarName]) { cssVarsMapping[localVarName] = cssVar.global ? localVarName : stylable_utils_1.generateScopedCSSVar(meta.namespace, localVarName.slice(2)); } } return cssVarsMapping; } getScopedCSSVar(decl, meta, cssVarsMapping) { let prop = decl.prop; if (meta.cssVars[prop]) { prop = cssVarsMapping[prop]; } return prop; } addGlobalsToMeta(selectorAst, meta) { if (!meta) { return; } for (const ast of selectorAst) { selector_utils_1.traverseNode(ast, (inner) => { if (inner.type === 'class') { meta.globals[inner.name] = true; } }); } } transformGlobals(ast, meta) { ast.walkRules((r) => { const selectorAst = selector_utils_1.parseSelector(r.selector); selector_utils_1.traverseNode(selectorAst, (node) => { if (node.type === 'nested-pseudo-class' && node.name === 'global') { this.addGlobalsToMeta([node], meta); node.type = 'selector'; return true; } return undefined; }); // this.addGlobalsToMeta([selectorAst], meta); r.selector = selector_utils_1.stringifySelector(selectorAst); }); } resolveSelectorElements(meta, selector) { if (USE_SCOPE_SELECTOR_2) { return this.scopeSelector2(meta, selector, undefined, true).elements; } else { return this.scopeSelector(meta, selector, undefined, true).elements; } } scopeSelector(originMeta, selector, classesExport, calcPaths = false, rule) { let meta = originMeta; let current = meta; let symbol = null; let nestedSymbol; let originSymbol; const selectorAst = selector_utils_1.parseSelector(selector); const addedSelectors = []; const elements = selectorAst.nodes.map((selectorNode) => { const selectorElements = []; selector_utils_1.traverseNode(selectorNode, (node) => { const { name, type } = node; if (calcPaths && (type === 'class' || type === 'element' || type === 'pseudo-element')) { selectorElements.push({ name, type, resolved: this.resolver.resolveExtends(current, name, type === 'element', this), }); } if (type === 'selector' || type === 'spacing' || type === 'operator') { if (nestedSymbol) { symbol = nestedSymbol; nestedSymbol = null; } else { meta = originMeta; current = originMeta; symbol = originMeta.classes[originMeta.root]; originSymbol = symbol; } } else if (type === 'class') { const next = this.handleClass(current, node, name, classesExport, rule, originMeta); originSymbol = current.classes[name]; symbol = next.symbol; current = next.meta; } else if (type === 'element') { const next = this.handleElement(current, node, name, originMeta); originSymbol = current.elements[name]; symbol = next.symbol; current = next.meta; } else if (type === 'pseudo-element') { const next = this.handlePseudoElement(current, node, name, selectorNode, addedSelectors, rule, originMeta); originSymbol = current.classes[name]; meta = current; symbol = next.symbol; current = next.meta; } else if (type === 'pseudo-class') { current = pseudo_states_1.transformPseudoStateSelector(current, node, name, symbol, meta, originSymbol, this.resolver, this.diagnostics, rule); } else if (type === 'nested-pseudo-class') { if (name === 'global') { // node.type = 'selector'; return true; } nestedSymbol = symbol; } else if (type === 'invalid' && node.value === '&' && current.parent) { const origin = current.mappedSymbols[current.root]; const next = this.handleClass(current, { type: 'class', nodes: [], name: origin.name, }, origin.name, undefined, undefined, originMeta); originSymbol = current.classes[origin.name]; symbol = next.symbol; current = next.meta; } /* do nothing */ return undefined; }); return selectorElements; }); this.addAdditionalSelectors(addedSelectors, selectorAst); return { current, symbol, selectorAst, elements, selector: selector_utils_1.stringifySelector(selectorAst), }; } addAdditionalSelectors(addedSelectors, selectorAst) { addedSelectors.forEach((s) => { const clone = lodash_clonedeep_1.default(s.selectorNode); const i = s.selectorNode.nodes.indexOf(s.node); if (i === -1) { throw new Error('not supported inside nested classes'); } else { clone.nodes[i].value = s.customElementChunk; } selectorAst.nodes.push(clone); }); } applyRootScoping(meta, selectorAst) { const scopedRoot = meta.mappedSymbols[meta.root][stylable_value_parsers_1.valueMapping.global] || this.scope(meta.root, meta.namespace); selectorAst.nodes.forEach((selector) => { const first = selector.nodes[0]; /* This finds a transformed or non transform global selector */ if (first && (first.type === 'selector' || first.type === 'nested-pseudo-class') && first.name === 'global') { return; } // -st-global can make anther global inside root if (first && first.nodes === scopedRoot) { return; } if (first && first.before && first.before === '.' + scopedRoot) { return; } if (first && first.type === 'invalid' && first.value === '&') { return; } if (!first || first.name !== scopedRoot) { selector.nodes = [ typeof scopedRoot !== 'string' ? { type: 'selector', nodes: scopedRoot, name: 'global' } : { type: 'class', name: scopedRoot, nodes: [] }, { type: 'spacing', value: ' ', name: '', nodes: [] }, ...selector.nodes, ]; } }); } scopeRule(meta, rule, _classesExport) { if (USE_SCOPE_SELECTOR_2) { return this.scopeSelector2(meta, rule.selector, undefined, false, rule).selector; } else { return this.scopeSelector(meta, rule.selector, _classesExport, false, rule).selector; } } handleClass(meta, node, name, classesExport, rule, originMeta) { const symbol = meta.classes[name]; const extend = symbol ? symbol[stylable_value_parsers_1.valueMapping.extends] : undefined; if (!extend && symbol && symbol.alias) { let next = this.resolver.deepResolve(symbol.alias); if (next && next._kind === 'css' && next.symbol && next.symbol._kind === 'class') { const globalMappedNodes = next.symbol[stylable_value_parsers_1.valueMapping.global]; if (globalMappedNodes) { node.before = ''; node.type = 'selector'; node.nodes = globalMappedNodes; this.addGlobalsToMeta(globalMappedNodes, originMeta); } else { node.name = this.exportClass(next.meta, next.symbol.name, next.symbol, classesExport); } if (next.symbol[stylable_value_parsers_1.valueMapping.extends]) { next = this.resolver.deepResolve(next.symbol[stylable_value_parsers_1.valueMapping.extends]); if (next && next._kind === 'css') { return next; } } else { return next; } } else if (rule) { this.diagnostics.error(rule, exports.transformerWarnings.UNKNOWN_IMPORT_ALIAS(name), { word: symbol.alias.name, }); } } let scopedName = ''; let globalScopedSelector = ''; const globalMappedNodes = symbol && symbol[stylable_value_parsers_1.valueMapping.global]; if (globalMappedNodes) { globalScopedSelector = selector_utils_1.stringifySelector({ type: 'selector', name: '', nodes: globalMappedNodes, }); } else { scopedName = this.exportClass(meta, name, symbol, classesExport); } if (globalScopedSelector) { node.before = ''; node.type = 'selector'; node.nodes = symbol[stylable_value_parsers_1.valueMapping.global] || []; this.addGlobalsToMeta(globalMappedNodes, originMeta); } else { node.name = scopedName; } const next = this.resolver.deepResolve(extend); if (next && next._kind === 'css' && next.symbol && next.symbol._kind === 'class') { if (this.mode === 'development' && rule && rule.selectorType === 'class') { rule.after(selector_utils_1.createWarningRule(next.symbol.name, this.scope(next.symbol.name, next.meta.namespace), path_1.basename(next.meta.source), name, this.scope(symbol.name, meta.namespace), path_1.basename(meta.source))); } return next; } // local if (extend && extend._kind === 'class') { if (extend === symbol && extend.alias) { const next = this.resolver.deepResolve(extend.alias); if (next && next._kind === 'css' && next.symbol) { return next; } } } return { _kind: 'css', meta, symbol }; } handleElement(meta, node, name, originMeta) { const tRule = meta.elements[name]; const extend = tRule ? meta.mappedSymbols[name] : undefined; const next = this.resolver.deepResolve(extend); if (next && next._kind === 'css' && next.symbol) { if (next.symbol._kind === 'class' && next.symbol[stylable_value_parsers_1.valueMapping.global]) { node.before = ''; node.type = 'selector'; node.nodes = next.symbol[stylable_value_parsers_1.valueMapping.global] || []; this.addGlobalsToMeta(node.nodes, originMeta); } else { node.type = 'class'; node.name = this.scope(next.symbol.name, next.meta.namespace); } // node.name = (next.symbol as ClassSymbol)[valueMapping.global] || // this.scope(next.symbol.name, next.meta.namespace); return next; } return { meta, symbol: tRule }; } handlePseudoElement(meta, node, name, selectorNode, addedSelectors, rule, originMeta) { let next; const customSelector = meta.customSelectors[':--' + name]; if (customSelector) { const rootRes = this.scopeSelector(meta, '.root', {}, false); const res = this.scopeSelector(meta, customSelector, {}, false); const rootEg = new RegExp('^\\s*' + rootRes.selector.replace(/\./, '\\.') + '\\s*'); const selectors = res.selectorAst.nodes.map((sel) => selector_utils_1.stringifySelector(sel).trim().replace(rootEg, '')); if (selectors[0]) { node.type = 'invalid'; /*just take it */ node.before = ' '; node.value = selectors[0]; } for (let i = 1 /*start from second one*/; i < selectors.length; i++) { addedSelectors.push({ selectorNode, node, customElementChunk: selectors[i], }); } if (res.selectorAst.nodes.length === 1 && res.symbol) { return { _kind: 'css', meta: res.current, symbol: res.symbol }; } // this is an error mode fallback return { _kind: 'css', meta, symbol: { _kind: 'element', name: '*' }, }; } // find if the current symbol exists in the initial meta; let symbol = meta.mappedSymbols[name]; let current = meta; while (!symbol) { // go up the root extends path and find first symbol const root = current.mappedSymbols[current.root]; next = this.resolver.deepResolve(root[stylable_value_parsers_1.valueMapping.extends]); if (next && next._kind === 'css') { current = next.meta; symbol = next.meta.mappedSymbols[name]; } else { break; } } if (symbol) { if (symbol._kind === 'class') { node.type = 'class'; node.before = symbol[stylable_value_parsers_1.valueMapping.root] ? '' : ' '; next = this.resolver.deepResolve(symbol); if (symbol[stylable_value_parsers_1.valueMapping.global]) { node.type = 'selector'; node.nodes = symbol[stylable_value_parsers_1.valueMapping.global] || []; this.addGlobalsToMeta(node.nodes, originMeta); } else { if (symbol.alias && !symbol[stylable_value_parsers_1.valueMapping.extends]) { if (next && next.meta && next.symbol) { node.name = this.scope(next.symbol.name, next.meta.namespace); } else { // TODO: maybe warn on un resolved alias } } else { node.name = this.scope(symbol.name, current.namespace); } } if (next && next._kind === 'css') { return next; } } } else if (rule) { if (!native_reserved_lists_1.nativePseudoElements.includes(name) && !is_vendor_prefixed_1.default(name)) { this.diagnostics.warn(rule, exports.transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(name), { word: name, }); } } return { _kind: 'css', meta: current, symbol }; } scope(name, namespace, delimiter = this.delimiter) { return namespace ? namespace + delimiter + name : name; } exportClasses(meta) { const locals = {}; const metaParts = this.resolveMetaParts(meta); for (const [localName, resolved] of Object.entries(metaParts.class)) { const exportedClasses = this.getPartExports(resolved); locals[localName] = exportedClasses.join(' '); } return locals; } /* None alias symbol */ getPartExports(resolved) { const exportedClasses = []; let first = true; for (const { meta, symbol } of resolved) { if (!first && symbol[stylable_value_parsers_1.valueMapping.root]) { break; } first = false; if (symbol.alias && !symbol[stylable_value_parsers_1.valueMapping.extends]) { continue; } exportedClasses.push(this.scope(symbol.name, meta.namespace)); } return exportedClasses; } scopeSelector2(originMeta, selector, _classesExport, _calcPaths = false, rule) { const context = new ScopeContext(originMeta, selector_utils_1.parseSelector(selector), rule || postcss_1.default.rule({ selector })); return { selector: selector_utils_1.stringifySelector(this.scopeSelectorAst(context)), elements: context.elements, }; } scopeSelectorAst(context) { const { originMeta, selectorAst } = context; // split selectors to chunks: .a.b .c:hover, a .c:hover -> [[[.a.b], [.c:hover]], [[.a], [.c:hover]]] const selectorListChunks = selector_utils_1.separateChunks2(selectorAst); // resolve meta classes and elements context.metaParts = this.resolveMetaParts(originMeta); // set stylesheet root as the global anchor if (!context.currentAnchor) { context.initRootAnchor({ name: originMeta.root, type: 'class', resolved: context.metaParts.class[originMeta.root], }); } // loop over selectors for (const selectorChunks of selectorListChunks) { context.elements.push([]); context.selectorIndex++; context.chunks = selectorChunks; // loop over chunks for (const chunk of selectorChunks) { context.chunk = chunk; // loop over each node in a chunk for (const node of chunk.nodes) { context.node = node; // transfrom node this.handleChunkNode(context); } } if (selectorListChunks.length - 1 > context.selectorIndex) { context.initRootAnchor({ name: originMeta.root, type: 'class', resolved: context.metaParts.class[originMeta.root], }); } } const outputAst = selector_utils_1.mergeChunks(selectorListChunks); context.additionalSelectors.forEach((addSelector) => outputAst.nodes.push(addSelector())); return outputAst; } handleChunkNode(context) { const { currentAnchor, metaParts, node, originMeta, transformGlobals, } = context; const { type, name } = node; if (type === 'class') { const resolved = metaParts.class[name] || [ // used to scope classes from js mixins { _kind: 'css', meta: originMeta, symbol: { _kind: 'class', name } }, ]; context.setCurrentAnchor({ name, type: 'class', resolved }); const { symbol, meta } = selector_utils_1.getOriginDefinition(resolved); this.scopeClassNode(symbol, meta, node, originMeta); } else if (type === 'element') { const resolved = metaParts.element[name] || [ // provides resolution for native elements { _kind: 'css', meta: originMeta, symbol: { _kind: 'element', name } }, ]; context.setCurrentAnchor({ name, type: 'element', resolved }); // native node does not resolve e.g. div if (resolved && resolved.length > 1) { const { symbol, meta } = selector_utils_1.getOriginDefinition(resolved); this.scopeClassNode(symbol, meta, node, originMeta); } } else if (type === 'pseudo-element') { const len = currentAnchor.resolved.length; const lookupStartingPoint = len === 1 /* no extends */ ? 0 : 1; let resolved; for (let i = lookupStartingPoint; i < len; i++) { const { symbol, meta } = currentAnchor.resolved[i]; if (!symbol[stylable_value_parsers_1.valueMapping.root]) { // debugger continue; } const customSelector = meta.customSelectors[':--' + name]; if (customSelector) { this.handleCustomSelector(customSelector, meta, context, name, node); return; } const requestedPart = meta.classes[name]; if (symbol.alias || !requestedPart) { // skip alias since thay cannot add parts continue; } resolved = this.resolveMetaParts(meta).class[name]; // first definition of a part in the extends/alias chain context.setCurrentAnchor({ name, type: 'pseudo-element', resolved, }); const resolvedPart = selector_utils_1.getOriginDefinition(resolved); node.before = resolvedPart.symbol[stylable_value_parsers_1.valueMapping.root] ? '' : ' '; this.scopeClassNode(resolvedPart.symbol, resolvedPart.meta, node, originMeta); break; } if (!resolved) { // first definition of a part in the extends/alias chain context.setCurrentAnchor({ name, type: 'pseudo-element', resolved: [], }); if (!native_reserved_lists_1.nativePseudoElements.includes(name) && !is_vendor_prefixed_1.default(name)) { this.diagnostics.warn(context.rule, exports.transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(name), { word: name, }); } } } else if (type === 'pseudo-class') { let found = false; for (const { symbol, meta } of currentAnchor.resolved) { const states = symbol[stylable_value_parsers_1.valueMapping.states]; if (states && hasOwnProperty.call(states, name)) { found = true; pseudo_states_1.setStateToNode(states, meta, name, node, meta.namespace, this.resolver, this.diagnostics, context.rule); break; } } if (!found && !native_reserved_lists_1.nativePseudoClasses.includes(name) && !is_vendor_prefixed_1.default(name)) { this.diagnostics.warn(context.rule, pseudo_states_1.stateErrors.UNKNOWN_STATE_USAGE(name), { word: name, }); } } else if (type === 'nested-pseudo-class') { if (name === 'global') { // :global(.a) -> .a if (transformGlobals) { node.type = 'selector'; } } else { const nestedContext = context.createNestedContext({ type: 'selectors', name: `${name}`, nodes: node.nodes, }); this.scopeSelectorAst(nestedContext); // delegate elements of first selector context.elements[context.selectorIndex].push(...nestedContext.elements[0]); } } else if (type === 'invalid' && node.value === '&') { if ( /* maybe should be currentAnchor meta */originMeta.parent) { const origin = originMeta.mappedSymbols[originMeta.root]; context.setCurrentAnchor({ name: origin.name, type: 'class', resolved: metaParts.class[origin.name], }); } } } handleCustomSelector(customSelector, meta, context, name, node) { const selectorListChunks = selector_utils_1.separateChunks2(selector_utils_1.parseSelector(customSelector)); const hasSingleSelector = selectorListChunks.length === 1; removeFirstRootInEachSelectorChunk(selectorListChunks, meta); const internalContext = new ScopeContext(meta, selector_utils_1.mergeChunks(selectorListChunks), context.rule); const customAstSelectors = this.scopeSelectorAst(internalContext).nodes; customAstSelectors.forEach(trimLeftSelectorAst); if (hasSingleSelector && internalContext.currentAnchor) { context.setCurrentAnchor({ name, type: 'pseudo-element', resolved: internalContext.currentAnchor.resolved, }); } else { // unknown context due to multiple selectors context.setCurrentAnchor({ name, type: 'pseudo-element', resolved: anyElementAnchor(meta).resolved, }); } Object.assign(node, customAstSelectors[0]); // first one handled inline above for (let i = 1; i < customAstSelectors.length; i++) { const selectorNode = context.selectorAst.nodes[context.selectorIndex]; const nodeIndex = selectorNode.nodes.indexOf(node); context.additionalSelectors.push(lazyCreateSelector(customAstSelectors[i], selectorNode, nodeIndex)); } } scopeClassNode(symbol, meta, node, originMeta) { if (symbol[stylable_value_parsers_1.valueMapping.global]) { const globalMappedNodes = symbol[stylable_value_parsers_1.valueMapping.global]; node.type = 'selector'; node.nodes = globalMappedNodes; this.addGlobalsToMeta(globalMappedNodes, originMeta); } else { node.type = 'class'; node.name = this.scope(symbol.name, meta.namespace); } } resolveMetaParts(meta) { let metaParts = this.metaParts.get(meta); if (!metaParts) { const resolvedClasses = {}; for (const className of Object.keys(meta.classes)) { resolvedClasses[className] = this.resolver.resolveExtends(meta, className, false, undefined, (res, extend) => { const decl = stylable_utils_1.findRule(meta.ast, '.' + className); if (decl) { if (res && res._kind === 'js') { this.diagnostics.error(decl, exports.transformerWarnings.CANNOT_EXTEND_JS(), { word: decl.value, }); } else if (res && !res.symbol) { this.diagnostics.error(decl, exports.transformerWarnings.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), { word: decl.value }); } else { this.diagnostics.error(decl, exports.transformerWarnings.IMPORT_ISNT_EXTENDABLE(), { word: decl.value }); } } else { if (meta.classes[className] && meta.classes[className].alias) { meta.ast.walkRules(new RegExp('\\.' + className), (rule) => { this.diagnostics.error(rule, exports.transformerWarnings.UNKNOWN_IMPORT_ALIAS(className), { word: className }); return false; }); } } }); } const resolvedElements = {}; for (const k of Object.keys(meta.elements)) { resolvedElements[k] = this.resolver.resolveExtends(meta, k, true); } metaParts = { class: resolvedClasses, element: resolvedElements }; this.metaParts.set(meta, metaParts); } return metaParts; } addDevRules(meta) { const metaParts = this.resolveMetaParts(meta); for (const [className, resolved] of Object.entries(metaParts.class)) { if (resolved.length > 1) { meta.outputAst.walkRules('.' + this.scope(className, meta.namespace), (rule) => { const a = resolved[0]; const b = resolved[1]; rule.after(selector_utils_1.createWarningRule(b.symbol.name, this.scope(b.symbol.name, b.meta.namespace), path_1.basename(b.meta.source), a.symbol.name, this.scope(a.symbol.name, a.meta.namespace), path_1.basename(a.meta.source), true)); }); } } } resetTransformProperties(meta) { meta.globals = {}; return (meta.outputAst = meta.ast.clone()); } } exports.StylableTransformer = StylableTransformer; function removeSTDirective(root) { const toRemove = []; root.walkRules((rule) => { if (rule.nodes && rule.nodes.length === 0) { toRemove.push(rule); return; } rule.walkDecls((decl) => { if (decl.prop.startsWith('-st-')) { toRemove.push(decl); } }); if (rule.raws) { rule.raws = { after: '\n', }; } }); if (root.raws) { root.raws = {}; } function removeRecursiveUpIfEmpty(node) { const parent = node.parent; node.remove(); if (parent && parent.nodes && parent.nodes.length === 0) { removeRecursiveUpIfEmpty(parent); } } toRemove.forEach((node) => { removeRecursiveUpIfEmpty(node); }); } exports.removeSTDirective = removeSTDirective; function validateScopes(meta, resolver, diagnostics) { for (const scope of meta.scopes) { const name = scope.params.startsWith('.') ? scope.params.slice(1) : scope.params; if (!name) { continue; } else if (!meta.mappedSymbols[name]) { diagnostics.error(scope, exports.transformerWarnings.UNKNOWN_SCOPING_PARAM(scope.params), { word: scope.params, }); continue; } const resolvedScope = resolver.deepResolve(meta.mappedSymbols[name]); if (resolvedScope && resolvedScope._kind === 'css') { const { meta: scopingMeta, symbol: scopingSymbol } = resolvedScope; if (scopingSymbol.name !== scopingMeta.root) { diagnostics.error(scope, exports.transformerWarnings.SCOPE_PARAM_NOT_ROOT(scope.params), { word: scope.params, }); } } else if (resolvedScope && resolvedScope._kind === 'js') { diagnostics.error(scope, exports.transformerWarnings.SCOPE_PARAM_NOT_CSS(scope.params), { word: scope.params, }); } else if (meta.classes[name] || (meta.elements[scope.params] && meta.elements[scope.params].alias)) { // do nothing valid input } else { diagnostics.error(scope, exports.transformerWarnings.UNKNOWN_SCOPING_PARAM(scope.params), { word: scope.params, }); } } } function removeFirstRootInEachSelectorChunk(selectorListChunks, meta) { selectorListChunks.forEach((selectorChunks) => { selectorChunks[0].nodes = selectorChunks[0].nodes.filter(({ type, name }) => { return !(type === 'class' && name === meta.root); }); }); } function trimLeftSelectorAst(n, i = 0) { if (n) { if (n.type === 'spacing') { n.value = ''; } n.before = ''; trimLeftSelectorAst(n.nodes && n.nodes[0], i + 1); if (i === 0) { n.before = ' '; } } } function anyElementAnchor(meta) { return { type: 'element', name: '*', resolved: [{ _kind: 'css', meta, symbol: { _kind: 'element', name: '*' } }], }; } function lazyCreateSelector(customElementChunk, selectorNode, nodeIndex) { if (nodeIndex === -1) { throw new Error('not supported inside nested classes'); } return () => { const clone = lodash_clonedeep_1.default(selectorNode); clone.nodes[nodeIndex].nodes = customElementChunk.nodes; return clone; }; } class ScopeContext { constructor(originMeta, selectorAst, rule) { this.additionalSelectors = []; this.selectorIndex = -1; this.elements = []; this.transformGlobals = false; this.originMeta = originMeta; this.selectorAst = selectorAst; this.rule = rule; } initRootAnchor(anchor) { this.currentAnchor = anchor; } setCurrentAnchor(anchor) { if (this.selectorIndex !== undefined && this.selectorIndex !== -1) { this.elements[this.selectorIndex].push(anchor); } this.currentAnchor = anchor; } createNestedContext(selectorAst) { const ctx = new ScopeContext(this.originMeta, selectorAst, this.rule); Object.assign(ctx, this); ctx.selectorAst = selectorAst; ctx.selectorIndex = -1; ctx.elements = []; ctx.additionalSelectors = []; return ctx; } } //# sourceMappingURL=stylable-transformer.js.map