@stylable/core
Advanced tools
Comparing version 5.0.0-rc.2 to 5.0.0-rc.3
@@ -12,3 +12,3 @@ export type { FeatureContext, FeatureTransformContext } from './feature'; | ||
export * as STMixin from './st-mixin'; | ||
export type { RefedMixin, MixinValue } from './st-mixin'; | ||
export type { MixinReflection, MixinValue } from './st-mixin'; | ||
export * as CSSClass from './css-class'; | ||
@@ -15,0 +15,0 @@ export type { ClassSymbol } from './css-class'; |
@@ -5,2 +5,3 @@ import { FeatureContext } from './feature'; | ||
import type * as postcss from 'postcss'; | ||
import type { Stylable } from '../stylable'; | ||
export interface ImportSymbol { | ||
@@ -13,2 +14,10 @@ _kind: 'import'; | ||
} | ||
export interface AnalyzedImport { | ||
from: string; | ||
default: string; | ||
named: Record<string, string>; | ||
typed: { | ||
keyframes: Record<string, string>; | ||
}; | ||
} | ||
export interface Imported { | ||
@@ -124,4 +133,9 @@ from: string; | ||
}>; | ||
export declare class StylablePublicApi { | ||
private stylable; | ||
constructor(stylable: Stylable); | ||
analyze(meta: StylableMeta): AnalyzedImport[]; | ||
} | ||
export declare function getImportStatements({ data }: StylableMeta): ReadonlyArray<Imported>; | ||
export declare function createImportSymbol(importDef: Imported, type: `default` | `named`, name: string, dirContext: string): ImportSymbol; | ||
//# sourceMappingURL=st-import.d.ts.map |
@@ -29,3 +29,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createImportSymbol = exports.getImportStatements = exports.hooks = exports.diagnostics = exports.ImportTypeHook = exports.PseudoImportDecl = exports.PseudoImport = void 0; | ||
exports.createImportSymbol = exports.getImportStatements = exports.StylablePublicApi = exports.hooks = exports.diagnostics = exports.ImportTypeHook = exports.PseudoImportDecl = exports.PseudoImport = void 0; | ||
const feature_1 = require("./feature"); | ||
@@ -120,2 +120,18 @@ const diagnostics_1 = require("./diagnostics"); | ||
// API | ||
class StylablePublicApi { | ||
constructor(stylable) { | ||
this.stylable = stylable; | ||
} | ||
analyze(meta) { | ||
return getImportStatements(meta).map(({ request, defaultExport, named, keyframes }) => ({ | ||
from: request, | ||
default: defaultExport, | ||
named, | ||
typed: { | ||
keyframes, | ||
}, | ||
})); | ||
} | ||
} | ||
exports.StylablePublicApi = StylablePublicApi; | ||
function isImportStatement(node) { | ||
@@ -122,0 +138,0 @@ return ((node.type === `atrule` && node.name === `st-import`) || |
@@ -1,2 +0,2 @@ | ||
import { FeatureTransformContext } from './feature'; | ||
import * as STSymbol from './st-symbol'; | ||
import type { ImportSymbol } from './st-import'; | ||
@@ -6,4 +6,6 @@ import type { ElementSymbol } from './css-type'; | ||
import * as postcss from 'postcss'; | ||
import type { FunctionNode, WordNode } from 'postcss-value-parser'; | ||
import type { StylableTransformer } from '../stylable-transformer'; | ||
import { FunctionNode, WordNode } from 'postcss-value-parser'; | ||
import type { StylableMeta } from '../stylable-meta'; | ||
import { Diagnostics } from '../diagnostics'; | ||
import type { Stylable } from '../stylable'; | ||
export interface MixinValue { | ||
@@ -18,6 +20,29 @@ type: string; | ||
} | ||
export interface RefedMixin { | ||
mixin: MixinValue; | ||
ref: ImportSymbol | ClassSymbol | ElementSymbol; | ||
} | ||
export declare type ValidMixinSymbols = ImportSymbol | ClassSymbol | ElementSymbol; | ||
export declare type AnalyzedMixin = { | ||
valid: true; | ||
data: MixinValue; | ||
symbol: ValidMixinSymbols; | ||
} | { | ||
valid: false; | ||
data: MixinValue; | ||
symbol: Exclude<STSymbol.StylableSymbol, ValidMixinSymbols> | undefined; | ||
}; | ||
export declare type MixinReflection = { | ||
name: string; | ||
kind: 'css-fragment'; | ||
args: Record<string, string>[]; | ||
optionalArgs: Map<string, { | ||
name: string; | ||
}>; | ||
} | { | ||
name: string; | ||
kind: 'js-func'; | ||
args: string[]; | ||
func: (...args: any[]) => any; | ||
} | { | ||
name: string; | ||
kind: 'invalid'; | ||
args: string; | ||
}; | ||
export declare const MixinType: { | ||
@@ -68,4 +93,4 @@ ALL: "-st-mixin"; | ||
}; | ||
UNKNOWN_MIXIN_SYMBOL: { | ||
(name: string): import("../diagnostics").DiagnosticBase; | ||
UNSUPPORTED_MIXIN_SYMBOL: { | ||
(name: string, symbolType: "element" | "var" | "class" | "import" | "cssVar" | "keyframes"): import("../diagnostics").DiagnosticBase; | ||
code: string; | ||
@@ -79,14 +104,17 @@ severity: import("../diagnostics").DiagnosticSeverity; | ||
}; | ||
UNKNOWN_ARG: { | ||
(argName: any): import("../diagnostics").DiagnosticBase; | ||
code: string; | ||
severity: import("../diagnostics").DiagnosticSeverity; | ||
}; | ||
}; | ||
export declare const hooks: import("./feature").FeatureHooks<import("./feature").NodeTypes>; | ||
export declare function appendMixins(context: FeatureTransformContext, transformer: StylableTransformer, rule: postcss.Rule, cssPropertyMapping: Record<string, string>, path?: string[]): void; | ||
interface ApplyMixinContext { | ||
transformer: StylableTransformer; | ||
mixDef: RefedMixin; | ||
rule: postcss.Rule; | ||
path: string[]; | ||
cssPropertyMapping: Record<string, string>; | ||
export declare class StylablePublicApi { | ||
private stylable; | ||
constructor(stylable: Stylable); | ||
resolveExpr(meta: StylableMeta, expr: string, { diagnostics, resolveOptionalArgs, }?: { | ||
diagnostics?: Diagnostics; | ||
resolveOptionalArgs?: boolean; | ||
}): MixinReflection[]; | ||
} | ||
export declare function appendMixin(context: FeatureTransformContext, config: ApplyMixinContext): void; | ||
export {}; | ||
//# sourceMappingURL=st-mixin.d.ts.map |
@@ -26,6 +26,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.appendMixin = exports.appendMixins = exports.hooks = exports.diagnostics = exports.MixinType = void 0; | ||
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"); | ||
@@ -36,2 +37,3 @@ const mixin_1 = require("../helpers/mixin"); | ||
const postcss = __importStar(require("postcss")); | ||
const postcss_value_parser_1 = require("postcss-value-parser"); | ||
const stylable_assets_1 = require("../stylable-assets"); | ||
@@ -54,4 +56,5 @@ const stylable_utils_1 = require("../stylable-utils"); | ||
JS_MIXIN_NOT_A_FUNC: (0, diagnostics_1.createDiagnosticReporter)('10005', 'error', () => `js mixin must be a function`), | ||
UNKNOWN_MIXIN_SYMBOL: (0, diagnostics_1.createDiagnosticReporter)('10007', 'error', (name) => `cannot mixin unknown symbol "${name}"`), | ||
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}"`), | ||
}; | ||
@@ -65,2 +68,62 @@ // HOOKS | ||
// API | ||
class StylablePublicApi { | ||
constructor(stylable) { | ||
this.stylable = stylable; | ||
} | ||
resolveExpr(meta, expr, { diagnostics = new diagnostics_1.Diagnostics(), resolveOptionalArgs = false, } = {}) { | ||
var _a; | ||
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: ((_a = data.valueNode) === null || _a === void 0 ? void 0 : _a.type) === 'function' ? (0, postcss_value_parser_1.stringify)(data.valueNode.nodes) : '', | ||
}); | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
exports.StylablePublicApi = StylablePublicApi; | ||
function appendMixins(context, transformer, rule, cssPropertyMapping, path = []) { | ||
@@ -72,3 +135,5 @@ const [decls, mixins] = collectRuleMixins(context, rule); | ||
for (const mixin of mixins) { | ||
appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); | ||
if (mixin.valid) { | ||
appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); | ||
} | ||
} | ||
@@ -79,6 +144,6 @@ for (const mixinDecl of decls) { | ||
} | ||
exports.appendMixins = appendMixins; | ||
function collectRuleMixins(context, rule) { | ||
let mixins = []; | ||
const { mainNamespace } = context.getResolvedSymbols(context.meta); | ||
const resolvedSymbols = context.getResolvedSymbols(context.meta); | ||
const { mainNamespace } = resolvedSymbols; | ||
const decls = []; | ||
@@ -88,3 +153,3 @@ rule.walkDecls((decl) => { | ||
decls.push(decl); | ||
mixins = collectDeclMixins(context, decl, (mixinSymbolName) => { | ||
mixins = collectDeclMixins(context, resolvedSymbols, decl, (mixinSymbolName) => { | ||
return mainNamespace[mixinSymbolName] === 'js' ? 'args' : 'named'; | ||
@@ -96,3 +161,3 @@ }, mixins); | ||
} | ||
function collectDeclMixins(context, decl, paramSignature, previousMixins) { | ||
function collectDeclMixins(context, resolvedSymbols, decl, paramSignature, previousMixins) { | ||
const { meta } = context; | ||
@@ -110,6 +175,14 @@ let mixins = []; | ||
const mixinRefSymbol = STSymbol.get(meta, mixin.type); | ||
if (mixinRefSymbol && | ||
(mixinRefSymbol._kind === 'import' || | ||
mixinRefSymbol._kind === 'class' || | ||
mixinRefSymbol._kind === 'element')) { | ||
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) { | ||
@@ -121,18 +194,32 @@ context.diagnostics.report(exports.diagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS(mixin.type), { | ||
} | ||
const refedMixin = { | ||
mixin, | ||
ref: mixinRefSymbol, | ||
}; | ||
mixins.push(refedMixin); | ||
} | ||
else { | ||
context.diagnostics.report(exports.diagnostics.UNKNOWN_MIXIN(mixin.type), { | ||
node: decl, | ||
word: mixin.type, | ||
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.mixin.partial); | ||
const nonPartials = previousMixins.filter((r) => !r.mixin.partial); | ||
const partials = previousMixins.filter((r) => r.data.partial); | ||
const nonPartials = previousMixins.filter((r) => !r.data.partial); | ||
const isInPartial = decl.prop === exports.MixinType.PARTIAL; | ||
@@ -160,3 +247,3 @@ if ((partials.length && decl.prop === exports.MixinType.PARTIAL) || | ||
const resolvedSymbols = context.getResolvedSymbols(context.meta); | ||
const symbolName = config.mixDef.mixin.type; | ||
const symbolName = config.mixDef.data.type; | ||
const resolvedType = resolvedSymbols.mainNamespace[symbolName]; | ||
@@ -177,3 +264,3 @@ if (resolvedType === `class` || resolvedType === `element`) { | ||
node: config.rule, | ||
word: config.mixDef.mixin.type, | ||
word: config.mixDef.data.type, | ||
}); | ||
@@ -183,24 +270,11 @@ return; | ||
} | ||
else { | ||
context.diagnostics.report(exports.diagnostics.JS_MIXIN_NOT_A_FUNC(), { | ||
node: config.rule, | ||
word: config.mixDef.mixin.type, | ||
}); | ||
} | ||
return; | ||
} | ||
// ToDo: report on unsupported mixed in symbol type | ||
const mixinDecl = config.mixDef.mixin.originDecl; | ||
context.diagnostics.report(exports.diagnostics.UNKNOWN_MIXIN_SYMBOL(mixinDecl.value), { | ||
node: mixinDecl, | ||
word: mixinDecl.value, | ||
}); | ||
} | ||
exports.appendMixin = appendMixin; | ||
function checkRecursive({ meta, diagnostics: report }, { mixDef, path, rule }) { | ||
const symbolName = mixDef.ref.name === meta.root | ||
? mixDef.ref._kind === 'class' | ||
const symbolName = mixDef.symbol.name === meta.root | ||
? mixDef.symbol._kind === 'class' | ||
? meta.root | ||
: 'default' | ||
: mixDef.mixin.type; | ||
: mixDef.data.type; | ||
const isRecursive = path.includes(symbolName + ' from ' + meta.source); | ||
@@ -221,3 +295,3 @@ if (isRecursive) { | ||
const mixDef = config.mixDef; | ||
const res = mixinFunction(mixDef.mixin.options.map((v) => v.value)); | ||
const res = mixinFunction(mixDef.data.options.map((v) => v.value)); | ||
const mixinRoot = (0, parser_1.cssObjectToAst)(res).root; | ||
@@ -230,10 +304,10 @@ mixinRoot.walkDecls((decl) => { | ||
config.transformer.transformAst(mixinRoot, meta, undefined, stVarOverride, [], true); | ||
const mixinPath = mixDef.ref.import.request; | ||
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.mixin.originDecl, context.diagnostics); | ||
(0, stylable_utils_1.mergeRules)(mixinRoot, config.rule, mixDef.data.originDecl, context.diagnostics); | ||
} | ||
function handleCSSMixin(context, config, resolveChain) { | ||
const mixDef = config.mixDef; | ||
const isPartial = mixDef.mixin.partial; | ||
const namedArgs = mixDef.mixin.options; | ||
const isPartial = mixDef.data.partial; | ||
const namedArgs = mixDef.data.options; | ||
const overrideKeys = Object.keys(namedArgs); | ||
@@ -243,11 +317,29 @@ if (isPartial && overrideKeys.length === 0) { | ||
} | ||
const roots = []; | ||
for (const resolved of resolveChain) { | ||
roots.push(createMixinRootFromCSSResolve(context, config, resolved)); | ||
if (resolved.symbol[`-st-extends`]) { | ||
break; | ||
const optionalArgs = new Set(); | ||
const roots = getCSSMixinRoots(context.meta, resolveChain, ({ mixinRoot, resolvedClass, 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, config.cssPropertyMapping); | ||
collectOptionalArgs({ meta: resolvedClass.meta, resolver: context.resolver }, mixinRoot, optionalArgs); | ||
// transform mixin | ||
const mixinMeta = resolvedClass.meta; | ||
const symbolName = isRootMixin && resolvedClass.meta !== context.meta ? 'default' : mixDef.data.type; | ||
config.transformer.transformAst(mixinRoot, mixinMeta, undefined, resolvedArgs, config.path.concat(symbolName + ' from ' + context.meta.source), true, resolvedClass.symbol.name); | ||
(0, stylable_assets_1.fixRelativeUrls)(mixinRoot, resolvedClass.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.mixin.originDecl, config.transformer.diagnostics); | ||
(0, stylable_utils_1.mergeRules)(roots[0], config.rule, mixDef.data.originDecl, config.transformer.diagnostics); | ||
} | ||
@@ -257,21 +349,27 @@ else if (roots.length > 1) { | ||
roots.forEach((root) => mixinRoot.prepend(...root.nodes)); | ||
(0, stylable_utils_1.mergeRules)(mixinRoot, config.rule, mixDef.mixin.originDecl, config.transformer.diagnostics); | ||
(0, stylable_utils_1.mergeRules)(mixinRoot, config.rule, mixDef.data.originDecl, config.transformer.diagnostics); | ||
} | ||
} | ||
function createMixinRootFromCSSResolve(context, config, resolvedClass) { | ||
const stVarOverride = context.evaluator.stVarOverride || {}; | ||
const meta = context.meta; | ||
const mixDef = config.mixDef; | ||
const isRootMixin = resolvedClass.symbol.name === resolvedClass.meta.root; | ||
const mixinRoot = (0, rule_1.createSubsetAst)(resolvedClass.meta.sourceAst, (resolvedClass.symbol._kind === 'class' ? '.' : '') + resolvedClass.symbol.name, undefined, isRootMixin, (name) => STCustomSelector.getCustomSelector(meta, name)); | ||
const namedArgs = mixDef.mixin.options; | ||
if (mixDef.mixin.partial) { | ||
filterPartialMixinDecl(meta, mixinRoot, Object.keys(namedArgs)); | ||
function collectOptionalArgs(context, mixinRoot, optionalArgs = new Set()) { | ||
mixinRoot.walkDecls((decl) => { | ||
const varNames = STVar.parseVarsFromExpr(decl.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, resolvedClass: resolved, isRootMixin }); | ||
roots.push(mixinRoot); | ||
if (resolved.symbol[`-st-extends`]) { | ||
break; | ||
} | ||
} | ||
const resolvedArgs = (0, functions_1.resolveArgumentsValue)(namedArgs, config.transformer, context.meta, context.diagnostics, mixDef.mixin.originDecl, stVarOverride, config.path, config.cssPropertyMapping); | ||
const mixinMeta = resolvedClass.meta; | ||
const symbolName = isRootMixin && resolvedClass.meta !== meta ? 'default' : mixDef.mixin.type; | ||
config.transformer.transformAst(mixinRoot, mixinMeta, undefined, resolvedArgs, config.path.concat(symbolName + ' from ' + meta.source), true, resolvedClass.symbol.name); | ||
(0, stylable_assets_1.fixRelativeUrls)(mixinRoot, mixinMeta.source, meta.source); | ||
return mixinRoot; | ||
return roots; | ||
} | ||
@@ -278,0 +376,0 @@ /** we assume that mixinRoot is freshly created nodes from the ast */ |
@@ -19,2 +19,3 @@ import { FeatureContext } from './feature'; | ||
}; | ||
export declare const readableTypeMap: Record<StylableSymbol['_kind'], string>; | ||
declare type SymbolTypes = StylableSymbol['_kind']; | ||
@@ -21,0 +22,0 @@ declare type filterSymbols<T extends SymbolTypes> = Extract<StylableSymbol, { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.reportRedeclare = exports.addSymbol = exports.getAllByType = exports.getAll = exports.get = exports.hooks = exports.diagnostics = void 0; | ||
exports.reportRedeclare = exports.addSymbol = exports.getAllByType = exports.getAll = exports.get = exports.hooks = exports.diagnostics = exports.readableTypeMap = void 0; | ||
const feature_1 = require("./feature"); | ||
@@ -16,2 +16,10 @@ const plugable_record_1 = require("../helpers/plugable-record"); | ||
}; | ||
exports.readableTypeMap = { | ||
class: 'css class', | ||
element: 'css element type', | ||
cssVar: 'css custom property', | ||
import: 'stylable imported symbol', | ||
keyframes: 'css keyframes', | ||
var: 'stylable var', | ||
}; | ||
// state structure | ||
@@ -18,0 +26,0 @@ function createState(clone) { |
@@ -0,1 +1,2 @@ | ||
import { FeatureTransformContext } from './feature'; | ||
import { Box } from '../custom-values'; | ||
@@ -92,2 +93,4 @@ import type { StylableMeta } from '../stylable-meta'; | ||
} | ||
export declare function parseVarsFromExpr(expr: string): Set<string>; | ||
export declare function resolveReferencedVarNames(context: Pick<FeatureTransformContext, 'meta' | 'resolver'>, initialName: string): Set<string>; | ||
//# sourceMappingURL=st-var.d.ts.map |
@@ -25,4 +25,7 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.StylablePublicApi = exports.get = exports.hooks = exports.diagnostics = void 0; | ||
exports.resolveReferencedVarNames = exports.parseVarsFromExpr = exports.StylablePublicApi = exports.get = exports.hooks = exports.diagnostics = void 0; | ||
const feature_1 = require("./feature"); | ||
@@ -40,2 +43,3 @@ const custom_values_1 = require("../custom-values"); | ||
const diagnostics_2 = require("../diagnostics"); | ||
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser")); | ||
exports.diagnostics = { | ||
@@ -96,2 +100,3 @@ FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: diagnostics_1.generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR, | ||
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), | ||
@@ -177,2 +182,22 @@ meta: context.meta, | ||
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) { | ||
@@ -336,2 +361,35 @@ rule.walkDecls((decl) => { | ||
} | ||
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 === null || symbol === void 0 ? void 0 : 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 === null || resolved === void 0 ? void 0 : 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) { | ||
@@ -338,0 +396,0 @@ const symbol = resolve.symbol; |
@@ -14,2 +14,3 @@ "use strict"; | ||
const value_1 = require("./helpers/value"); | ||
const escape_1 = require("./helpers/escape"); | ||
const features_1 = require("./features"); | ||
@@ -34,3 +35,3 @@ const custom_values_1 = require("./custom-values"); | ||
for (const k in options) { | ||
resolvedArgs[k] = evalDeclarationValue(transformer.resolver, options[k], meta, node, variableOverride, transformer.replaceValueHook, diagnostics, path, cssVarsMapping, undefined); | ||
resolvedArgs[k] = evalDeclarationValue(transformer.resolver, (0, escape_1.unescapeCSS)(options[k]), meta, node, variableOverride, transformer.replaceValueHook, diagnostics, path, cssVarsMapping, undefined); | ||
} | ||
@@ -37,0 +38,0 @@ return resolvedArgs; |
export { Stylable, StylableConfig } from './stylable'; | ||
export { Diagnostics, Diagnostic, DiagnosticSeverity } from './diagnostics'; | ||
export type { StylableSymbol, ClassSymbol, ImportSymbol, ElementSymbol, CSSVarSymbol, VarSymbol, Imported, KeyframesSymbol, RefedMixin, MixinValue, ComputedStVar, FlatComputedStVar, } from './features'; | ||
export type { StylableSymbol, ClassSymbol, ImportSymbol, ElementSymbol, CSSVarSymbol, VarSymbol, Imported, KeyframesSymbol, MixinReflection, ComputedStVar, FlatComputedStVar, } from './features'; | ||
export { StylableMeta } from './stylable-meta'; | ||
@@ -5,0 +5,0 @@ export type { CSSResolve, JSResolve } from './stylable-resolver'; |
@@ -47,2 +47,3 @@ import * as postcss from 'postcss'; | ||
resolverCache?: StylableResolverCache; | ||
stVarOverride?: Record<string, string>; | ||
} | ||
@@ -64,2 +65,3 @@ export declare const transformerDiagnostics: { | ||
mode: EnvMode; | ||
private defaultStVarOverride; | ||
private evaluator; | ||
@@ -66,0 +68,0 @@ private getResolvedSymbols; |
@@ -61,2 +61,3 @@ "use strict"; | ||
this.mode = options.mode || 'production'; | ||
this.defaultStVarOverride = options.stVarOverride || {}; | ||
this.getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.resolver, this.diagnostics); | ||
@@ -88,3 +89,3 @@ } | ||
} | ||
transformAst(ast, meta, metaExports, stVarOverride, path = [], mixinTransform = false, topNestClassName = ``) { | ||
transformAst(ast, meta, metaExports, stVarOverride = this.defaultStVarOverride, path = [], mixinTransform = false, topNestClassName = ``) { | ||
const prevStVarOverride = this.evaluator.stVarOverride; | ||
@@ -91,0 +92,0 @@ this.evaluator.stVarOverride = stVarOverride; |
@@ -9,3 +9,3 @@ import type { CacheItem, FileProcessor, MinimalFS } from './cached-process-file'; | ||
import type { IStylableOptimizer, ModuleResolver } from './types'; | ||
import { STVar } from './features'; | ||
import { STImport, STVar, STMixin } from './features'; | ||
import { Dependency } from './visit-meta-css-dependencies'; | ||
@@ -38,3 +38,5 @@ export interface StylableConfig { | ||
resolver: StylableResolver; | ||
stModule: STImport.StylablePublicApi; | ||
stVar: STVar.StylablePublicApi; | ||
stMixin: STMixin.StylablePublicApi; | ||
projectRoot: string; | ||
@@ -41,0 +43,0 @@ protected fileSystem: MinimalFS; |
@@ -39,3 +39,5 @@ "use strict"; | ||
constructor(config) { | ||
this.stModule = new features_1.STImport.StylablePublicApi(this); | ||
this.stVar = new features_1.STVar.StylablePublicApi(this); | ||
this.stMixin = new features_1.STMixin.StylablePublicApi(this); | ||
this.diagnostics = new diagnostics_1.Diagnostics(); | ||
@@ -109,6 +111,6 @@ this.projectRoot = config.projectRoot; | ||
} | ||
transform(meta, resourcePath, options = {}, processorOptions = {}) { | ||
if (typeof meta === 'string') { | ||
meta = this.createProcessor(processorOptions).process(this.cssParser(meta, { from: resourcePath })); | ||
} | ||
transform(metaOrSource, resourcePath, options = {}, processorOptions = {}) { | ||
const meta = typeof metaOrSource === 'string' | ||
? this.createProcessor(processorOptions).process(this.cssParser(metaOrSource, { from: resourcePath })) | ||
: metaOrSource; | ||
const transformer = this.createTransformer(options); | ||
@@ -115,0 +117,0 @@ this.fileProcessor.add(meta.source, meta); |
{ | ||
"name": "@stylable/core", | ||
"version": "5.0.0-rc.2", | ||
"version": "5.0.0-rc.3", | ||
"description": "CSS for Components", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -19,3 +19,3 @@ export type { FeatureContext, FeatureTransformContext } from './feature'; | ||
export * as STMixin from './st-mixin'; | ||
export type { RefedMixin, MixinValue } from './st-mixin'; | ||
export type { MixinReflection, MixinValue } from './st-mixin'; | ||
@@ -22,0 +22,0 @@ export * as CSSClass from './css-class'; |
@@ -13,2 +13,3 @@ import { createFeature, FeatureContext, FeatureTransformContext } from './feature'; | ||
import { createDiagnosticReporter } from '../diagnostics'; | ||
import type { Stylable } from '../stylable'; | ||
@@ -23,2 +24,11 @@ export interface ImportSymbol { | ||
export interface AnalyzedImport { | ||
from: string; | ||
default: string; | ||
named: Record<string, string>; | ||
typed: { | ||
keyframes: Record<string, string>; | ||
}; | ||
} | ||
export interface Imported { | ||
@@ -152,2 +162,16 @@ from: string; | ||
export class StylablePublicApi { | ||
constructor(private stylable: Stylable) {} | ||
public analyze(meta: StylableMeta): AnalyzedImport[] { | ||
return getImportStatements(meta).map(({ request, defaultExport, named, keyframes }) => ({ | ||
from: request, | ||
default: defaultExport, | ||
named, | ||
typed: { | ||
keyframes, | ||
}, | ||
})); | ||
} | ||
} | ||
function isImportStatement(node: postcss.ChildNode): node is postcss.Rule | postcss.AtRule { | ||
@@ -154,0 +178,0 @@ return ( |
@@ -1,5 +0,6 @@ | ||
import { createFeature, FeatureContext, FeatureTransformContext } from './feature'; | ||
import { createFeature, FeatureTransformContext } from './feature'; | ||
import * as STSymbol from './st-symbol'; | ||
import type { ImportSymbol } from './st-import'; | ||
import * as STCustomSelector from './st-custom-selector'; | ||
import * as STVar from './st-var'; | ||
import type { ElementSymbol } from './css-type'; | ||
@@ -12,10 +13,11 @@ import type { ClassSymbol } from './css-class'; | ||
import * as postcss from 'postcss'; | ||
import type { FunctionNode, WordNode } from 'postcss-value-parser'; | ||
import { FunctionNode, WordNode, stringify } from 'postcss-value-parser'; | ||
import { fixRelativeUrls } from '../stylable-assets'; | ||
import { isValidDeclaration, mergeRules, utilDiagnostics } from '../stylable-utils'; | ||
import type { StylableMeta } from '../stylable-meta'; | ||
import type { CSSResolve } from '../stylable-resolver'; | ||
import type { CSSResolve, MetaResolvedSymbols } from '../stylable-resolver'; | ||
import type { StylableTransformer } from '../stylable-transformer'; | ||
import { dirname } from 'path'; | ||
import { createDiagnosticReporter } from '../diagnostics'; | ||
import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; | ||
import type { Stylable } from '../stylable'; | ||
@@ -30,7 +32,26 @@ export interface MixinValue { | ||
export interface RefedMixin { | ||
mixin: MixinValue; | ||
ref: ImportSymbol | ClassSymbol | ElementSymbol; | ||
} | ||
export type ValidMixinSymbols = ImportSymbol | ClassSymbol | ElementSymbol; | ||
export type AnalyzedMixin = | ||
| { | ||
valid: true; | ||
data: MixinValue; | ||
symbol: ValidMixinSymbols; | ||
} | ||
| { | ||
valid: false; | ||
data: MixinValue; | ||
symbol: Exclude<STSymbol.StylableSymbol, ValidMixinSymbols> | undefined; | ||
}; | ||
export type MixinReflection = | ||
| { | ||
name: string; | ||
kind: 'css-fragment'; | ||
args: Record<string, string>[]; | ||
optionalArgs: Map<string, { name: string }>; | ||
} | ||
| { name: string; kind: 'js-func'; args: string[]; func: (...args: any[]) => any } | ||
| { name: string; kind: 'invalid'; args: string }; | ||
export const MixinType = { | ||
@@ -71,6 +92,7 @@ ALL: `-st-mixin` as const, | ||
), | ||
UNKNOWN_MIXIN_SYMBOL: createDiagnosticReporter( | ||
UNSUPPORTED_MIXIN_SYMBOL: createDiagnosticReporter( | ||
'10007', | ||
'error', | ||
(name: string) => `cannot mixin unknown symbol "${name}"` | ||
(name: string, symbolType: STSymbol.StylableSymbol['_kind']) => | ||
`cannot mix unsupported symbol "${name}" of type "${STSymbol.readableTypeMap[symbolType]}"` | ||
), | ||
@@ -82,2 +104,7 @@ CIRCULAR_MIXIN: createDiagnosticReporter( | ||
), | ||
UNKNOWN_ARG: createDiagnosticReporter( | ||
'10009', | ||
'warning', | ||
(argName) => `unknown mixin argument "${argName}"` | ||
), | ||
}; | ||
@@ -94,4 +121,77 @@ | ||
// API | ||
export class StylablePublicApi { | ||
constructor(private stylable: Stylable) {} | ||
public resolveExpr( | ||
meta: StylableMeta, | ||
expr: string, | ||
{ | ||
diagnostics = new Diagnostics(), | ||
resolveOptionalArgs = false, | ||
}: { diagnostics?: Diagnostics; resolveOptionalArgs?: boolean } = {} | ||
) { | ||
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: MixinReflection[] = []; | ||
for (const { data } of analyzedMixins) { | ||
const name = data.type; | ||
const symbolKind = mainNamespace[name]; | ||
if (symbolKind === 'class' || symbolKind === 'element') { | ||
const mixRef: MixinReflection = { | ||
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<string, { name: string }>(); | ||
const resolveChain = resolvedSymbols[symbolKind][name]; | ||
getCSSMixinRoots(meta, resolveChain, ({ mixinRoot }) => { | ||
const names = new Set<string>(); | ||
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: MixinReflection = { | ||
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' ? stringify(data.valueNode.nodes) : '', | ||
}); | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
export function appendMixins( | ||
function appendMixins( | ||
context: FeatureTransformContext, | ||
@@ -108,3 +208,5 @@ transformer: StylableTransformer, | ||
for (const mixin of mixins) { | ||
appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); | ||
if (mixin.valid) { | ||
appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); | ||
} | ||
} | ||
@@ -119,5 +221,6 @@ for (const mixinDecl of decls) { | ||
rule: postcss.Rule | ||
): [decls: postcss.Declaration[], mixins: RefedMixin[]] { | ||
let mixins: RefedMixin[] = []; | ||
const { mainNamespace } = context.getResolvedSymbols(context.meta); | ||
): [decls: postcss.Declaration[], mixins: AnalyzedMixin[]] { | ||
let mixins: AnalyzedMixin[] = []; | ||
const resolvedSymbols = context.getResolvedSymbols(context.meta); | ||
const { mainNamespace } = resolvedSymbols; | ||
const decls: postcss.Declaration[] = []; | ||
@@ -129,2 +232,3 @@ rule.walkDecls((decl) => { | ||
context, | ||
resolvedSymbols, | ||
decl, | ||
@@ -142,9 +246,10 @@ (mixinSymbolName) => { | ||
function collectDeclMixins( | ||
context: FeatureContext, | ||
context: Pick<FeatureTransformContext, 'meta' | 'diagnostics'>, | ||
resolvedSymbols: MetaResolvedSymbols, | ||
decl: postcss.Declaration, | ||
paramSignature: (mixinSymbolName: string) => 'named' | 'args', | ||
previousMixins?: RefedMixin[] | ||
): RefedMixin[] { | ||
previousMixins?: AnalyzedMixin[] | ||
): AnalyzedMixin[] { | ||
const { meta } = context; | ||
let mixins: RefedMixin[] = []; | ||
let mixins: AnalyzedMixin[] = []; | ||
const parser = | ||
@@ -163,8 +268,16 @@ decl.prop === MixinType.ALL | ||
const mixinRefSymbol = STSymbol.get(meta, mixin.type); | ||
const symbolName = mixin.type; | ||
const resolvedType = resolvedSymbols.mainNamespace[symbolName]; | ||
if ( | ||
mixinRefSymbol && | ||
(mixinRefSymbol._kind === 'import' || | ||
mixinRefSymbol._kind === 'class' || | ||
mixinRefSymbol._kind === 'element') | ||
resolvedType && | ||
((resolvedType === 'js' && | ||
typeof resolvedSymbols.js[symbolName].symbol === 'function') || | ||
resolvedType === 'class' || | ||
resolvedType === 'element') | ||
) { | ||
mixins.push({ | ||
valid: true, | ||
data: mixin, | ||
symbol: mixinRefSymbol as ValidMixinSymbols, | ||
}); | ||
if (mixin.partial && Object.keys(mixin.options).length === 0) { | ||
@@ -179,12 +292,29 @@ context.diagnostics.report( | ||
} | ||
const refedMixin = { | ||
mixin, | ||
ref: mixinRefSymbol, | ||
}; | ||
mixins.push(refedMixin); | ||
} else { | ||
context.diagnostics.report(diagnostics.UNKNOWN_MIXIN(mixin.type), { | ||
node: decl, | ||
word: mixin.type, | ||
mixins.push({ | ||
valid: false, | ||
data: mixin, | ||
symbol: mixinRefSymbol as | ||
| Exclude<STSymbol.StylableSymbol, ValidMixinSymbols> | ||
| undefined, | ||
}); | ||
if (resolvedType === 'js') { | ||
context.diagnostics.report(diagnostics.JS_MIXIN_NOT_A_FUNC(), { | ||
node: decl, | ||
word: mixin.type, | ||
}); | ||
} else if (resolvedType) { | ||
context.diagnostics.report( | ||
diagnostics.UNSUPPORTED_MIXIN_SYMBOL(mixin.type, resolvedType), | ||
{ | ||
node: decl, | ||
word: mixin.type, | ||
} | ||
); | ||
} else { | ||
context.diagnostics.report(diagnostics.UNKNOWN_MIXIN(mixin.type), { | ||
node: decl, | ||
word: mixin.type, | ||
}); | ||
} | ||
} | ||
@@ -195,4 +325,4 @@ } | ||
if (previousMixins) { | ||
const partials = previousMixins.filter((r) => r.mixin.partial); | ||
const nonPartials = previousMixins.filter((r) => !r.mixin.partial); | ||
const partials = previousMixins.filter((r) => r.data.partial); | ||
const nonPartials = previousMixins.filter((r) => !r.data.partial); | ||
const isInPartial = decl.prop === MixinType.PARTIAL; | ||
@@ -218,3 +348,3 @@ if ( | ||
transformer: StylableTransformer; | ||
mixDef: RefedMixin; | ||
mixDef: AnalyzedMixin & { valid: true }; | ||
rule: postcss.Rule; | ||
@@ -225,3 +355,3 @@ path: string[]; | ||
export function appendMixin(context: FeatureTransformContext, config: ApplyMixinContext) { | ||
function appendMixin(context: FeatureTransformContext, config: ApplyMixinContext) { | ||
if (checkRecursive(context, config)) { | ||
@@ -231,3 +361,3 @@ return; | ||
const resolvedSymbols = context.getResolvedSymbols(context.meta); | ||
const symbolName = config.mixDef.mixin.type; | ||
const symbolName = config.mixDef.data.type; | ||
const resolvedType = resolvedSymbols.mainNamespace[symbolName]; | ||
@@ -246,21 +376,9 @@ if (resolvedType === `class` || resolvedType === `element`) { | ||
node: config.rule, | ||
word: config.mixDef.mixin.type, | ||
word: config.mixDef.data.type, | ||
}); | ||
return; | ||
} | ||
} else { | ||
context.diagnostics.report(diagnostics.JS_MIXIN_NOT_A_FUNC(), { | ||
node: config.rule, | ||
word: config.mixDef.mixin.type, | ||
}); | ||
} | ||
return; | ||
} | ||
// ToDo: report on unsupported mixed in symbol type | ||
const mixinDecl = config.mixDef.mixin.originDecl; | ||
context.diagnostics.report(diagnostics.UNKNOWN_MIXIN_SYMBOL(mixinDecl.value), { | ||
node: mixinDecl, | ||
word: mixinDecl.value, | ||
}); | ||
} | ||
@@ -273,7 +391,7 @@ | ||
const symbolName = | ||
mixDef.ref.name === meta.root | ||
? mixDef.ref._kind === 'class' | ||
mixDef.symbol.name === meta.root | ||
? mixDef.symbol._kind === 'class' | ||
? meta.root | ||
: 'default' | ||
: mixDef.mixin.type; | ||
: mixDef.data.type; | ||
const isRecursive = path.includes(symbolName + ' from ' + meta.source); | ||
@@ -299,3 +417,3 @@ if (isRecursive) { | ||
const mixDef = config.mixDef; | ||
const res = mixinFunction((mixDef.mixin.options as any[]).map((v) => v.value)); | ||
const res = mixinFunction((mixDef.data.options as any[]).map((v) => v.value)); | ||
const mixinRoot = cssObjectToAst(res).root; | ||
@@ -310,3 +428,3 @@ | ||
config.transformer.transformAst(mixinRoot, meta, undefined, stVarOverride, [], true); | ||
const mixinPath = (mixDef.ref as ImportSymbol).import.request; | ||
const mixinPath = (mixDef.symbol as ImportSymbol).import.request; | ||
fixRelativeUrls( | ||
@@ -318,3 +436,3 @@ mixinRoot, | ||
mergeRules(mixinRoot, config.rule, mixDef.mixin.originDecl, context.diagnostics); | ||
mergeRules(mixinRoot, config.rule, mixDef.data.originDecl, context.diagnostics); | ||
} | ||
@@ -328,4 +446,4 @@ | ||
const mixDef = config.mixDef; | ||
const isPartial = mixDef.mixin.partial; | ||
const namedArgs = mixDef.mixin.options as Record<string, string>; | ||
const isPartial = mixDef.data.partial; | ||
const namedArgs = mixDef.data.options as Record<string, string>; | ||
const overrideKeys = Object.keys(namedArgs); | ||
@@ -337,69 +455,107 @@ | ||
const roots = []; | ||
for (const resolved of resolveChain) { | ||
roots.push(createMixinRootFromCSSResolve(context, config, resolved)); | ||
if (resolved.symbol[`-st-extends`]) { | ||
break; | ||
const optionalArgs = new Set<string>(); | ||
const roots = getCSSMixinRoots( | ||
context.meta, | ||
resolveChain, | ||
({ mixinRoot, resolvedClass, isRootMixin }) => { | ||
const stVarOverride = context.evaluator.stVarOverride || {}; | ||
const mixDef = config.mixDef; | ||
const namedArgs = mixDef.data.options as Record<string, string>; | ||
if (mixDef.data.partial) { | ||
filterPartialMixinDecl(context.meta, mixinRoot, Object.keys(namedArgs)); | ||
} | ||
// resolve override args | ||
const resolvedArgs = resolveArgumentsValue( | ||
namedArgs, | ||
config.transformer, | ||
context.meta, | ||
context.diagnostics, | ||
mixDef.data.originDecl, | ||
stVarOverride, | ||
config.path, | ||
config.cssPropertyMapping | ||
); | ||
collectOptionalArgs( | ||
{ meta: resolvedClass.meta, resolver: context.resolver }, | ||
mixinRoot, | ||
optionalArgs | ||
); | ||
// transform mixin | ||
const mixinMeta: StylableMeta = resolvedClass.meta; | ||
const symbolName = | ||
isRootMixin && resolvedClass.meta !== context.meta ? 'default' : mixDef.data.type; | ||
config.transformer.transformAst( | ||
mixinRoot, | ||
mixinMeta, | ||
undefined, | ||
resolvedArgs, | ||
config.path.concat(symbolName + ' from ' + context.meta.source), | ||
true, | ||
resolvedClass.symbol.name | ||
); | ||
fixRelativeUrls(mixinRoot, resolvedClass.meta.source, context.meta.source); | ||
} | ||
); | ||
for (const overrideArg of overrideKeys) { | ||
if (!optionalArgs.has(overrideArg)) { | ||
context.diagnostics.report(diagnostics.UNKNOWN_ARG(overrideArg), { | ||
node: mixDef.data.originDecl, | ||
word: overrideArg, | ||
}); | ||
} | ||
} | ||
if (roots.length === 1) { | ||
mergeRules(roots[0], config.rule, mixDef.mixin.originDecl, config.transformer.diagnostics); | ||
mergeRules(roots[0], config.rule, mixDef.data.originDecl, config.transformer.diagnostics); | ||
} else if (roots.length > 1) { | ||
const mixinRoot = postcss.root(); | ||
roots.forEach((root) => mixinRoot.prepend(...root.nodes)); | ||
mergeRules(mixinRoot, config.rule, mixDef.mixin.originDecl, config.transformer.diagnostics); | ||
mergeRules(mixinRoot, config.rule, mixDef.data.originDecl, config.transformer.diagnostics); | ||
} | ||
} | ||
function createMixinRootFromCSSResolve( | ||
context: FeatureTransformContext, | ||
config: ApplyMixinContext, | ||
resolvedClass: CSSResolve | ||
function collectOptionalArgs( | ||
context: Pick<FeatureTransformContext, 'meta' | 'resolver'>, | ||
mixinRoot: postcss.Root, | ||
optionalArgs: Set<string> = new Set() | ||
) { | ||
const stVarOverride = context.evaluator.stVarOverride || {}; | ||
const meta = context.meta; | ||
const mixDef = config.mixDef; | ||
const isRootMixin = resolvedClass.symbol.name === resolvedClass.meta.root; | ||
const mixinRoot = createSubsetAst<postcss.Root>( | ||
resolvedClass.meta.sourceAst, | ||
(resolvedClass.symbol._kind === 'class' ? '.' : '') + resolvedClass.symbol.name, | ||
undefined, | ||
isRootMixin, | ||
(name) => STCustomSelector.getCustomSelector(meta, name) | ||
); | ||
mixinRoot.walkDecls((decl) => { | ||
const varNames = STVar.parseVarsFromExpr(decl.value); | ||
for (const name of varNames) { | ||
for (const refName of STVar.resolveReferencedVarNames(context, name)) { | ||
optionalArgs.add(refName); | ||
} | ||
} | ||
}); | ||
} | ||
const namedArgs = mixDef.mixin.options as Record<string, string>; | ||
if (mixDef.mixin.partial) { | ||
filterPartialMixinDecl(meta, mixinRoot, Object.keys(namedArgs)); | ||
function getCSSMixinRoots( | ||
contextMeta: StylableMeta, | ||
resolveChain: CSSResolve<ClassSymbol | ElementSymbol>[], | ||
processMixinRoot: (data: { | ||
mixinRoot: postcss.Root; | ||
resolvedClass: CSSResolve; | ||
isRootMixin: boolean; | ||
}) => void | ||
) { | ||
const roots = []; | ||
for (const resolved of resolveChain) { | ||
const isRootMixin = resolved.symbol.name === resolved.meta.root; | ||
const mixinRoot = createSubsetAst<postcss.Root>( | ||
resolved.meta.sourceAst, | ||
(resolved.symbol._kind === 'class' ? '.' : '') + resolved.symbol.name, | ||
undefined, | ||
isRootMixin, | ||
(name) => STCustomSelector.getCustomSelector(contextMeta, name) | ||
); | ||
processMixinRoot({ mixinRoot, resolvedClass: resolved, isRootMixin }); | ||
roots.push(mixinRoot); | ||
if (resolved.symbol[`-st-extends`]) { | ||
break; | ||
} | ||
} | ||
const resolvedArgs = resolveArgumentsValue( | ||
namedArgs, | ||
config.transformer, | ||
context.meta, | ||
context.diagnostics, | ||
mixDef.mixin.originDecl, | ||
stVarOverride, | ||
config.path, | ||
config.cssPropertyMapping | ||
); | ||
const mixinMeta: StylableMeta = resolvedClass.meta; | ||
const symbolName = isRootMixin && resolvedClass.meta !== meta ? 'default' : mixDef.mixin.type; | ||
config.transformer.transformAst( | ||
mixinRoot, | ||
mixinMeta, | ||
undefined, | ||
resolvedArgs, | ||
config.path.concat(symbolName + ' from ' + meta.source), | ||
true, | ||
resolvedClass.symbol.name | ||
); | ||
fixRelativeUrls(mixinRoot, mixinMeta.source, meta.source); | ||
return mixinRoot; | ||
return roots; | ||
} | ||
@@ -406,0 +562,0 @@ |
@@ -32,2 +32,10 @@ import { FeatureContext, createFeature } from './feature'; | ||
} as const; | ||
export const readableTypeMap: Record<StylableSymbol['_kind'], string> = { | ||
class: 'css class', | ||
element: 'css element type', | ||
cssVar: 'css custom property', | ||
import: 'stylable imported symbol', | ||
keyframes: 'css keyframes', | ||
var: 'stylable var', | ||
}; | ||
// state structure | ||
@@ -34,0 +42,0 @@ function createState(clone?: State): State { |
@@ -19,2 +19,3 @@ import { createFeature, FeatureContext, FeatureTransformContext } from './feature'; | ||
import type { RuntimeStVar } from '../stylable-transformer'; | ||
import postcssValueParser from 'postcss-value-parser'; | ||
@@ -145,2 +146,3 @@ export interface VarSymbol { | ||
const evaluated = context.evaluator.evaluateValue(noDaigContext, { | ||
// ToDo: change to `value(${name})` in order to fix overrides in exports | ||
value: stripQuotation(symbol.text), | ||
@@ -253,2 +255,22 @@ meta: context.meta, | ||
export function parseVarsFromExpr(expr: string) { | ||
const nameSet = new Set<string>(); | ||
postcssValueParser(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; | ||
} | ||
function collectVarSymbols(context: FeatureContext, rule: postcss.Rule) { | ||
@@ -433,2 +455,40 @@ rule.walkDecls((decl) => { | ||
export function resolveReferencedVarNames( | ||
context: Pick<FeatureTransformContext, 'meta' | 'resolver'>, | ||
initialName: string | ||
) { | ||
const refNames = new Set<string>(); | ||
const varsToCheck: { meta: StylableMeta; name: string }[] = [ | ||
{ meta: context.meta, name: initialName }, | ||
]; | ||
const checked = new Set<string>(); | ||
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; | ||
} | ||
function reportUnsupportedSymbolInValue( | ||
@@ -435,0 +495,0 @@ context: FeatureTransformContext, |
@@ -15,2 +15,3 @@ import { dirname, relative } from 'path'; | ||
import { getFormatterArgs, getStringValue, stringifyFunction } from './helpers/value'; | ||
import { unescapeCSS } from './helpers/escape'; | ||
import type { ParsedValue } from './types'; | ||
@@ -98,3 +99,3 @@ import type { FeatureTransformContext } from './features/feature'; | ||
transformer.resolver, | ||
options[k], | ||
unescapeCSS(options[k]), | ||
meta, | ||
@@ -101,0 +102,0 @@ node, |
@@ -12,4 +12,3 @@ export { Stylable, StylableConfig } from './stylable'; | ||
KeyframesSymbol, | ||
RefedMixin, | ||
MixinValue, | ||
MixinReflection, | ||
ComputedStVar, | ||
@@ -16,0 +15,0 @@ FlatComputedStVar, |
@@ -98,2 +98,3 @@ import isVendorPrefixed from 'is-vendor-prefixed'; | ||
resolverCache?: StylableResolverCache; | ||
stVarOverride?: Record<string, string>; | ||
} | ||
@@ -117,2 +118,3 @@ | ||
public mode: EnvMode; | ||
private defaultStVarOverride: Record<string, string>; | ||
private evaluator: StylableEvaluator = new StylableEvaluator(); | ||
@@ -134,2 +136,3 @@ private getResolvedSymbols: ReturnType<typeof createSymbolResolverWithCache>; | ||
this.mode = options.mode || 'production'; | ||
this.defaultStVarOverride = options.stVarOverride || {}; | ||
this.getResolvedSymbols = createSymbolResolverWithCache(this.resolver, this.diagnostics); | ||
@@ -166,3 +169,3 @@ } | ||
metaExports?: StylableExports, | ||
stVarOverride?: Record<string, string>, | ||
stVarOverride: Record<string, string> = this.defaultStVarOverride, | ||
path: string[] = [], | ||
@@ -169,0 +172,0 @@ mixinTransform = false, |
@@ -17,3 +17,3 @@ import type { CacheItem, FileProcessor, MinimalFS } from './cached-process-file'; | ||
import { createDefaultResolver } from './module-resolver'; | ||
import { STVar, CSSCustomProperty } from './features'; | ||
import { STImport, STVar, STMixin, CSSCustomProperty } from './features'; | ||
import { Dependency, visitMetaCSSDependencies } from './visit-meta-css-dependencies'; | ||
@@ -52,3 +52,5 @@ import * as postcss from 'postcss'; | ||
public resolver: StylableResolver; | ||
public stModule = new STImport.StylablePublicApi(this); | ||
public stVar = new STVar.StylablePublicApi(this); | ||
public stMixin = new STMixin.StylablePublicApi(this); | ||
// | ||
@@ -149,6 +151,7 @@ public projectRoot: string; | ||
} | ||
// ToDo: unify signature - accept only meta + optional transformer options | ||
public transform(meta: StylableMeta): StylableResults; | ||
public transform(source: string, resourcePath: string): StylableResults; | ||
public transform( | ||
meta: string | StylableMeta, | ||
metaOrSource: string | StylableMeta, | ||
resourcePath?: string, | ||
@@ -158,7 +161,8 @@ options: Partial<TransformerOptions> = {}, | ||
): StylableResults { | ||
if (typeof meta === 'string') { | ||
meta = this.createProcessor(processorOptions).process( | ||
this.cssParser(meta, { from: resourcePath }) | ||
); | ||
} | ||
const meta = | ||
typeof metaOrSource === 'string' | ||
? this.createProcessor(processorOptions).process( | ||
this.cssParser(metaOrSource, { from: resourcePath }) | ||
) | ||
: metaOrSource; | ||
const transformer = this.createTransformer(options); | ||
@@ -165,0 +169,0 @@ this.fileProcessor.add(meta.source, meta); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1020179
18190