import { createFeature, FeatureContext, FeatureTransformContext } from './feature'; import { generalDiagnostics } from './diagnostics'; import * as STSymbol from './st-symbol'; import type { StylableSymbol } from './st-symbol'; import type { ImportSymbol } from './st-import'; import type { ElementSymbol } from './css-type'; import type * as STStructure from './st-structure'; import * as STCustomState from './st-custom-state'; import { getOriginDefinition } from '../helpers/resolve'; import { namespace } from '../helpers/namespace'; import { namespaceEscape, unescapeCSS } from '../helpers/escape'; import { getNamedArgs } from '../helpers/value'; import { convertToClass, stringifySelector, isSimpleSelector, parseSelectorWithCache, convertToPseudoClass, convertToSelector, } from '../helpers/selector'; import { getAlias } from '../stylable-utils'; import type { StylableMeta } from '../stylable-meta'; import { validateRuleStateDefinition } from '../helpers/custom-state'; import type { Stylable } from '../stylable'; import { ImmutableClass, Class, SelectorNode, ImmutableSelectorNode, stringifySelectorAst, SelectorNodes, } from '@tokey/css-selector-parser'; import * as postcss from 'postcss'; import { basename } from 'path'; import { createDiagnosticReporter } from '../diagnostics'; import postcssValueParser from 'postcss-value-parser'; import { plugableRecord } from '../helpers/plugable-record'; export interface StPartDirectives extends STStructure.HasParts, Partial { '-st-root'?: boolean; '-st-extends'?: ImportSymbol | ClassSymbol | ElementSymbol; '-st-global'?: SelectorNode[]; } const stPartDirectives = { '-st-root': true, '-st-states': true, '-st-extends': true, '-st-global': true, } as const; export interface ClassSymbol extends StPartDirectives { _kind: 'class'; name: string; alias?: ImportSymbol; scoped?: string; // ToDo: check if in use } export const diagnostics = { INVALID_FUNCTIONAL_SELECTOR: generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR, UNSCOPED_CLASS: createDiagnosticReporter( '00002', 'warning', (name: string) => `unscoped class "${name}" will affect all elements of the same type in the document` ), STATE_DEFINITION_IN_ELEMENT: createDiagnosticReporter( '11002', 'error', () => 'cannot define pseudo states inside a type selector' ), STATE_DEFINITION_IN_COMPLEX: createDiagnosticReporter( '11003', 'error', () => 'cannot define pseudo states inside complex selectors' ), OVERRIDE_TYPED_RULE: createDiagnosticReporter( '11006', 'warning', (key: string, name: string) => `override "${key}" on typed rule "${name}"` ), CANNOT_RESOLVE_EXTEND: createDiagnosticReporter( '11004', 'error', (name: string) => `cannot resolve '-st-extends' type for '${name}'` ), CANNOT_EXTEND_IN_COMPLEX: createDiagnosticReporter( '11005', 'error', () => `cannot define "-st-extends" inside a complex selector` ), EMPTY_ST_GLOBAL: createDiagnosticReporter( '00003', 'error', () => `-st-global must contain a valid selector` ), UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL: createDiagnosticReporter( '00004', 'error', () => `unsupported multi selector in -st-global` ), UNSUPPORTED_COMPLEX_SELECTOR: createDiagnosticReporter( '00010', 'error', () => `unsupported complex selector` ), IMPORT_ISNT_EXTENDABLE: createDiagnosticReporter( '00005', 'error', () => 'import is not extendable' ), CANNOT_EXTEND_UNKNOWN_SYMBOL: createDiagnosticReporter( '00006', 'error', (name: string) => `cannot extend unknown symbol "${name}"` ), CANNOT_EXTEND_JS: createDiagnosticReporter( '00007', 'error', () => 'JS import is not extendable' ), UNKNOWN_IMPORT_ALIAS: createDiagnosticReporter( '00008', 'error', (name: string) => `cannot use alias for unknown import "${name}"` ), DISABLED_DIRECTIVE: createDiagnosticReporter( '00009', 'error', (className: string, directive: keyof typeof stPartDirectives) => { const alternative = directive === '-st-extends' ? ` use "@st .${className} :is(.base)" instead` : directive === '-st-global' ? `use "@st .${className} => :global()" instead` : directive === '-st-states' ? `use "@st .${className} { @st .state; }" instead` : ''; return `cannot use ${directive} on .${className} since class is defined with "@st" - ${alternative}`; } ), }; const dataKey = plugableRecord.key<{ classesDefinedWithAtSt: Set; }>('st-structure'); // HOOKS export const hooks = createFeature<{ SELECTOR: Class; IMMUTABLE_SELECTOR: ImmutableClass; RESOLVED: Record; }>({ metaInit({ meta }) { plugableRecord.set(meta.data, dataKey, { classesDefinedWithAtSt: new Set(), }); }, analyzeSelectorNode({ context, node, rule }): void { if (node.nodes) { // error on functional class context.diagnostics.report( diagnostics.INVALID_FUNCTIONAL_SELECTOR(`.` + node.value, `class`), { node: rule, word: stringifySelector(node), } ); } addClass(context, node.value, rule); }, analyzeDeclaration({ context, decl }) { if (context.meta.type === 'stylable' && isDirectiveDeclaration(decl)) { handleDirectives(context, decl); } }, transformResolve({ context }) { const resolvedSymbols = context.getResolvedSymbols(context.meta); const locals: Record = {}; for (const [localName, resolved] of Object.entries(resolvedSymbols.class)) { const exportedClasses = []; let first = true; // collect list of css classes for exports for (const { meta, symbol } of resolved) { if (!first && symbol[`-st-root`]) { // extended stylesheet root: stop collection as root is expected to // be placed by inner component, for example in