diff --git a/packages/cli/src/build-stylable.ts b/packages/cli/src/build-stylable.ts index c867154047..5c03d9a092 100644 --- a/packages/cli/src/build-stylable.ts +++ b/packages/cli/src/build-stylable.ts @@ -56,7 +56,7 @@ export async function buildStylable( watchOptions = {}, }: BuildStylableContext = {} ) { - const { config } = resolveConfig(rootDir, configFilePath, fs) || {}; + const { config } = resolveConfig(rootDir, fs, configFilePath) || {}; validateDefaultConfig(config?.defaultConfig); const projects = await projectsConfig(rootDir, overrideBuildOptions, defaultOptions, config); diff --git a/packages/cli/src/config/projects-config.ts b/packages/cli/src/config/projects-config.ts index 13a4933902..9cea0cfdb6 100644 --- a/packages/cli/src/config/projects-config.ts +++ b/packages/cli/src/config/projects-config.ts @@ -64,8 +64,7 @@ export async function projectsConfig( return projects; } -// todo: make fs not optional next major version -export function resolveConfig(context: string, request?: string, fs?: IFileSystem) { +export function resolveConfig(context: string, fs: IFileSystem, request?: string) { return request ? requireConfigFile(request, context, fs) : resolveConfigFile(context, fs); } diff --git a/packages/core/src/helpers/custom-state.ts b/packages/core/src/helpers/custom-state.ts index 1cd8ef0da3..e166de8773 100644 --- a/packages/core/src/helpers/custom-state.ts +++ b/packages/core/src/helpers/custom-state.ts @@ -4,7 +4,7 @@ import postcssValueParser, { type FunctionNode, } from 'postcss-value-parser'; import cssesc from 'cssesc'; -import type { PseudoClass, SelectorNode } from '@tokey/css-selector-parser'; +import type { PseudoClass, SelectorList, SelectorNode } from '@tokey/css-selector-parser'; import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; import { parseSelectorWithCache, @@ -338,7 +338,21 @@ function defineTemplateState( const template = stripQuotation(postcssValueParser.stringify(templateDef)); if (argsFullValue.length === 1) { // simple template with no params - mappedStates[stateName] = template.trim().replace(/\\["']/g, '"'); + const selectorStr = template.trim().replace(/\\["']/g, '"'); + const selectorAst = parseSelectorWithCache(selectorStr, { clone: true }); + if ( + !validateTemplateSelector({ + stateName, + selectorStr, + selectorAst, + cssNode: decl, + diagnostics, + }) + ) { + return; + } else { + mappedStates[stateName] = selectorStr; + } } else if (argsFullValue.length === 2) { // single parameter template if (!template.includes('$0')) { @@ -1075,30 +1089,57 @@ function transformMappedStateWithParam({ selectorNode?: postcss.Node; diagnostics: Diagnostics; }) { - const targetSelectorStr = template.replace(/\$0/g, param); - const selectorAst = parseSelectorWithCache(targetSelectorStr, { clone: true }); + const selectorStr = template.replace(/\$0/g, param); + const selectorAst = parseSelectorWithCache(selectorStr, { clone: true }); + if ( + !validateTemplateSelector({ + stateName, + selectorStr, + selectorAst, + cssNode: selectorNode, + diagnostics, + }) + ) { + return; + } + convertToSelector(node).nodes = selectorAst[0].nodes; +} + +function validateTemplateSelector({ + stateName, + selectorStr, + selectorAst, + cssNode, + diagnostics, +}: { + stateName: string; + selectorStr: string; + selectorAst: SelectorList; + cssNode?: postcss.Node; + diagnostics: Diagnostics; +}): boolean { if (selectorAst.length > 1) { - if (selectorNode) { + if (cssNode) { diagnostics.report( - stateDiagnostics.UNSUPPORTED_MULTI_SELECTOR(stateName, targetSelectorStr), + stateDiagnostics.UNSUPPORTED_MULTI_SELECTOR(stateName, selectorStr), { - node: selectorNode, + node: cssNode, } ); } - return; + return false; } else { const firstSelector = selectorAst[0].nodes.find(({ type }) => type !== 'comment'); if (firstSelector?.type === 'type' || firstSelector?.type === 'universal') { - if (selectorNode) { + if (cssNode) { diagnostics.report( - stateDiagnostics.UNSUPPORTED_INITIAL_SELECTOR(stateName, targetSelectorStr), + stateDiagnostics.UNSUPPORTED_INITIAL_SELECTOR(stateName, selectorStr), { - node: selectorNode, + node: cssNode, } ); } - return; + return false; } let unexpectedSelector: undefined | SelectorNode = undefined; for (const node of selectorAst[0].nodes) { @@ -1108,33 +1149,30 @@ function transformMappedStateWithParam({ } } if (unexpectedSelector) { - if (selectorNode) { + if (cssNode) { switch (unexpectedSelector.type) { case 'combinator': diagnostics.report( - stateDiagnostics.UNSUPPORTED_COMPLEX_SELECTOR( - stateName, - targetSelectorStr - ), + stateDiagnostics.UNSUPPORTED_COMPLEX_SELECTOR(stateName, selectorStr), { - node: selectorNode, + node: cssNode, } ); break; case 'invalid': diagnostics.report( - stateDiagnostics.INVALID_SELECTOR(stateName, targetSelectorStr), + stateDiagnostics.INVALID_SELECTOR(stateName, selectorStr), { - node: selectorNode, + node: cssNode, } ); break; } } - return; + return false; } } - convertToSelector(node).nodes = selectorAst[0].nodes; + return true; } function resolveParam( diff --git a/packages/core/src/stylable-resolver.ts b/packages/core/src/stylable-resolver.ts index cb6a2aefa5..40605c813e 100644 --- a/packages/core/src/stylable-resolver.ts +++ b/packages/core/src/stylable-resolver.ts @@ -9,7 +9,6 @@ import { StylableSymbol, CSSClass, STSymbol, - STCustomSelector, VarSymbol, CSSVarSymbol, KeyframesSymbol, @@ -20,7 +19,6 @@ import { CSSContains, STStructure, } from './features'; -import type { StylableTransformer } from './stylable-transformer'; import { findRule } from './helpers/rule'; import type { ModuleResolver } from './types'; import { CustomValueExtension, isCustomValue, stTypes } from './custom-values'; @@ -385,7 +383,6 @@ export class StylableResolver { meta, deepResolved.symbol!, false, - undefined, validateClassResolveExtends( meta, name, @@ -445,7 +442,6 @@ export class StylableResolver { meta: StylableMeta, nameOrSymbol: string | ClassSymbol | ElementSymbol, isElement = false, - transformer?: StylableTransformer, reportError?: ReportError ): CSSResolvePath { const name = typeof nameOrSymbol === `string` ? nameOrSymbol : nameOrSymbol.name; @@ -456,23 +452,6 @@ export class StylableResolver { : meta.getClass(nameOrSymbol) : nameOrSymbol; - const customSelector = isElement - ? null - : STCustomSelector.getCustomSelectorExpended(meta, name); - - if (!symbol && !customSelector) { - return []; - } - - if (customSelector && transformer) { - const parsed = transformer.resolveSelectorElements(meta, customSelector); - if (parsed.length === 1) { - return parsed[0][parsed[0].length - 1].resolved; - } else { - return []; - } - } - if (!symbol) { return []; } diff --git a/packages/core/test/features/css-pseudo-class.spec.ts b/packages/core/test/features/css-pseudo-class.spec.ts index 299335807a..a57ab0e63b 100644 --- a/packages/core/test/features/css-pseudo-class.spec.ts +++ b/packages/core/test/features/css-pseudo-class.spec.ts @@ -459,6 +459,50 @@ describe('features/css-pseudo-class', () => { .root:static(unknown-param) {} `); }); + it('should report invalid template selector', () => { + testStylableCore(` + .a { + /* + @analyze-error(not-compound) ${stCustomStateDiagnostics.UNSUPPORTED_COMPLEX_SELECTOR( + 'notCompound', + '.x .y' + )} + */ + -st-states: notCompound(".x .y"); + } + .b { + /* + @analyze-error(multi) ${stCustomStateDiagnostics.UNSUPPORTED_MULTI_SELECTOR( + 'multi', + '.x, .y' + )} + */ + -st-states: multi(".x, .y"); + } + .c { + /* + @analyze-error(invalid) ${stCustomStateDiagnostics.INVALID_SELECTOR( + 'invalid', + ':unclosed(' + )} + */ + -st-states: invalid(":unclosed("); + } + .d { + /* + @analyze-error(invalidStart) ${stCustomStateDiagnostics.UNSUPPORTED_INITIAL_SELECTOR( + 'invalidStartElement', + 'div.x' + )} + @analyze-error(invalidStart) ${stCustomStateDiagnostics.UNSUPPORTED_INITIAL_SELECTOR( + 'invalidStartWildcard', + '*.x' + )} + */ + -st-states: invalidStartElement("div.x"), invalidStartWildcard("*.x"); + } + `); + }); }); describe('custom mapped parameter', () => { it('should transform mapped state (quoted)', () => { @@ -517,12 +561,6 @@ describe('features/css-pseudo-class', () => { shouldReportNoDiagnostics(meta); }); it('should report invalid template selector', () => { - /** - * currently only checks template with parameter - * for backwards compatibility standalone template can accept - * any kind of selector - we might want to limit this in a future - * major version. - */ testStylableCore(` .root { -st-states: classAndThenParam(".x$0", string), diff --git a/packages/esbuild/src/stylable-esbuild-plugin.ts b/packages/esbuild/src/stylable-esbuild-plugin.ts index 70ff8ec826..d55f626316 100644 --- a/packages/esbuild/src/stylable-esbuild-plugin.ts +++ b/packages/esbuild/src/stylable-esbuild-plugin.ts @@ -110,8 +110,8 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi const projectRoot = build.initialOptions.absWorkingDir || process.cwd(); const configFromFile = resolveConfig( projectRoot, - typeof configFile === 'string' ? configFile : undefined, - fs + fs, + typeof configFile === 'string' ? configFile : undefined ); const stConfig = stylableConfig( { diff --git a/packages/rollup-plugin/src/index.ts b/packages/rollup-plugin/src/index.ts index 617cab041c..70e2220779 100644 --- a/packages/rollup-plugin/src/index.ts +++ b/packages/rollup-plugin/src/index.ts @@ -123,8 +123,8 @@ export function stylableRollupPlugin({ }); configFromFile = resolveStcConfig( stConfig.projectRoot, - typeof stcConfig === 'string' ? stcConfig : undefined, - fs + fs, + typeof stcConfig === 'string' ? stcConfig : undefined ); stylable = new Stylable({ diff --git a/packages/schema-extract/test/test.spec.ts b/packages/schema-extract/test/test.spec.ts index 53479fe96a..949f646a05 100644 --- a/packages/schema-extract/test/test.spec.ts +++ b/packages/schema-extract/test/test.spec.ts @@ -259,7 +259,7 @@ describe('Stylable JSON Schema Extractor', () => { it('schema with mapped states', () => { const css = `.root{ - -st-states: state("custom"); + -st-states: state(".custom"); }`; const res = extractSchema(css, '/entry.st.css', '/', path); diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index 3664d375f6..46cf3c595b 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -330,8 +330,8 @@ export class StylableWebpackPlugin { private getStylableConfig(compiler: Compiler) { const configuration = resolveStcConfig( compiler.context, - typeof this.options.stcConfig === 'string' ? this.options.stcConfig : undefined, - getTopLevelInputFilesystem(compiler) + getTopLevelInputFilesystem(compiler), + typeof this.options.stcConfig === 'string' ? this.options.stcConfig : undefined ); return configuration; @@ -359,7 +359,10 @@ export class StylableWebpackPlugin { return; } - const resolverOptions: Omit = { + const resolverOptions: Omit< + ResolveOptionsWebpackOptions, + 'fileSystem' | 'resolver' | 'plugins' + > = { ...compiler.options.resolve, aliasFields: compiler.options.resolve.byDependency?.esm?.aliasFields ||