UNPKG

@stylable/core

Version:

CSS for Components

414 lines 17.8 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.resolveReferencedVarNames = exports.parseVarsFromExpr = exports.StylablePublicApi = exports.get = exports.hooks = exports.diagnostics = void 0; const feature_1 = require("./feature"); const custom_values_1 = require("../custom-values"); const diagnostics_1 = require("./diagnostics"); const STSymbol = __importStar(require("./st-symbol")); const stylable_resolver_1 = require("../stylable-resolver"); const functions_1 = require("../functions"); const rule_1 = require("../helpers/rule"); const selector_1 = require("../helpers/selector"); const value_1 = require("../helpers/value"); const string_1 = require("../helpers/string"); const process_declaration_functions_1 = require("../process-declaration-functions"); const diagnostics_2 = require("../diagnostics"); const postcss_value_parser_1 = __importDefault(require("postcss-value-parser")); exports.diagnostics = { FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: diagnostics_1.generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR, NO_VARS_DEF_IN_ST_SCOPE: (0, diagnostics_2.createDiagnosticReporter)('07002', 'error', () => `cannot define ":vars" inside of "@st-scope"`), DEPRECATED_ST_FUNCTION_NAME: (0, diagnostics_2.createDiagnosticReporter)('07003', 'info', (name, alternativeName) => `"${name}" is deprecated, use "${alternativeName}"`), CYCLIC_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07004', 'error', (cyclicChain) => `Cyclic value definition detected: "${cyclicChain .map((s, i) => (i === cyclicChain.length - 1 ? '↻ ' : i === 0 ? '→ ' : '↪ ') + s) .join('\n')}"`), MISSING_VAR_IN_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07005', 'error', () => `invalid value() with no var identifier`), COULD_NOT_RESOLVE_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07006', 'error', (args) => `cannot resolve value function${args ? ` using the arguments provided: "${args}"` : ''}`), MULTI_ARGS_IN_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07007', 'error', (args) => `value function accepts only a single argument: "value(${args})"`), CANNOT_USE_AS_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07008', 'error', (type, varName) => `${type} "${varName}" cannot be used as a variable`), CANNOT_USE_JS_AS_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07009', 'error', (type, varName) => `JavaScript ${type} import "${varName}" cannot be used as a variable`), UNKNOWN_VAR: (0, diagnostics_2.createDiagnosticReporter)('07010', 'error', (name) => `unknown var "${name}"`), }; // HOOKS exports.hooks = (0, feature_1.createFeature)({ analyzeSelectorNode({ context, node, rule }) { if (node.type !== `pseudo_class` || node.value !== `vars`) { return; } // make sure `:vars` is the only selector if (rule.selector === `:vars`) { if ((0, rule_1.isChildOfAtRule)(rule, `st-scope`)) { context.diagnostics.report(exports.diagnostics.NO_VARS_DEF_IN_ST_SCOPE(), { node: rule }); } else { collectVarSymbols(context, rule); } // stop further walk into `:vars {}` return selector_1.walkSelector.stopAll; } else { context.diagnostics.report(exports.diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(`:vars`), { node: rule, }); } return; }, prepareAST({ node, toRemove }) { if (node.type === 'rule' && node.selector === ':vars') { toRemove.push(node); } }, transformResolve({ context }) { // Resolve local vars const resolved = {}; const symbols = STSymbol.getAllByType(context.meta, `var`); // Temporarily don't report issues here // ToDo: move reporting here (from value() transformation) const noDaigContext = { ...context, diagnostics: new diagnostics_2.Diagnostics(), }; for (const name of Object.keys(symbols)) { const symbol = symbols[name]; const evaluated = context.evaluator.evaluateValue(noDaigContext, { // ToDo: change to `value(${name})` in order to fix overrides in exports value: (0, string_1.stripQuotation)(symbol.text), meta: context.meta, node: symbol.node, }); resolved[name] = evaluated; } return resolved; }, transformValue({ context, node, data }) { evaluateValueCall(context, node, data); }, transformJSExports({ exports, resolved }) { for (const [name, { topLevelType, outputValue }] of Object.entries(resolved)) { exports.stVars[name] = topLevelType ? (0, custom_values_1.unbox)(topLevelType) : outputValue; } }, }); // API function get(meta, name) { return STSymbol.get(meta, name, `var`); } exports.get = get; // Stylable StVar Public APIs const UNKNOWN_LOCATION = Object.freeze({ offset: -1, line: -1, column: -1, }); class StylablePublicApi { constructor(stylable) { this.stylable = stylable; } getComputed(meta) { const topLevelDiagnostics = new diagnostics_2.Diagnostics(); const getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.stylable.resolver, topLevelDiagnostics); const evaluator = new functions_1.StylableEvaluator({ getResolvedSymbols }); const { var: stVars } = getResolvedSymbols(meta); const computed = {}; for (const [localName, resolvedVar] of Object.entries(stVars)) { const diagnostics = new diagnostics_2.Diagnostics(); const { outputValue, topLevelType, runtimeValue } = evaluator.evaluateValue({ resolver: this.stylable.resolver, evaluator, meta, diagnostics, }, { meta: resolvedVar.meta, value: (0, string_1.stripQuotation)(resolvedVar.symbol.text), node: resolvedVar.symbol.node, }); const computedStVar = { value: runtimeValue ?? outputValue, input: topLevelType ?? (0, custom_values_1.unbox)(outputValue, false), diagnostics, source: { meta: resolvedVar.meta, start: resolvedVar.symbol.node.source?.start || UNKNOWN_LOCATION, end: resolvedVar.symbol.node.source?.end || UNKNOWN_LOCATION, }, }; computed[localName] = computedStVar; } return computed; } flatten(meta) { const computed = this.getComputed(meta); const flatStVars = []; for (const [symbol, stVar] of Object.entries(computed)) { flatStVars.push(...this.flatSingle(stVar.input, [symbol], stVar.source)); } return flatStVars; } flatSingle(input, path, source) { const currentVars = []; if (input.flatValue) { currentVars.push({ value: input.flatValue, path, source, }); } if (typeof input.value === `object` && input.value !== null) { for (const [key, innerInput] of Object.entries(input.value)) { currentVars.push(...this.flatSingle(typeof innerInput === 'string' ? (0, custom_values_1.boxString)(innerInput) : innerInput, [...path, key], source)); } } return currentVars; } } exports.StylablePublicApi = StylablePublicApi; function parseVarsFromExpr(expr) { const nameSet = new Set(); (0, postcss_value_parser_1.default)(expr).walk((node) => { if (node.type === 'function' && node.value === 'value') { for (const argNode of node.nodes) { switch (argNode.type) { case 'word': nameSet.add(argNode.value); return; case 'div': if (argNode.value === ',') { return; } } } } }); return nameSet; } exports.parseVarsFromExpr = parseVarsFromExpr; function collectVarSymbols(context, rule) { rule.walkDecls((decl) => { warnOnDeprecatedCustomValues(context, decl); // check type annotation let type = null; const prev = decl.prev(); if (prev && prev.type === 'comment') { const typeMatch = prev.text.match(/^@type (.+)$/); if (typeMatch) { type = typeMatch[1]; } } // add symbol const name = decl.prop; STSymbol.addSymbol({ context, symbol: { _kind: 'var', name, value: '', text: decl.value, node: decl, valueType: type, }, node: decl, }); }); } function warnOnDeprecatedCustomValues(context, decl) { (0, process_declaration_functions_1.processDeclarationFunctions)(decl, (node) => { if (node.type === 'nested-item' && custom_values_1.deprecatedStFunctions[node.name]) { const { alternativeName } = custom_values_1.deprecatedStFunctions[node.name]; context.diagnostics.report(exports.diagnostics.DEPRECATED_ST_FUNCTION_NAME(node.name, alternativeName), { node: decl, word: node.name, }); } }, false); } function evaluateValueCall(context, parsedNode, data) { const { stVarOverride, value, node } = data; const passedThrough = context.passedThrough || []; const parsedArgs = value_1.strategies.args(parsedNode).map((x) => x.value); const varName = parsedArgs[0]; const restArgs = parsedArgs.slice(1); // check var not empty if (!varName) { if (node) { context.diagnostics.report(exports.diagnostics.MISSING_VAR_IN_VALUE(), { node, word: (0, value_1.getStringValue)(parsedNode), }); } } else if (parsedArgs.length >= 1) { // override with value if (stVarOverride?.[varName]) { parsedNode.resolvedValue = stVarOverride?.[varName]; return; } // check cyclic const refUniqID = createUniqID(data.meta.source, varName); if (passedThrough.includes(refUniqID)) { // TODO: move diagnostic to original value usage instead of the end of the cyclic chain handleCyclicValues(context, passedThrough, refUniqID, data.node, value, parsedNode); return; } // resolve const resolvedSymbols = context.getResolvedSymbols(data.meta); const resolvedVar = resolvedSymbols.var[varName]; const resolvedVarSymbol = resolvedVar?.symbol; const possibleNonSTVarSymbol = STSymbol.get(context.meta, varName); if (resolvedVarSymbol) { const { outputValue, topLevelType, typeError } = context.evaluator.evaluateValue({ ...context, passedThrough: passedThrough.concat(refUniqID) }, { ...data, value: (0, string_1.stripQuotation)(resolvedVarSymbol.text), args: restArgs, node: resolvedVarSymbol.node, meta: resolvedVar.meta, rootArgument: varName, initialNode: node, }); // report errors if (node) { const argsAsString = parsedArgs.join(', '); if (!typeError && !topLevelType && parsedArgs.length > 1) { context.diagnostics.report(exports.diagnostics.MULTI_ARGS_IN_VALUE(argsAsString), { node, }); } } parsedNode.resolvedValue = context.evaluator.valueHook ? context.evaluator.valueHook(outputValue, varName, true, passedThrough) : outputValue; } else if (possibleNonSTVarSymbol) { const type = resolvedSymbols.mainNamespace[varName]; if (type === `js`) { const deepResolve = resolvedSymbols.js[varName]; const jsValue = deepResolve.symbol; if (typeof jsValue === 'string') { parsedNode.resolvedValue = context.evaluator.valueHook ? context.evaluator.valueHook(jsValue, varName, false, passedThrough) : jsValue; } else if (node) { // unsupported Javascript value // ToDo: provide actual exported id (default/named as x) context.diagnostics.report(exports.diagnostics.CANNOT_USE_JS_AS_VALUE(typeof jsValue, varName), { node, word: varName, }); } } else if (type) { // report mismatch type const deepResolve = resolvedSymbols[type][varName]; let finalResolve = { _kind: `css`, meta: data.meta, symbol: possibleNonSTVarSymbol, }; if (deepResolve instanceof Array) { // take the deep resolved in order to // print the actual mismatched type finalResolve = deepResolve[deepResolve.length - 1]; } else if (deepResolve._kind === `css`) { finalResolve = deepResolve; } reportUnsupportedSymbolInValue(context, varName, finalResolve, node); } else if (node) { // report unknown var context.diagnostics.report(exports.diagnostics.UNKNOWN_VAR(varName), { node, word: varName, }); } } else if (node) { context.diagnostics.report(exports.diagnostics.UNKNOWN_VAR(varName), { node, word: varName, }); } } } function resolveReferencedVarNames(context, initialName) { const refNames = new Set(); const varsToCheck = [ { meta: context.meta, name: initialName }, ]; const checked = new Set(); while (varsToCheck.length) { const { meta, name } = varsToCheck.shift(); const contextualId = meta.source + '/' + name; if (!checked.has(contextualId)) { checked.add(contextualId); refNames.add(name); const symbol = STSymbol.get(meta, name); switch (symbol?._kind) { case 'var': parseVarsFromExpr(symbol.text).forEach((refName) => varsToCheck.push({ meta, name: refName, })); break; case 'import': { const resolved = context.resolver.deepResolve(symbol); if (resolved?._kind === 'css' && resolved.symbol?._kind === 'var') { varsToCheck.push({ meta: resolved.meta, name: resolved.symbol.name }); } break; } } } } return refNames; } exports.resolveReferencedVarNames = resolveReferencedVarNames; function reportUnsupportedSymbolInValue(context, name, resolve, node) { const symbol = resolve.symbol; const errorKind = symbol._kind === 'class' && symbol[` -st-root`] ? 'stylesheet' : symbol._kind; if (node) { context.diagnostics.report(exports.diagnostics.CANNOT_USE_AS_VALUE(errorKind, name), { node, word: name, }); } } function handleCyclicValues(context, passedThrough, refUniqID, node, value, parsedNode) { if (node) { const cyclicChain = passedThrough.map((variable) => variable || ''); cyclicChain.push(refUniqID); context.diagnostics.report(exports.diagnostics.CYCLIC_VALUE(cyclicChain), { node, word: refUniqID, // ToDo: check word is path+var and not var name }); } return (0, value_1.stringifyFunction)(value, parsedNode); } function createUniqID(source, varName) { return `${source}: ${varName}`; } //# sourceMappingURL=st-var.js.map