@stylable/core
Version:
CSS for Components
433 lines • 16.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSymbolResolverWithCache = exports.StylableResolver = exports.isValidCSSResolve = void 0;
const features_1 = require("./features");
const rule_1 = require("./helpers/rule");
const custom_values_1 = require("./custom-values");
function isValidCSSResolve(resolved) {
return resolved.symbol !== undefined;
}
exports.isValidCSSResolve = isValidCSSResolve;
function isInPath(extendPath, { symbol: { name: name1 }, meta: { source: source1 } }) {
return extendPath.find(({ symbol: { name }, meta: { source } }) => {
return name1 === name && source === source1;
});
}
// this is a safe cache key delimiter for all OS;
const safePathDelimiter = ';:';
class StylableResolver {
constructor(fileProcessor, requireModule, moduleResolver, cache) {
this.fileProcessor = fileProcessor;
this.requireModule = requireModule;
this.moduleResolver = moduleResolver;
this.cache = cache;
}
getModule({ context, request, }) {
let entity;
let resolvedPath;
const key = cacheKey(context, request);
if (this.cache?.has(key)) {
const entity = this.cache.get(key);
if (entity.kind === 'resolve') {
resolvedPath = entity.resolvedPath;
}
else {
return entity;
}
}
try {
resolvedPath || (resolvedPath = this.moduleResolver(context, request));
}
catch (error) {
entity = {
kind: request.endsWith('css') ? 'css' : 'js',
value: null,
error,
request,
context,
resolvedPath: undefined,
};
this.cache?.set(key, entity);
return entity;
}
if (resolvedPath.endsWith('.css')) {
const kind = 'css';
try {
entity = { kind, value: this.fileProcessor.process(resolvedPath), resolvedPath };
}
catch (error) {
entity = { kind, value: null, error, request, context, resolvedPath };
}
}
else {
const kind = 'js';
try {
entity = { kind, value: this.requireModule(resolvedPath), resolvedPath };
}
catch (error) {
entity = { kind, value: null, error, request, context, resolvedPath };
}
}
this.cache?.set(key, entity);
return entity;
}
analyze(filePath) {
return this.fileProcessor.process(filePath);
}
resolvePath(directoryPath, request) {
const key = cacheKey(directoryPath, request);
let resolvedPath = this.cache?.get(key)?.resolvedPath;
if (resolvedPath !== undefined) {
return resolvedPath;
}
resolvedPath = this.moduleResolver(directoryPath, request);
this.cache?.set(key, {
resolvedPath,
value: null,
kind: 'resolve',
});
return resolvedPath;
}
resolveImported(imported, name, subtype = 'mappedSymbols') {
const res = this.getModule(imported);
if (res.value === null) {
return null;
}
if (res.kind === 'css') {
const { value: meta } = res;
const namespace = subtype === `mappedSymbols`
? `main`
: subtype === 'mappedKeyframes'
? `keyframes`
: subtype;
name = !name && namespace === `main` ? meta.root : name;
const symbol = features_1.STSymbol.getAll(meta, namespace)[name];
return {
_kind: 'css',
symbol,
meta,
};
}
else {
const { value: jsModule } = res;
return {
_kind: 'js',
symbol: !name ? jsModule.default || jsModule : jsModule[name],
meta: null,
};
}
}
resolveImport(importSymbol) {
const name = importSymbol.type === 'named' ? importSymbol.name : '';
return this.resolveImported(importSymbol.import, name);
}
resolve(maybeImport) {
if (!maybeImport || maybeImport._kind !== 'import') {
if (maybeImport &&
maybeImport._kind !== 'var' &&
maybeImport._kind !== 'cssVar' &&
maybeImport._kind !== 'keyframes' &&
maybeImport._kind !== 'layer' &&
maybeImport._kind !== 'container') {
if (maybeImport.alias && !maybeImport[`-st-extends`]) {
maybeImport = maybeImport.alias;
}
else if (maybeImport[`-st-extends`]) {
maybeImport = maybeImport[`-st-extends`];
}
else {
return null;
}
}
else if (maybeImport && maybeImport._kind === 'cssVar') {
if (maybeImport.alias) {
maybeImport = maybeImport.alias;
}
else {
return null;
}
}
else {
return null;
}
}
if (!maybeImport || maybeImport._kind !== 'import') {
return null;
}
return this.resolveImport(maybeImport);
}
deepResolve(maybeImport, path = []) {
let resolved = this.resolve(maybeImport);
while (resolved &&
resolved._kind === 'css' &&
resolved.symbol &&
resolved.symbol._kind === 'import') {
resolved = this.resolve(resolved.symbol);
}
if (resolved && resolved.symbol && resolved.meta) {
if (((resolved.symbol._kind === 'class' || resolved.symbol._kind === 'element') &&
resolved.symbol.alias &&
!resolved.symbol[`-st-extends`]) ||
(resolved.symbol._kind === 'cssVar' && resolved.symbol.alias)) {
if (path.includes(resolved.symbol)) {
return { _kind: 'css', symbol: resolved.symbol, meta: resolved.meta };
}
path.push(resolved.symbol);
return this.deepResolve(resolved.symbol.alias, path);
}
}
return resolved;
}
resolveSymbolOrigin(symbol, meta, path = []) {
if (!symbol || !meta) {
return null;
}
if (symbol._kind === 'element' || symbol._kind === 'class') {
if (path.includes(symbol)) {
return { meta, symbol, _kind: 'css' };
}
path.push(symbol);
const isAliasOnly = symbol.alias && !symbol[`-st-extends`];
return isAliasOnly
? this.resolveSymbolOrigin(symbol.alias, meta, path)
: { meta, symbol, _kind: 'css' };
}
else if (symbol._kind === 'cssVar') {
if (path.includes(symbol)) {
return { meta, symbol, _kind: 'css' };
}
}
else if (symbol._kind === 'import') {
const resolved = this.resolveImport(symbol);
if (resolved && resolved.symbol && resolved._kind === 'css') {
return this.resolveSymbolOrigin(resolved.symbol, resolved.meta, path);
}
else {
return null;
}
}
return null;
}
resolveSymbols(meta, diagnostics) {
const resolvedSymbols = {
mainNamespace: {},
class: {},
element: {},
var: {},
js: {},
customValues: { ...custom_values_1.stTypes },
keyframes: {},
layer: {},
container: {},
cssVar: {},
import: {},
};
// resolve main namespace
for (const [name, symbol] of Object.entries(meta.getAllSymbols())) {
let deepResolved;
if (symbol._kind === `import` || (symbol._kind === `cssVar` && symbol.alias)) {
deepResolved = this.deepResolve(symbol);
if (!deepResolved || !deepResolved.symbol) {
// diagnostics for unresolved imports are reported
// as part of st-import validateImports
continue;
}
else if (deepResolved?._kind === `js`) {
resolvedSymbols.js[name] = deepResolved;
resolvedSymbols.mainNamespace[name] = `js`;
const customValueSymbol = deepResolved.symbol;
if ((0, custom_values_1.isCustomValue)(customValueSymbol)) {
resolvedSymbols.customValues[name] = customValueSymbol.register(name);
}
continue;
}
else if (symbol._kind !== `cssVar` &&
(deepResolved.symbol._kind === `class` ||
deepResolved.symbol._kind === `element`)) {
// virtual alias
deepResolved = {
_kind: `css`,
meta,
symbol: features_1.CSSClass.createSymbol({ name, alias: symbol }),
};
}
}
else {
deepResolved = { _kind: `css`, meta, symbol };
}
// deepResolved symbol is resolved by this point
switch (deepResolved.symbol._kind) {
case `class`:
resolvedSymbols.class[name] = this.resolveExtends(meta, deepResolved.symbol, false, validateClassResolveExtends(meta, name, diagnostics, deepResolved));
break;
case `element`:
resolvedSymbols.element[name] = this.resolveExtends(meta, name, true);
break;
case `var`:
resolvedSymbols.var[name] = deepResolved;
break;
case `cssVar`:
resolvedSymbols.cssVar[name] = deepResolved;
break;
}
resolvedSymbols.mainNamespace[name] = deepResolved.symbol._kind;
}
// resolve keyframes
for (const [name, symbol] of Object.entries(features_1.CSSKeyframes.getAll(meta))) {
const result = resolveByNamespace(meta, symbol, this, 'keyframes');
if (result) {
resolvedSymbols.keyframes[name] = {
_kind: `css`,
meta: result.meta,
symbol: result.symbol,
};
}
}
// resolve layers
for (const [name, symbol] of Object.entries(features_1.CSSLayer.getAll(meta))) {
const result = resolveByNamespace(meta, symbol, this, 'layer');
if (result) {
resolvedSymbols.layer[name] = {
_kind: `css`,
meta: result.meta,
symbol: result.symbol,
};
}
}
// resolve containers
for (const [name, symbol] of Object.entries(features_1.CSSContains.getAll(meta))) {
const result = resolveByNamespace(meta, symbol, this, 'container');
if (result) {
resolvedSymbols.container[name] = {
_kind: `css`,
meta: result.meta,
symbol: result.symbol,
};
}
}
return resolvedSymbols;
}
resolveExtends(meta, nameOrSymbol, isElement = false, reportError) {
const name = typeof nameOrSymbol === `string` ? nameOrSymbol : nameOrSymbol.name;
const symbol = typeof nameOrSymbol === `string`
? isElement
? meta.getTypeElement(nameOrSymbol)
: meta.getClass(nameOrSymbol)
: nameOrSymbol;
if (!symbol) {
return [];
}
let current = {
_kind: 'css',
symbol,
meta,
};
const extendPath = [];
while (current?.symbol) {
if (isInPath(extendPath, current)) {
break;
}
extendPath.push(current);
const parent = current.symbol[`-st-extends`] || current.symbol.alias;
if (parent) {
if (parent._kind === 'import') {
const res = this.resolve(parent);
if (res &&
res._kind === 'css' &&
res.symbol &&
(res.symbol._kind === 'element' || res.symbol._kind === 'class')) {
const { _kind, meta, symbol } = res;
current = {
_kind,
meta,
symbol,
};
}
else {
if (reportError) {
reportError(res, parent, extendPath, meta, name, isElement);
}
break;
}
}
else {
current = { _kind: 'css', symbol: parent, meta: current.meta };
}
}
else {
break;
}
}
return extendPath;
}
}
exports.StylableResolver = StylableResolver;
function cacheKey(context, request) {
return `${context}${safePathDelimiter}${request}`;
}
function validateClassResolveExtends(meta, name, diagnostics, deepResolved) {
return (res, extend) => {
const decl = (0, rule_1.findRule)(meta.sourceAst, '.' + name);
if (decl) {
// ToDo: move to STExtends
if (res && res._kind === 'js') {
diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_JS(), {
node: decl,
word: decl.value,
});
}
else if (res && !res.symbol) {
diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), {
node: decl,
word: decl.value,
});
}
else {
diagnostics.report(features_1.CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE(), {
node: decl,
word: decl.value,
});
}
}
else {
if (deepResolved.symbol.alias) {
meta.sourceAst.walkRules(new RegExp('\\.' + name), (rule) => {
diagnostics.report(features_1.CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS(name), {
node: rule,
word: name,
});
return false;
});
}
}
};
}
function createSymbolResolverWithCache(resolver, diagnostics) {
const cache = new WeakMap();
return (meta) => {
let symbols = cache.get(meta);
if (!symbols) {
symbols = resolver.resolveSymbols(meta, diagnostics);
cache.set(meta, symbols);
}
return symbols;
};
}
exports.createSymbolResolverWithCache = createSymbolResolverWithCache;
function resolveByNamespace(meta, symbol, resolver, type) {
const current = { meta, symbol };
while ('import' in current.symbol && current.symbol.import) {
const res = resolver.resolveImported(current.symbol.import, current.symbol.name, type);
if (res?._kind === 'css' && res.symbol?._kind === type) {
({ meta: current.meta, symbol: current.symbol } = res);
}
else {
return undefined;
}
}
if (current.symbol?._kind === type) {
return current;
}
return undefined;
}
//# sourceMappingURL=stylable-resolver.js.map