UNPKG

@stylable/core

Version:

CSS for Components

406 lines 18.4 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.StylablePublicApi = exports.hooks = exports.diagnostics = exports.MixinType = void 0; const feature_1 = require("./feature"); const STSymbol = __importStar(require("./st-symbol")); const STCustomSelector = __importStar(require("./st-custom-selector")); const STVar = __importStar(require("./st-var")); const rule_1 = require("../helpers/rule"); const selector_1 = require("../helpers/selector"); const mixin_1 = require("../helpers/mixin"); const functions_1 = require("../functions"); const parser_1 = require("../parser"); const postcss = __importStar(require("postcss")); const postcss_value_parser_1 = __importDefault(require("postcss-value-parser")); const stylable_assets_1 = require("../stylable-assets"); const stylable_utils_1 = require("../stylable-utils"); const path_1 = require("path"); const diagnostics_1 = require("../diagnostics"); const css_selector_parser_1 = require("@tokey/css-selector-parser"); exports.MixinType = { ALL: `-st-mixin`, PARTIAL: `-st-partial-mixin`, }; exports.diagnostics = { VALUE_CANNOT_BE_STRING: mixin_1.mixinHelperDiagnostics.VALUE_CANNOT_BE_STRING, INVALID_NAMED_PARAMS: mixin_1.mixinHelperDiagnostics.INVALID_NAMED_PARAMS, INVALID_MERGE_OF: stylable_utils_1.utilDiagnostics.INVALID_MERGE_OF, INVALID_RECURSIVE_MIXIN: stylable_utils_1.utilDiagnostics.INVALID_RECURSIVE_MIXIN, PARTIAL_MIXIN_MISSING_ARGUMENTS: (0, diagnostics_1.createDiagnosticReporter)('10001', 'error', (type) => `"${exports.MixinType.PARTIAL}" can only be used with override arguments provided, missing overrides on "${type}"`), UNKNOWN_MIXIN: (0, diagnostics_1.createDiagnosticReporter)('10002', 'error', (name) => `unknown mixin: "${name}"`), OVERRIDE_MIXIN: (0, diagnostics_1.createDiagnosticReporter)('10003', 'warning', (mixinType) => `override ${mixinType} on same rule`), FAILED_TO_APPLY_MIXIN: (0, diagnostics_1.createDiagnosticReporter)('10004', 'error', (error) => `could not apply mixin: ${error}`), JS_MIXIN_NOT_A_FUNC: (0, diagnostics_1.createDiagnosticReporter)('10005', 'error', () => `js mixin must be a function`), UNSUPPORTED_MIXIN_SYMBOL: (0, diagnostics_1.createDiagnosticReporter)('10007', 'error', (name, symbolType) => `cannot mix unsupported symbol "${name}" of type "${STSymbol.readableTypeMap[symbolType]}"`), CIRCULAR_MIXIN: (0, diagnostics_1.createDiagnosticReporter)('10006', 'error', (circularPaths) => `circular mixin found: ${circularPaths.join(' --> ')}`), UNKNOWN_ARG: (0, diagnostics_1.createDiagnosticReporter)('10009', 'warning', (argName) => `unknown mixin argument "${argName}"`), }; // HOOKS exports.hooks = (0, feature_1.createFeature)({ transformSelectorNode({ selectorContext, node }) { const isMarker = (0, rule_1.isStMixinMarker)(node); if (isMarker) { selectorContext.setNextSelectorScope(selectorContext.inferredSelectorMixin, node, node.value); } return isMarker; }, transformLastPass({ context, ast, transformer, path }) { ast.walkRules((rule) => appendMixins(context, transformer, rule, path)); }, }); // API class StylablePublicApi { constructor(stylable) { this.stylable = stylable; } resolveExpr(meta, expr, { diagnostics = new diagnostics_1.Diagnostics(), resolveOptionalArgs = false, } = {}) { const resolvedSymbols = this.stylable.resolver.resolveSymbols(meta, diagnostics); const { mainNamespace } = resolvedSymbols; const analyzedMixins = collectDeclMixins({ meta, diagnostics }, resolvedSymbols, postcss.decl({ prop: '-st-mixin', value: expr }), (mixinSymbolName) => (mainNamespace[mixinSymbolName] === 'js' ? 'args' : 'named')); const result = []; for (const { data } of analyzedMixins) { const name = data.type; const symbolKind = mainNamespace[name]; if (symbolKind === 'class' || symbolKind === 'element') { const mixRef = { name, kind: 'css-fragment', args: [], optionalArgs: new Map(), }; for (const [argName, argValue] of Object.entries(data.options)) { mixRef.args.push({ [argName]: argValue }); } if (resolveOptionalArgs) { const varMap = new Map(); const resolveChain = resolvedSymbols[symbolKind][name]; getCSSMixinRoots(meta, resolveChain, ({ mixinRoot }) => { const names = new Set(); collectOptionalArgs({ meta, resolver: this.stylable.resolver }, mixinRoot, names); names.forEach((name) => varMap.set(name, { name })); }); mixRef.optionalArgs = varMap; } result.push(mixRef); } else if (symbolKind === 'js' && typeof resolvedSymbols.js[name].symbol === 'function') { const mixRef = { name, kind: 'js-func', args: [], func: resolvedSymbols.js[name].symbol, }; for (const arg of Object.values(data.options)) { mixRef.args.push(arg.value); } result.push(mixRef); } else { result.push({ name, kind: 'invalid', args: data.valueNode?.type === 'function' ? postcss_value_parser_1.default.stringify(data.valueNode.nodes) : '', }); } } return result; } scopeNestedSelector(scopeSelector, nestSelector) { return (0, selector_1.scopeNestedSelector)((0, css_selector_parser_1.parseCssSelector)(scopeSelector), (0, css_selector_parser_1.parseCssSelector)(nestSelector)) .selector; } } exports.StylablePublicApi = StylablePublicApi; function appendMixins(context, transformer, rule, path = []) { const [decls, mixins] = collectRuleMixins(context, rule); if (!mixins || mixins.length === 0) { return; } for (const mixin of mixins) { if (mixin.valid) { appendMixin(context, { transformer, mixDef: mixin, rule, path }); } } for (const mixinDecl of decls) { mixinDecl.remove(); } } function collectRuleMixins(context, rule) { let mixins = []; const resolvedSymbols = context.getResolvedSymbols(context.meta); const { mainNamespace } = resolvedSymbols; const decls = []; for (const node of rule.nodes) { if (node.type === 'decl' && (node.prop === `-st-mixin` || node.prop === `-st-partial-mixin`)) { decls.push(node); mixins = collectDeclMixins(context, resolvedSymbols, node, (mixinSymbolName) => { return mainNamespace[mixinSymbolName] === 'js' ? 'args' : 'named'; }, mixins); } } return [decls, mixins]; } function collectDeclMixins(context, resolvedSymbols, decl, paramSignature, previousMixins) { const { meta } = context; let mixins = []; const parser = decl.prop === exports.MixinType.ALL ? mixin_1.parseStMixin : decl.prop === exports.MixinType.PARTIAL ? mixin_1.parseStPartialMixin : null; if (!parser) { return previousMixins || mixins; } parser(decl, paramSignature, context.diagnostics, /*emitStrategyDiagnostics*/ true).forEach((mixin) => { const mixinRefSymbol = STSymbol.get(meta, mixin.type); const symbolName = mixin.type; const resolvedType = resolvedSymbols.mainNamespace[symbolName]; if (resolvedType && ((resolvedType === 'js' && typeof resolvedSymbols.js[symbolName].symbol === 'function') || resolvedType === 'class' || resolvedType === 'element')) { mixins.push({ valid: true, data: mixin, symbol: mixinRefSymbol, }); if (mixin.partial && Object.keys(mixin.options).length === 0) { context.diagnostics.report(exports.diagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS(mixin.type), { node: decl, word: mixin.type, }); } } else { mixins.push({ valid: false, data: mixin, symbol: mixinRefSymbol, }); if (resolvedType === 'js') { context.diagnostics.report(exports.diagnostics.JS_MIXIN_NOT_A_FUNC(), { node: decl, word: mixin.type, }); } else if (resolvedType) { context.diagnostics.report(exports.diagnostics.UNSUPPORTED_MIXIN_SYMBOL(mixin.type, resolvedType), { node: decl, word: mixin.type, }); } else { context.diagnostics.report(exports.diagnostics.UNKNOWN_MIXIN(mixin.type), { node: decl, word: mixin.type, }); } } }); if (previousMixins) { const partials = previousMixins.filter((r) => r.data.partial); const nonPartials = previousMixins.filter((r) => !r.data.partial); const isInPartial = decl.prop === exports.MixinType.PARTIAL; if ((partials.length && decl.prop === exports.MixinType.PARTIAL) || (nonPartials.length && decl.prop === exports.MixinType.ALL)) { context.diagnostics.report(exports.diagnostics.OVERRIDE_MIXIN(decl.prop), { node: decl }); } if (partials.length && nonPartials.length) { mixins = isInPartial ? nonPartials.concat(mixins) : partials.concat(mixins); } else if (partials.length) { mixins = isInPartial ? mixins : partials.concat(mixins); } else if (nonPartials.length) { mixins = isInPartial ? nonPartials.concat(mixins) : mixins; } } return mixins; } function appendMixin(context, config) { if (checkRecursive(context, config)) { return; } const resolvedSymbols = context.getResolvedSymbols(context.meta); const symbolName = config.mixDef.data.type; const resolvedType = resolvedSymbols.mainNamespace[symbolName]; if (resolvedType === `class` || resolvedType === `element`) { const resolveChain = resolvedSymbols[resolvedType][symbolName]; handleCSSMixin(context, config, resolveChain); return; } else if (resolvedType === `js`) { const jsValue = resolvedSymbols.js[symbolName].symbol; if (typeof jsValue === 'function') { try { handleJSMixin(context, config, jsValue); } catch (e) { context.diagnostics.report(exports.diagnostics.FAILED_TO_APPLY_MIXIN(String(e)), { node: config.rule, word: config.mixDef.data.type, }); return; } } return; } } function checkRecursive({ meta, diagnostics: report }, { mixDef, path, rule }) { const symbolName = mixDef.symbol.name === meta.root ? mixDef.symbol._kind === 'class' ? meta.root : 'default' : mixDef.data.type; const isRecursive = path.includes(symbolName + ' from ' + meta.source); if (isRecursive) { // Todo: add test verifying word report.report(exports.diagnostics.CIRCULAR_MIXIN(path), { node: rule, word: symbolName, }); return true; } return false; } function handleJSMixin(context, config, mixinFunction) { const stVarOverride = context.evaluator.stVarOverride || {}; const meta = context.meta; const mixDef = config.mixDef; const res = mixinFunction(mixDef.data.options.map((v) => v.value)); const mixinRoot = (0, parser_1.cssObjectToAst)(res); mixinRoot.walkDecls((decl) => { if (!(0, stylable_utils_1.isValidDeclaration)(decl)) { decl.value = String(decl); } }); config.transformer.transformAst(mixinRoot, meta, undefined, stVarOverride, [], true); const mixinPath = mixDef.symbol.import.request; (0, stylable_assets_1.fixRelativeUrls)(mixinRoot, context.resolver.resolvePath((0, path_1.dirname)(meta.source), mixinPath), meta.source); (0, stylable_utils_1.mergeRules)(mixinRoot, config.rule, mixDef.data.originDecl, context.diagnostics, true); } function handleCSSMixin(context, config, resolveChain) { const mixDef = config.mixDef; const isPartial = mixDef.data.partial; const namedArgs = mixDef.data.options; const overrideKeys = Object.keys(namedArgs); if (isPartial && overrideKeys.length === 0) { return; } const optionalArgs = new Set(); const roots = getCSSMixinRoots(context.meta, resolveChain, ({ mixinRoot, resolved, isRootMixin }) => { const stVarOverride = context.evaluator.stVarOverride || {}; const mixDef = config.mixDef; const namedArgs = mixDef.data.options; if (mixDef.data.partial) { filterPartialMixinDecl(context.meta, mixinRoot, Object.keys(namedArgs)); } // resolve override args const resolvedArgs = (0, functions_1.resolveArgumentsValue)(namedArgs, config.transformer, context.meta, context.diagnostics, mixDef.data.originDecl, stVarOverride, config.path); collectOptionalArgs({ meta: resolved.meta, resolver: context.resolver }, mixinRoot, optionalArgs); // transform mixin const mixinMeta = resolved.meta; const symbolName = isRootMixin && resolved.meta !== context.meta ? 'default' : mixDef.data.type; config.transformer.transformAst(mixinRoot, mixinMeta, undefined, resolvedArgs, config.path.concat(symbolName + ' from ' + context.meta.source), true, config.transformer.createInferredSelector(mixinMeta, { name: resolved.symbol.name, type: resolved.symbol._kind, })); (0, stylable_assets_1.fixRelativeUrls)(mixinRoot, resolved.meta.source, context.meta.source); }); for (const overrideArg of overrideKeys) { if (!optionalArgs.has(overrideArg)) { context.diagnostics.report(exports.diagnostics.UNKNOWN_ARG(overrideArg), { node: mixDef.data.originDecl, word: overrideArg, }); } } if (roots.length === 1) { (0, stylable_utils_1.mergeRules)(roots[0], config.rule, mixDef.data.originDecl, config.transformer.diagnostics, false); } else if (roots.length > 1) { const mixinRoot = postcss.root(); roots.forEach((root) => mixinRoot.prepend(...root.nodes)); (0, stylable_utils_1.mergeRules)(mixinRoot, config.rule, mixDef.data.originDecl, config.transformer.diagnostics, false); } } function collectOptionalArgs(context, mixinRoot, optionalArgs = new Set()) { mixinRoot.walk((node) => { const value = node.type === 'decl' ? node.value : node.type === 'atrule' ? node.params : ''; const varNames = STVar.parseVarsFromExpr(value); for (const name of varNames) { for (const refName of STVar.resolveReferencedVarNames(context, name)) { optionalArgs.add(refName); } } }); } function getCSSMixinRoots(contextMeta, resolveChain, processMixinRoot) { const roots = []; for (const resolved of resolveChain) { const isRootMixin = resolved.symbol.name === resolved.meta.root; const mixinRoot = (0, rule_1.createSubsetAst)(resolved.meta.sourceAst, (resolved.symbol._kind === 'class' ? '.' : '') + resolved.symbol.name, undefined, isRootMixin, (name) => STCustomSelector.getCustomSelector(contextMeta, name)); processMixinRoot({ mixinRoot, resolved, isRootMixin }); roots.push(mixinRoot); if (resolved.symbol[`-st-extends`]) { break; } } return roots; } /** we assume that mixinRoot is freshly created nodes from the ast */ function filterPartialMixinDecl(meta, mixinRoot, overrideKeys) { let regexp; const overrideSet = new Set(overrideKeys); let size; do { size = overrideSet.size; regexp = new RegExp(`value\\((\\s*${Array.from(overrideSet).join('\\s*)|(\\s*')}\\s*)\\)`); for (const { text, name } of Object.values(meta.getAllStVars())) { if (!overrideSet.has(name) && text.match(regexp)) { overrideSet.add(name); } } } while (overrideSet.size !== size); mixinRoot.walkDecls((decl) => { if (!decl.value.match(regexp)) { const parent = decl.parent; decl.remove(); if (parent?.nodes?.length === 0) { parent.remove(); } } }); } //# sourceMappingURL=st-mixin.js.map