@stylable/core
Version:
CSS for Components
1,046 lines • 46.2 kB
JavaScript
"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