From 5553e9037b4d96741616bfb089c35d48f6a204ea Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Tue, 21 Dec 2021 13:41:22 +0200 Subject: [PATCH 01/20] empty `css-custom-property` feature file to open the PR --- packages/core/src/features/css-custom-property.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/core/src/features/css-custom-property.ts diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts new file mode 100644 index 000000000..e69de29bb From c249470247e013930ca56bd8c59ac6f67eb25800 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 10:38:26 +0200 Subject: [PATCH 02/20] chore: init `css-custom-property` feature - feature file `/features/css-custom-property.ts` - moved `CSSVarSymbol` symbol - export empty diagnostics and hooks - register to stylable meta class --- packages/core/src/features/css-custom-property.ts | 12 ++++++++++++ packages/core/src/features/index.ts | 5 ++++- packages/core/src/features/st-symbol.ts | 3 ++- packages/core/src/features/types.ts | 6 ------ packages/core/src/stylable-meta.ts | 12 ++++++++++-- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index e69de29bb..f6c772588 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -0,0 +1,12 @@ +import { createFeature } from './feature'; +export interface CSSVarSymbol { + _kind: 'cssVar'; + name: string; + global: boolean; +} + +export const diagnostics = {}; + +// HOOKS + +export const hooks = createFeature({}); diff --git a/packages/core/src/features/index.ts b/packages/core/src/features/index.ts index 23218075a..c61afd331 100644 --- a/packages/core/src/features/index.ts +++ b/packages/core/src/features/index.ts @@ -14,7 +14,10 @@ export type { ClassSymbol } from './css-class'; export * as CSSType from './css-type'; export type { ElementSymbol } from './css-type'; +export * as CSSCustomProperty from './css-custom-property'; +export type { CSSVarSymbol } from './css-custom-property'; + export * as CSSKeyframes from './css-keyframes'; export type { KeyframesSymbol } from './css-keyframes'; -export type { CSSVarSymbol, RefedMixin, StylableDirectives, VarSymbol } from './types'; +export type { RefedMixin, StylableDirectives, VarSymbol } from './types'; diff --git a/packages/core/src/features/st-symbol.ts b/packages/core/src/features/st-symbol.ts index 633d58b1a..f3b8a44c6 100644 --- a/packages/core/src/features/st-symbol.ts +++ b/packages/core/src/features/st-symbol.ts @@ -1,8 +1,9 @@ import { FeatureContext, createFeature } from './feature'; -import type { VarSymbol, CSSVarSymbol } from './types'; +import type { VarSymbol } from './types'; import type { ImportSymbol } from './st-import'; import type { ClassSymbol } from './css-class'; import type { ElementSymbol } from './css-type'; +import type { CSSVarSymbol } from './css-custom-property'; import type { KeyframesSymbol } from './css-keyframes'; import { plugableRecord } from '../helpers/plugable-record'; import { ignoreDeprecationWarn } from '../helpers/deprecation'; diff --git a/packages/core/src/features/types.ts b/packages/core/src/features/types.ts index 93a217c57..606ce306b 100644 --- a/packages/core/src/features/types.ts +++ b/packages/core/src/features/types.ts @@ -27,9 +27,3 @@ export interface VarSymbol { valueType: string | null; node: postcss.Node; } - -export interface CSSVarSymbol { - _kind: 'cssVar'; - name: string; - global: boolean; -} diff --git a/packages/core/src/stylable-meta.ts b/packages/core/src/stylable-meta.ts index 5a2688007..0c2edff71 100644 --- a/packages/core/src/stylable-meta.ts +++ b/packages/core/src/stylable-meta.ts @@ -16,11 +16,19 @@ import type { PlugableRecord } from './helpers/plugable-record'; import { getSourcePath } from './stylable-utils'; import { setFieldForDeprecation } from './helpers/deprecation'; import { valueMapping } from './stylable-value-parsers'; -import { STSymbol, STImport, STGlobal, CSSClass, CSSType, CSSKeyframes } from './features'; +import { + STSymbol, + STImport, + STGlobal, + CSSClass, + CSSType, + CSSCustomProperty, + CSSKeyframes, +} from './features'; export const RESERVED_ROOT_NAME = 'root'; -const features = [STSymbol, STImport, STGlobal, CSSClass, CSSType, CSSKeyframes]; +const features = [STSymbol, STImport, STGlobal, CSSClass, CSSType, CSSCustomProperty, CSSKeyframes]; export class StylableMeta { public data: PlugableRecord = {}; From 498c3a8994b3d1be2d03cd0fc0ab5a5718b46b5c Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 10:52:58 +0200 Subject: [PATCH 03/20] refactor: `validate-at-property.ts` -> `helpers/css-custom-property.ts` --- .../css-custom-property.ts} | 4 ++-- packages/core/src/stylable-processor.ts | 2 +- packages/core/test/stylable-transformer/at-property.spec.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename packages/core/src/{validate-at-property.ts => helpers/css-custom-property.ts} (97%) diff --git a/packages/core/src/validate-at-property.ts b/packages/core/src/helpers/css-custom-property.ts similarity index 97% rename from packages/core/src/validate-at-property.ts rename to packages/core/src/helpers/css-custom-property.ts index d45551b06..ff08b6a22 100644 --- a/packages/core/src/validate-at-property.ts +++ b/packages/core/src/helpers/css-custom-property.ts @@ -1,6 +1,6 @@ import type * as postcss from 'postcss'; -import type { Diagnostics } from './diagnostics'; -import { stripQuotation } from './utils'; +import type { Diagnostics } from '../diagnostics'; +import { stripQuotation } from '../utils'; const UNIVERSAL_SYNTAX_DEFINITION = '*'; const AT_PROPERTY_DISCRIPTOR_LIST = ['initial-value', 'syntax', 'inherits']; diff --git a/packages/core/src/stylable-processor.ts b/packages/core/src/stylable-processor.ts index 2b40d89d8..3842798ed 100644 --- a/packages/core/src/stylable-processor.ts +++ b/packages/core/src/stylable-processor.ts @@ -45,7 +45,7 @@ import { } from './stylable-value-parsers'; import { deprecated, filename2varname, globalValue, stripQuotation } from './utils'; import { ignoreDeprecationWarn } from './helpers/deprecation'; -import { validateAtProperty } from './validate-at-property'; +import { validateAtProperty } from './helpers/css-custom-property'; const parseStates = SBTypesParsers[valueMapping.states]; const parseGlobal = SBTypesParsers[valueMapping.global]; diff --git a/packages/core/test/stylable-transformer/at-property.spec.ts b/packages/core/test/stylable-transformer/at-property.spec.ts index 86c6ea00f..e9baa0b48 100644 --- a/packages/core/test/stylable-transformer/at-property.spec.ts +++ b/packages/core/test/stylable-transformer/at-property.spec.ts @@ -1,7 +1,7 @@ import { expectTransformDiagnostics, generateStylableResult } from '@stylable/core-test-kit'; import { expect } from 'chai'; import type * as postcss from 'postcss'; -import { atPropertyValidationWarnings } from '@stylable/core/dist/validate-at-property'; +import { atPropertyValidationWarnings } from '@stylable/core/dist/helpers/css-custom-property'; import { STSymbol } from '@stylable/core/dist/features'; describe('@property support', () => { From 30672655a18ea775befd16f427d2b9262c89f495 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 11:49:03 +0200 Subject: [PATCH 04/20] refactor: processor `css-custom-property` feature extract - moved all code related to feature file - new `analyzeDeclaration` feature hook --- .../core/src/features/css-custom-property.ts | 104 +++++++++++++++++- packages/core/src/features/feature.ts | 4 + packages/core/src/stylable-processor.ts | 91 +-------------- packages/core/test/css-vars.spec.ts | 10 +- 4 files changed, 116 insertions(+), 93 deletions(-) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index f6c772588..8a0176e2b 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -1,12 +1,110 @@ -import { createFeature } from './feature'; +import { validateAtProperty } from '../helpers/css-custom-property'; +import { createFeature, FeatureContext } from './feature'; +import * as STSymbol from './st-symbol'; +import type * as postcss from 'postcss'; +// ToDo: refactor out +import postcssValueParser from 'postcss-value-parser'; +// ToDo: move to helpers +import { globalValue } from '../utils'; +import { isCSSVarProp } from '../stylable-utils'; +import { validateAllowedNodesUntil } from '../stylable-value-parsers'; + export interface CSSVarSymbol { _kind: 'cssVar'; name: string; global: boolean; } -export const diagnostics = {}; +export const diagnostics = { + ILLEGAL_CSS_VAR_USE(name: string) { + return `a custom css property must begin with "--" (double-dash), but received "${name}"`; + }, + ILLEGAL_CSS_VAR_ARGS(name: string) { + return `custom property "${name}" usage (var()) must receive comma separated values`; + }, +}; // HOOKS -export const hooks = createFeature({}); +export const hooks = createFeature({ + analyzeAtRule({ context, atRule }) { + addCSSVarDefinition(context, atRule); + validateAtProperty(atRule, context.diagnostics); + }, + analyzeDeclaration({ context, decl }) { + if (isCSSVarProp(decl.prop)) { + addCSSVarDefinition(context, decl); + } + if (decl.value.includes('var(')) { + handleCSSVarUse(context, decl); + } + }, +}); + +function handleCSSVarUse(context: FeatureContext, decl: postcss.Declaration) { + const parsed = postcssValueParser(decl.value); + parsed.walk((node) => { + if (node.type === 'function' && node.value === 'var' && node.nodes) { + const varName = node.nodes[0]; + if (!validateAllowedNodesUntil(node, 1)) { + const args = postcssValueParser.stringify(node.nodes); + context.diagnostics.warn(decl, diagnostics.ILLEGAL_CSS_VAR_ARGS(args), { + word: args, + }); + } + + addCSSVar(context, postcssValueParser.stringify(varName).trim(), decl, false); + } + }); +} + +function addCSSVarDefinition(context: FeatureContext, node: postcss.Declaration | postcss.AtRule) { + let varName = node.type === 'atrule' ? node.params.trim() : node.prop.trim(); + let isGlobal = false; + + const globalVarName = globalValue(varName); + + if (globalVarName !== undefined) { + varName = globalVarName.trim(); + isGlobal = true; + } + + if (node.type === 'atrule' && STSymbol.get(context.meta, varName)) { + context.diagnostics.warn(node, STSymbol.diagnostics.REDECLARE_SYMBOL(varName), { + word: varName, + }); + } + + addCSSVar(context, varName, node, isGlobal); +} + +function addCSSVar( + context: FeatureContext, + varName: string, + node: postcss.Declaration | postcss.AtRule, + global: boolean +) { + if (isCSSVarProp(varName)) { + if (!context.meta.cssVars[varName]) { + const cssVarSymbol: CSSVarSymbol = { + _kind: 'cssVar', + name: varName, + global, + }; + context.meta.cssVars[varName] = cssVarSymbol; + const prevSymbol = STSymbol.get(context.meta, varName); + const override = node.type === `atrule` || !prevSymbol; + if (override) { + STSymbol.addSymbol({ + context, + symbol: cssVarSymbol, + safeRedeclare: true, + }); + } + } + } else { + context.diagnostics.warn(node, diagnostics.ILLEGAL_CSS_VAR_USE(varName), { + word: varName, + }); + } +} diff --git a/packages/core/src/features/feature.ts b/packages/core/src/features/feature.ts index ee6390211..d84d76d5e 100644 --- a/packages/core/src/features/feature.ts +++ b/packages/core/src/features/feature.ts @@ -35,6 +35,7 @@ export interface FeatureHooks { rule: postcss.Rule; walkContext: SelectorNodeContext; }) => void; + analyzeDeclaration: (options: { context: FeatureContext; decl: postcss.Declaration }) => void; transformInit: (options: { context: FeatureTransformContext }) => void; transformResolve: (options: { context: FeatureTransformContext }) => T['RESOLVED']; transformAtRuleNode: (options: { @@ -67,6 +68,9 @@ const defaultHooks: FeatureHooks = { analyzeSelectorNode() { /**/ }, + analyzeDeclaration() { + /**/ + }, transformInit() { /**/ }, diff --git a/packages/core/src/stylable-processor.ts b/packages/core/src/stylable-processor.ts index 3842798ed..8a4026354 100644 --- a/packages/core/src/stylable-processor.ts +++ b/packages/core/src/stylable-processor.ts @@ -1,14 +1,14 @@ import path from 'path'; import * as postcss from 'postcss'; -import postcssValueParser from 'postcss-value-parser'; import { deprecatedStFunctions } from './custom-values'; import { Diagnostics } from './diagnostics'; import { parseSelector as deprecatedParseSelector } from './deprecated/deprecated-selector-utils'; import { murmurhash3_32_gc } from './murmurhash'; import { knownPseudoClassesWithNestedSelectors } from './native-reserved-lists'; import { StylableMeta } from './stylable-meta'; -import type { +import { ClassSymbol, + CSSCustomProperty, CSSVarSymbol, ElementSymbol, RefedMixin, @@ -40,12 +40,10 @@ import { rootValueMapping, SBTypesParsers, stValuesMap, - validateAllowedNodesUntil, valueMapping, } from './stylable-value-parsers'; -import { deprecated, filename2varname, globalValue, stripQuotation } from './utils'; +import { deprecated, filename2varname, stripQuotation } from './utils'; import { ignoreDeprecationWarn } from './helpers/deprecation'; -import { validateAtProperty } from './helpers/css-custom-property'; const parseStates = SBTypesParsers[valueMapping.states]; const parseGlobal = SBTypesParsers[valueMapping.global]; @@ -97,12 +95,6 @@ export const processorWarnings = { GLOBAL_CSS_VAR_MISSING_COMMA(name: string) { return `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`; }, - ILLEGAL_CSS_VAR_USE(name: string) { - return `a custom css property must begin with "--" (double-dash), but received "${name}"`; - }, - ILLEGAL_CSS_VAR_ARGS(name: string) { - return `custom property "${name}" usage (var()) must receive comma separated values`; - }, INVALID_NAMESPACE_REFERENCE() { return 'st-namespace-reference dose not have any value'; }, @@ -153,13 +145,8 @@ export class StylableProcessor implements FeatureContext { root.walkDecls((decl) => { if (stValuesMap[decl.prop]) { this.handleDirectives(decl.parent as SRule, decl); - } else if (isCSSVarProp(decl.prop)) { - this.addCSSVarDefinition(decl); - } - - if (decl.value.includes('var(')) { - this.handleCSSVarUse(decl); } + CSSCustomProperty.hooks.analyzeDeclaration({ context: this, decl }); this.collectUrls(decl); }); @@ -238,8 +225,7 @@ export class StylableProcessor implements FeatureContext { this.meta.scopes.push(atRule); break; case 'property': { - this.addCSSVarDefinition(atRule); - validateAtProperty(atRule, this.diagnostics); + CSSCustomProperty.hooks.analyzeAtRule({ context: this, atRule }); break; } case 'st-global-custom-property': { @@ -518,73 +504,6 @@ export class StylableProcessor implements FeatureContext { rule.remove(); } - protected handleCSSVarUse(decl: postcss.Declaration) { - const parsed = postcssValueParser(decl.value); - parsed.walk((node) => { - if (node.type === 'function' && node.value === 'var' && node.nodes) { - const varName = node.nodes[0]; - if (!validateAllowedNodesUntil(node, 1)) { - const args = postcssValueParser.stringify(node.nodes); - this.diagnostics.warn(decl, processorWarnings.ILLEGAL_CSS_VAR_ARGS(args), { - word: args, - }); - } - - this.addCSSVar(postcssValueParser.stringify(varName).trim(), decl, false); - } - }); - } - - protected addCSSVarDefinition(node: postcss.Declaration | postcss.AtRule) { - let varName = node.type === 'atrule' ? node.params.trim() : node.prop.trim(); - let isGlobal = false; - - const globalVarName = globalValue(varName); - - if (globalVarName !== undefined) { - varName = globalVarName.trim(); - isGlobal = true; - } - - if (node.type === 'atrule' && STSymbol.get(this.meta, varName)) { - this.diagnostics.warn(node, STSymbol.diagnostics.REDECLARE_SYMBOL(varName), { - word: varName, - }); - } - - this.addCSSVar(varName, node, isGlobal); - } - - protected addCSSVar( - varName: string, - node: postcss.Declaration | postcss.AtRule, - global: boolean - ) { - if (isCSSVarProp(varName)) { - if (!this.meta.cssVars[varName]) { - const cssVarSymbol: CSSVarSymbol = { - _kind: 'cssVar', - name: varName, - global, - }; - this.meta.cssVars[varName] = cssVarSymbol; - const prevSymbol = STSymbol.get(this.meta, varName); - const override = node.type === `atrule` || !prevSymbol; - if (override) { - STSymbol.addSymbol({ - context: this, - symbol: cssVarSymbol, - safeRedeclare: true, - }); - } - } - } else { - this.diagnostics.warn(node, processorWarnings.ILLEGAL_CSS_VAR_USE(varName), { - word: varName, - }); - } - } - protected handleDirectives(rule: SRule, decl: postcss.Declaration) { const isSimplePerSelector = isSimpleSelector(rule.selector); const type = isSimplePerSelector.reduce((accType, { type }) => { diff --git a/packages/core/test/css-vars.spec.ts b/packages/core/test/css-vars.spec.ts index 0b65837af..5db829183 100644 --- a/packages/core/test/css-vars.spec.ts +++ b/packages/core/test/css-vars.spec.ts @@ -6,7 +6,7 @@ import { processSource, } from '@stylable/core-test-kit'; import { processorWarnings } from '@stylable/core'; -import { STSymbol, STImport } from '@stylable/core/dist/features'; +import { STSymbol, STImport, CSSCustomProperty } from '@stylable/core/dist/features'; describe('css custom-properties (vars)', () => { describe('process', () => { @@ -860,7 +860,7 @@ describe('css custom-properties (vars)', () => { const res = expectTransformDiagnostics(config, [ { - message: processorWarnings.ILLEGAL_CSS_VAR_USE('illegalVar'), + message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE('illegalVar'), file: '/entry.st.css', }, ]); @@ -886,7 +886,9 @@ describe('css custom-properties (vars)', () => { const res = expectTransformDiagnostics(config, [ { - message: processorWarnings.ILLEGAL_CSS_VAR_ARGS('--value illegalHere, red'), + message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_ARGS( + '--value illegalHere, red' + ), file: '/entry.st.css', }, ]); @@ -942,7 +944,7 @@ describe('css custom-properties (vars)', () => { expectTransformDiagnostics(config, [ { - message: processorWarnings.ILLEGAL_CSS_VAR_USE('illegalVar'), + message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE('illegalVar'), file: '/entry.st.css', }, ]); From 4deced99396f1a5e9ee458ac9b7f27fedb454c37 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 12:37:56 +0200 Subject: [PATCH 05/20] refactor: extract helpers - `isCSSVarProp` from `stylable-utils` to `helpers/css-custom-property` - `validateAllowedNodesUntil` from `stylable-value-parser` to `helpers/value` - `globalValue` from `utils` to `helpers/global` - deprecate `paramMapping` constant export --- ...t-global-custom-property-to-at-property.ts | 11 +++----- .../core/src/features/css-custom-property.ts | 9 +++---- packages/core/src/features/css-keyframes.ts | 5 ++-- packages/core/src/features/st-import.ts | 2 +- packages/core/src/functions.ts | 2 +- .../core/src/helpers/css-custom-property.ts | 4 +++ packages/core/src/helpers/global.ts | 9 +++++++ packages/core/src/helpers/value.ts | 19 +++++++++++++ packages/core/src/index.ts | 13 ++++++--- packages/core/src/stylable-processor.ts | 12 +++------ packages/core/src/stylable-transformer.ts | 3 ++- packages/core/src/stylable-utils.ts | 4 --- packages/core/src/stylable-value-parsers.ts | 27 +------------------ packages/core/src/utils.ts | 7 ----- 14 files changed, 58 insertions(+), 69 deletions(-) create mode 100644 packages/core/src/helpers/global.ts diff --git a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts index accf67166..d5d3e7efe 100644 --- a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts +++ b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts @@ -1,10 +1,5 @@ -import { - CSSVarSymbol, - Diagnostics, - isCSSVarProp, - paramMapping, - processorWarnings, -} from '@stylable/core'; +import { CSSVarSymbol, Diagnostics, isCSSVarProp, processorWarnings } from '@stylable/core'; +import { GLOBAL_FUNC } from '@stylable/core/dist/helpers/global'; import type { AtRule } from 'postcss'; import type { CodeMod } from './types'; @@ -18,7 +13,7 @@ export const stGlobalCustomPropertyToAtProperty: CodeMod = ({ ast, diagnostics, atRule.before( postcss.atRule({ name: 'property', - params: `${paramMapping.global}(${property.name})`, + params: `${GLOBAL_FUNC}(${property.name})`, }) ); } diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index 8a0176e2b..a1955c47e 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -1,14 +1,11 @@ -import { validateAtProperty } from '../helpers/css-custom-property'; import { createFeature, FeatureContext } from './feature'; import * as STSymbol from './st-symbol'; +import { validateAtProperty, isCSSVarProp } from '../helpers/css-custom-property'; +import { validateAllowedNodesUntil } from '../helpers/value'; +import { globalValue } from '../helpers/global'; import type * as postcss from 'postcss'; // ToDo: refactor out import postcssValueParser from 'postcss-value-parser'; -// ToDo: move to helpers -import { globalValue } from '../utils'; -import { isCSSVarProp } from '../stylable-utils'; -import { validateAllowedNodesUntil } from '../stylable-value-parsers'; - export interface CSSVarSymbol { _kind: 'cssVar'; name: string; diff --git a/packages/core/src/features/css-keyframes.ts b/packages/core/src/features/css-keyframes.ts index c840431c0..088dae434 100644 --- a/packages/core/src/features/css-keyframes.ts +++ b/packages/core/src/features/css-keyframes.ts @@ -8,8 +8,7 @@ import { plugableRecord } from '../helpers/plugable-record'; import { ignoreDeprecationWarn } from '../helpers/deprecation'; import { isInConditionalGroup } from '../helpers/rule'; import { namespace } from '../helpers/namespace'; -import { paramMapping } from '../stylable-value-parsers'; -import { globalValue } from '../utils'; +import { globalValue, GLOBAL_FUNC } from '../helpers/global'; import type * as postcss from 'postcss'; import postcssValueParser from 'postcss-value-parser'; @@ -65,7 +64,7 @@ export const diagnostics = { return '"@keyframes" missing parameter'; }, MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL() { - return `"@keyframes" missing parameter inside "${paramMapping.global}()"`; + return `"@keyframes" missing parameter inside "${GLOBAL_FUNC}()"`; }, KEYFRAME_NAME_RESERVED(name: string) { return `keyframes "${name}" is reserved`; diff --git a/packages/core/src/features/st-import.ts b/packages/core/src/features/st-import.ts index 77c842fab..737debd0c 100644 --- a/packages/core/src/features/st-import.ts +++ b/packages/core/src/features/st-import.ts @@ -5,7 +5,7 @@ import type { StylableSymbol } from './st-symbol'; import { plugableRecord } from '../helpers/plugable-record'; import { ignoreDeprecationWarn } from '../helpers/deprecation'; import { parseStImport, parsePseudoImport, parseImportMessages } from '../helpers/import'; -import { isCSSVarProp } from '../stylable-utils'; +import { isCSSVarProp } from '../helpers/css-custom-property'; import type { StylableMeta } from '../stylable-meta'; import { rootValueMapping, valueMapping } from '../stylable-value-parsers'; import path from 'path'; diff --git a/packages/core/src/functions.ts b/packages/core/src/functions.ts index d3dbf8f46..b789dfaec 100644 --- a/packages/core/src/functions.ts +++ b/packages/core/src/functions.ts @@ -8,7 +8,7 @@ import { assureRelativeUrlPrefix } from './stylable-assets'; import type { StylableMeta } from './stylable-meta'; import type { CSSResolve, JSResolve, StylableResolver } from './stylable-resolver'; import type { replaceValueHook, StylableTransformer } from './stylable-transformer'; -import { isCSSVarProp } from './stylable-utils'; +import { isCSSVarProp } from './helpers/css-custom-property'; import { strategies, valueMapping } from './stylable-value-parsers'; import { getFormatterArgs, getStringValue } from './helpers/value'; import type { ParsedValue } from './types'; diff --git a/packages/core/src/helpers/css-custom-property.ts b/packages/core/src/helpers/css-custom-property.ts index ff08b6a22..318826248 100644 --- a/packages/core/src/helpers/css-custom-property.ts +++ b/packages/core/src/helpers/css-custom-property.ts @@ -110,3 +110,7 @@ export function validateAtProperty( valid: true, }; } + +export function isCSSVarProp(value: string) { + return value.startsWith('--'); +} diff --git a/packages/core/src/helpers/global.ts b/packages/core/src/helpers/global.ts new file mode 100644 index 000000000..0d231b172 --- /dev/null +++ b/packages/core/src/helpers/global.ts @@ -0,0 +1,9 @@ +export const PROPERTY = `-st-global` as const; +export const GLOBAL_FUNC = 'st-global' as const; + +export const globalValueRegExp = new RegExp(`^${GLOBAL_FUNC}\\((.*?)\\)$`); + +export function globalValue(str: string) { + const match = str.match(globalValueRegExp); + return match?.[1]; +} diff --git a/packages/core/src/helpers/value.ts b/packages/core/src/helpers/value.ts index f68e1f9fa..f1769c830 100644 --- a/packages/core/src/helpers/value.ts +++ b/packages/core/src/helpers/value.ts @@ -124,3 +124,22 @@ export function groupValues(nodes: ValueNode[], divType = 'div') { } return grouped; } + +export function validateAllowedNodesUntil( + node: ParsedValue, + i: number, + untilType = 'div', + allowed = ['comment'] +) { + i = 1; + let current = node.nodes[i]; + while (current && current.type !== untilType) { + if (!allowed.includes(current.type)) { + return false; + } + i++; + current = node.nodes[i]; + } + + return true; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index baebb4d8e..d3375e5b1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -29,6 +29,14 @@ export { } from './stylable-processor'; export { ensureStylableImports, parseStylableImport } from './helpers/import'; +export { isCSSVarProp } from './helpers/css-custom-property'; +export { globalValueRegExp } from './helpers/global'; + +import { GLOBAL_FUNC } from './helpers/global'; +/**@deprecated*/ +export const paramMapping = { + global: GLOBAL_FUNC, +}; export { StylableMeta, RESERVED_ROOT_NAME } from './stylable-meta'; export { @@ -50,7 +58,6 @@ export { generateScopedCSSVar, getAlias, getSourcePath, - isCSSVarProp, isValidClassName, isValidDeclaration, mergeRules, @@ -78,15 +85,12 @@ export { STYLABLE_VALUE_MATCHER, TypedClass, animationPropRegExp, - globalValueRegExp, mixinDeclRegExp, - paramMapping, rootValueMapping, stKeys, stValues, stValuesMap, strategies, - validateAllowedNodesUntil, valueMapping, valueParserWarnings, } from './stylable-value-parsers'; @@ -186,6 +190,7 @@ export { getStringValue, groupValues, listOptions, + validateAllowedNodesUntil, } from './helpers/value'; // *** deprecated *** diff --git a/packages/core/src/stylable-processor.ts b/packages/core/src/stylable-processor.ts index 8a4026354..a277fc924 100644 --- a/packages/core/src/stylable-processor.ts +++ b/packages/core/src/stylable-processor.ts @@ -17,12 +17,8 @@ import { } from './features'; import { generalDiagnostics } from './features/diagnostics'; import { FeatureContext, STSymbol, STImport, CSSClass, CSSType, CSSKeyframes } from './features'; -import { - CUSTOM_SELECTOR_RE, - expandCustomSelectors, - getAlias, - isCSSVarProp, -} from './stylable-utils'; +import { CUSTOM_SELECTOR_RE, expandCustomSelectors, getAlias } from './stylable-utils'; +import { isCSSVarProp } from './helpers/css-custom-property'; import { processDeclarationFunctions } from './process-declaration-functions'; import { walkSelector, @@ -34,9 +30,9 @@ import { stringifySelector, } from './helpers/selector'; import { isChildOfAtRule } from './helpers/rule'; +import { GLOBAL_FUNC } from './helpers/global'; import type { SRule } from './deprecated/postcss-ast-extension'; import { - paramMapping, rootValueMapping, SBTypesParsers, stValuesMap, @@ -102,7 +98,7 @@ export const processorWarnings = { return `nesting of rules within rules is not supported, found: "${child}" inside "${parent}"`; }, DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY() { - return `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${paramMapping.global}`; + return `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${GLOBAL_FUNC}`; }, DEPRECATED_ST_FUNCTION_NAME: (name: string, alternativeName: string) => { return `"${name}" is deprecated, use "${alternativeName}"`; diff --git a/packages/core/src/stylable-transformer.ts b/packages/core/src/stylable-transformer.ts index 5f92a5edb..753375aea 100644 --- a/packages/core/src/stylable-transformer.ts +++ b/packages/core/src/stylable-transformer.ts @@ -31,7 +31,8 @@ import type { StylableMeta } from './stylable-meta'; import { STSymbol, STGlobal, CSSClass, CSSType, CSSKeyframes } from './features'; import type { SRule, SDecl } from './deprecated/postcss-ast-extension'; import { CSSResolve, StylableResolverCache, StylableResolver } from './stylable-resolver'; -import { generateScopedCSSVar, isCSSVarProp } from './stylable-utils'; +import { generateScopedCSSVar } from './stylable-utils'; // ToDO: move +import { isCSSVarProp } from './helpers/css-custom-property'; import { valueMapping } from './stylable-value-parsers'; import { unescapeCSS, namespaceEscape } from './helpers/escape'; import type { ModuleResolver } from './types'; diff --git a/packages/core/src/stylable-utils.ts b/packages/core/src/stylable-utils.ts index 1600accf6..7fdb50dd8 100644 --- a/packages/core/src/stylable-utils.ts +++ b/packages/core/src/stylable-utils.ts @@ -131,10 +131,6 @@ export function generateScopedCSSVar(namespace: string, varName: string) { return `--${namespace}-${varName}`; } -export function isCSSVarProp(value: string) { - return value.startsWith('--'); -} - export function scopeCSSVar(resolver: StylableResolver, meta: StylableMeta, symbolName: string) { const importedVar = resolver.deepResolve(STSymbol.get(meta, symbolName)); if ( diff --git a/packages/core/src/stylable-value-parsers.ts b/packages/core/src/stylable-value-parsers.ts index 3e87ab6e5..bd1a6e4f8 100644 --- a/packages/core/src/stylable-value-parsers.ts +++ b/packages/core/src/stylable-value-parsers.ts @@ -4,7 +4,7 @@ import type { Diagnostics } from './diagnostics'; import { processPseudoStates } from './pseudo-states'; import { parseSelectorWithCache } from './helpers/selector'; import { getNamedArgs, getFormatterArgs } from './helpers/value'; -import type { ParsedValue, StateParsedValue } from './types'; +import type { StateParsedValue } from './types'; export const valueParserWarnings = { VALUE_CANNOT_BE_STRING() { @@ -64,10 +64,6 @@ export const valueMapping = { global: '-st-global' as const, }; -export const paramMapping = { - global: 'st-global' as const, -}; - export const mixinDeclRegExp = new RegExp(`(${valueMapping.mixin})|(${valueMapping.partialMixin})`); export type stKeys = keyof typeof valueMapping; @@ -78,8 +74,6 @@ export const stValues: string[] = Object.keys(valueMapping).map( export const animationPropRegExp = /animation$|animation-name$/; -export const globalValueRegExp = new RegExp(`^${paramMapping.global}\\((.*?)\\)$`); - export const stValuesMap: Record = Object.keys(valueMapping).reduce((acc, key) => { acc[valueMapping[key as stKeys]] = true; return acc; @@ -223,22 +217,3 @@ function stringifyParam(nodes: any) { } }); } - -export function validateAllowedNodesUntil( - node: ParsedValue, - i: number, - untilType = 'div', - allowed = ['comment'] -) { - i = 1; - let current = node.nodes[i]; - while (current && current.type !== untilType) { - if (!allowed.includes(current.type)) { - return false; - } - i++; - current = node.nodes[i]; - } - - return true; -} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 63e2f44ed..3d3011da4 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,10 +1,3 @@ -import { globalValueRegExp } from './stylable-value-parsers'; - -export function globalValue(str: string) { - const match = str.match(globalValueRegExp); - return match?.[1]; -} - export function stripQuotation(str: string) { return str.replace(/^['"](.*?)['"]$/g, '$1'); } From b5ddb8fa73e1959fd31217cb8b9f2b43a6db4b5e Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 13:11:20 +0200 Subject: [PATCH 06/20] refactor: move more code from processor to feature - analyze `@st-global-custom-property` to feature - add `toRemove` api to `analyzeAtRule` feature until `rawAst` is immutable --- ...t-global-custom-property-to-at-property.ts | 15 ++-- ...bal-custom-property-to-at-property.spec.ts | 4 +- .../core/src/features/css-custom-property.ts | 61 ++++++++++++++-- packages/core/src/features/feature.ts | 6 +- packages/core/src/stylable-processor.ts | 70 ++----------------- packages/core/test/css-vars.spec.ts | 15 ++-- 6 files changed, 87 insertions(+), 84 deletions(-) diff --git a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts index d5d3e7efe..23e4880d1 100644 --- a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts +++ b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts @@ -1,5 +1,6 @@ -import { CSSVarSymbol, Diagnostics, isCSSVarProp, processorWarnings } from '@stylable/core'; +import { CSSVarSymbol, Diagnostics, isCSSVarProp } from '@stylable/core'; import { GLOBAL_FUNC } from '@stylable/core/dist/helpers/global'; +import { CSSCustomProperty } from '@stylable/core/dist/features'; import type { AtRule } from 'postcss'; import type { CodeMod } from './types'; @@ -36,9 +37,13 @@ function parseStGlobalCustomProperty(atRule: AtRule, diagnostics: Diagnostics): .filter((s) => s !== ','); if (cssVarsBySpacing.length > cssVarsByComma.length) { - diagnostics.warn(atRule, processorWarnings.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { - word: atRule.params, - }); + diagnostics.warn( + atRule, + CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), + { + word: atRule.params, + } + ); return cssVars; } @@ -52,7 +57,7 @@ function parseStGlobalCustomProperty(atRule: AtRule, diagnostics: Diagnostics): global: true, }); } else { - diagnostics.warn(atRule, processorWarnings.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { + diagnostics.warn(atRule, CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { word: cssVar, }); } diff --git a/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts b/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts index cb3311fb2..ae5915573 100644 --- a/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts +++ b/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts @@ -1,4 +1,4 @@ -import { processorWarnings } from '@stylable/core'; +import { CSSCustomProperty } from '@stylable/core/dist/features'; import { expect } from 'chai'; import { createTempDirectory, ITempDirectory } from 'create-temp-directory'; import { populateDirectorySync, loadDirSync, runCliCodeMod } from '@stylable/e2e-test-kit'; @@ -68,7 +68,7 @@ describe('CLI Codemods st-global-custom-property-to-at-property', () => { expect(stdout).to.match( new RegExp( - `style.st.css: ${processorWarnings.GLOBAL_CSS_VAR_MISSING_COMMA( + `style.st.css: ${CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( '--myVar --mySecondVar' )}` ) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index a1955c47e..6b55a85d6 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -2,7 +2,7 @@ import { createFeature, FeatureContext } from './feature'; import * as STSymbol from './st-symbol'; import { validateAtProperty, isCSSVarProp } from '../helpers/css-custom-property'; import { validateAllowedNodesUntil } from '../helpers/value'; -import { globalValue } from '../helpers/global'; +import { globalValue, GLOBAL_FUNC } from '../helpers/global'; import type * as postcss from 'postcss'; // ToDo: refactor out import postcssValueParser from 'postcss-value-parser'; @@ -19,14 +19,28 @@ export const diagnostics = { ILLEGAL_CSS_VAR_ARGS(name: string) { return `custom property "${name}" usage (var()) must receive comma separated values`; }, + DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY() { + return `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${GLOBAL_FUNC}`; + }, + GLOBAL_CSS_VAR_MISSING_COMMA(name: string) { + return `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`; + }, + ILLEGAL_GLOBAL_CSS_VAR(name: string) { + return `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)`; + }, }; // HOOKS export const hooks = createFeature({ - analyzeAtRule({ context, atRule }) { - addCSSVarDefinition(context, atRule); - validateAtProperty(atRule, context.diagnostics); + analyzeAtRule({ context, atRule, toRemove }) { + if (atRule.name === `property`) { + addCSSVarDefinition(context, atRule); + validateAtProperty(atRule, context.diagnostics); + } else if (atRule.name === `st-global-custom-property`) { + analyzeDeprecatedStGlobalCustomProperty(context, atRule); + toRemove.push(atRule); + } }, analyzeDeclaration({ context, decl }) { if (isCSSVarProp(decl.prop)) { @@ -105,3 +119,42 @@ function addCSSVar( }); } } + +function analyzeDeprecatedStGlobalCustomProperty(context: FeatureContext, atRule: postcss.AtRule) { + // report deprecation + context.diagnostics.info(atRule, diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()); + // + const cssVarsByComma = atRule.params.split(','); + const cssVarsBySpacing = atRule.params + .trim() + .split(/\s+/g) + .filter((s) => s !== ','); + + if (cssVarsBySpacing.length > cssVarsByComma.length) { + context.diagnostics.warn(atRule, diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { + word: atRule.params, + }); + return; + } + + for (const entry of cssVarsByComma) { + const cssVar = entry.trim(); + if (isCSSVarProp(cssVar)) { + const property: CSSVarSymbol = { + _kind: 'cssVar', + name: cssVar, + global: true, + }; + context.meta.cssVars[cssVar] = property; + STSymbol.addSymbol({ + context, + symbol: property, + node: atRule, + }); + } else { + context.diagnostics.warn(atRule, diagnostics.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { + word: cssVar, + }); + } + } +} diff --git a/packages/core/src/features/feature.ts b/packages/core/src/features/feature.ts index d84d76d5e..3c6e2b0cf 100644 --- a/packages/core/src/features/feature.ts +++ b/packages/core/src/features/feature.ts @@ -28,7 +28,11 @@ export interface NodeTypes { export interface FeatureHooks { metaInit: (context: FeatureContext) => void; analyzeInit: (context: FeatureContext) => void; - analyzeAtRule: (options: { context: FeatureContext; atRule: postcss.AtRule }) => void; + analyzeAtRule: (options: { + context: FeatureContext; + atRule: postcss.AtRule; + toRemove: postcss.AtRule[]; // ToDo: remove once rawAst is immutable in processor + }) => void; analyzeSelectorNode: (options: { context: FeatureContext; node: T['IMMUTABLE_SELECTOR']; diff --git a/packages/core/src/stylable-processor.ts b/packages/core/src/stylable-processor.ts index a277fc924..2e4fff77a 100644 --- a/packages/core/src/stylable-processor.ts +++ b/packages/core/src/stylable-processor.ts @@ -9,7 +9,6 @@ import { StylableMeta } from './stylable-meta'; import { ClassSymbol, CSSCustomProperty, - CSSVarSymbol, ElementSymbol, RefedMixin, StylableDirectives, @@ -18,7 +17,6 @@ import { import { generalDiagnostics } from './features/diagnostics'; import { FeatureContext, STSymbol, STImport, CSSClass, CSSType, CSSKeyframes } from './features'; import { CUSTOM_SELECTOR_RE, expandCustomSelectors, getAlias } from './stylable-utils'; -import { isCSSVarProp } from './helpers/css-custom-property'; import { processDeclarationFunctions } from './process-declaration-functions'; import { walkSelector, @@ -30,7 +28,6 @@ import { stringifySelector, } from './helpers/selector'; import { isChildOfAtRule } from './helpers/rule'; -import { GLOBAL_FUNC } from './helpers/global'; import type { SRule } from './deprecated/postcss-ast-extension'; import { rootValueMapping, @@ -85,21 +82,12 @@ export const processorWarnings = { MISSING_SCOPING_PARAM() { return '"@st-scope" missing scoping selector parameter'; }, - ILLEGAL_GLOBAL_CSS_VAR(name: string) { - return `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)`; - }, - GLOBAL_CSS_VAR_MISSING_COMMA(name: string) { - return `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`; - }, INVALID_NAMESPACE_REFERENCE() { return 'st-namespace-reference dose not have any value'; }, INVALID_NESTING(child: string, parent: string) { return `nesting of rules within rules is not supported, found: "${child}" inside "${parent}"`; }, - DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY() { - return `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${GLOBAL_FUNC}`; - }, DEPRECATED_ST_FUNCTION_NAME: (name: string, alternativeName: string) => { return `"${name}" is deprecated, use "${alternativeName}"`; }, @@ -173,7 +161,7 @@ export class StylableProcessor implements FeatureContext { protected handleAtRules(root: postcss.Root) { let namespace = ''; - const toRemove: postcss.Node[] = []; + const toRemove: postcss.AtRule[] = []; root.walkAtRules((atRule) => { switch (atRule.name) { @@ -181,6 +169,7 @@ export class StylableProcessor implements FeatureContext { STImport.hooks.analyzeAtRule({ context: this, atRule, + toRemove, }); break; } @@ -202,6 +191,7 @@ export class StylableProcessor implements FeatureContext { CSSKeyframes.hooks.analyzeAtRule({ context: this, atRule, + toRemove, }); break; case 'custom-selector': { @@ -220,59 +210,9 @@ export class StylableProcessor implements FeatureContext { case 'st-scope': this.meta.scopes.push(atRule); break; - case 'property': { - CSSCustomProperty.hooks.analyzeAtRule({ context: this, atRule }); - break; - } + case 'property': case 'st-global-custom-property': { - this.diagnostics.info( - atRule, - processorWarnings.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY() - ); - - const cssVarsByComma = atRule.params.split(','); - const cssVarsBySpacing = atRule.params - .trim() - .split(/\s+/g) - .filter((s) => s !== ','); - - if (cssVarsBySpacing.length > cssVarsByComma.length) { - this.diagnostics.warn( - atRule, - processorWarnings.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), - { - word: atRule.params, - } - ); - break; - } - - for (const entry of cssVarsByComma) { - const cssVar = entry.trim(); - - if (isCSSVarProp(cssVar)) { - const property: CSSVarSymbol = { - _kind: 'cssVar', - name: cssVar, - global: true, - }; - - this.meta.cssVars[cssVar] = property; - STSymbol.addSymbol({ - context: this, - symbol: property, - node: atRule, - }); - } else { - this.diagnostics.warn( - atRule, - processorWarnings.ILLEGAL_GLOBAL_CSS_VAR(cssVar), - { word: cssVar } - ); - } - } - - toRemove.push(atRule); + CSSCustomProperty.hooks.analyzeAtRule({ context: this, atRule, toRemove }); break; } } diff --git a/packages/core/test/css-vars.spec.ts b/packages/core/test/css-vars.spec.ts index 5db829183..f237f346e 100644 --- a/packages/core/test/css-vars.spec.ts +++ b/packages/core/test/css-vars.spec.ts @@ -5,7 +5,6 @@ import { generateStylableResult, processSource, } from '@stylable/core-test-kit'; -import { processorWarnings } from '@stylable/core'; import { STSymbol, STImport, CSSCustomProperty } from '@stylable/core/dist/features'; describe('css custom-properties (vars)', () => { @@ -677,7 +676,8 @@ describe('css custom-properties (vars)', () => { const res = expectTransformDiagnostics(config, [ { file: '/entry.st.css', - message: processorWarnings.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), + message: + CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), severity: 'info', }, ]); @@ -834,7 +834,7 @@ describe('css custom-properties (vars)', () => { const res = expectTransformDiagnostics(config, [ { file: '/entry.st.css', - message: processorWarnings.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), + message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), severity: 'info', }, ]); @@ -965,12 +965,12 @@ describe('css custom-properties (vars)', () => { expectTransformDiagnostics(config, [ { - message: processorWarnings.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), + message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), file: '/entry.st.css', severity: 'info', }, { - message: processorWarnings.ILLEGAL_GLOBAL_CSS_VAR('illegalVar'), + message: CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR('illegalVar'), file: '/entry.st.css', skipLocationCheck: true, }, @@ -992,12 +992,13 @@ describe('css custom-properties (vars)', () => { expectTransformDiagnostics(config, [ { - message: processorWarnings.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), + message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), file: '/entry.st.css', severity: 'info', }, { - message: processorWarnings.GLOBAL_CSS_VAR_MISSING_COMMA('--var1 --var2'), + message: + CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA('--var1 --var2'), file: '/entry.st.css', skipLocationCheck: true, }, From e088a4501ed1935f8534cbf632e0228309fe69cc Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 14:03:41 +0200 Subject: [PATCH 07/20] refactor: transformer `css-custom-property` feature extract - `transformResolve` - `transformAtRule` - `transformJSExports` --- .../core/src/features/css-custom-property.ts | 87 ++++++++++++++++++- .../core/src/helpers/css-custom-property.ts | 4 + packages/core/src/index.ts | 5 +- packages/core/src/stylable-transformer.ts | 77 +++++----------- packages/core/src/stylable-utils.ts | 28 +----- 5 files changed, 113 insertions(+), 88 deletions(-) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index 6b55a85d6..babbfa471 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -1,11 +1,18 @@ import { createFeature, FeatureContext } from './feature'; import * as STSymbol from './st-symbol'; -import { validateAtProperty, isCSSVarProp } from '../helpers/css-custom-property'; +import { + validateAtProperty, + isCSSVarProp, + generateScopedCSSVar, +} from '../helpers/css-custom-property'; import { validateAllowedNodesUntil } from '../helpers/value'; import { globalValue, GLOBAL_FUNC } from '../helpers/global'; +import type { StylableMeta } from '../stylable-meta'; +import type { StylableResolver } from '../stylable-resolver'; import type * as postcss from 'postcss'; // ToDo: refactor out import postcssValueParser from 'postcss-value-parser'; + export interface CSSVarSymbol { _kind: 'cssVar'; name: string; @@ -32,7 +39,9 @@ export const diagnostics = { // HOOKS -export const hooks = createFeature({ +export const hooks = createFeature<{ + RESOLVED: Record; +}>({ analyzeAtRule({ context, atRule, toRemove }) { if (atRule.name === `property`) { addCSSVarDefinition(context, atRule); @@ -50,6 +59,60 @@ export const hooks = createFeature({ handleCSSVarUse(context, decl); } }, + transformResolve({ context: { meta, resolver } }) { + const cssVarsMapping: Record = {}; + // imported vars + for (const imported of meta.getImportStatements()) { + for (const symbolName of Object.keys(imported.named)) { + if (isCSSVarProp(symbolName)) { + const importedVar = resolver.deepResolve(STSymbol.get(meta, symbolName)); + + if ( + importedVar && + importedVar._kind === 'css' && + importedVar.symbol && + importedVar.symbol._kind === 'cssVar' + ) { + cssVarsMapping[symbolName] = importedVar.symbol.global + ? importedVar.symbol.name + : generateScopedCSSVar( + importedVar.meta.namespace, + importedVar.symbol.name.slice(2) + ); + } + } + } + } + + // locally defined vars + for (const localVarName of Object.keys(meta.cssVars)) { + const cssVar = meta.cssVars[localVarName]; + + if (!cssVarsMapping[localVarName]) { + cssVarsMapping[localVarName] = cssVar.global + ? localVarName + : generateScopedCSSVar(meta.namespace, localVarName.slice(2)); + } + } + + return cssVarsMapping; + }, + transformAtRuleNode({ atRule, resolved }) { + if (atRule.nodes?.length) { + // ToDo: namespace + atRule.params = resolved[atRule.params] ?? atRule.params; + } else { + // remove `@property` with no body + atRule.remove(); + } + // ToDo: move removal of `@st-global-custom-property` here + }, + transformJSExports({ exports, resolved }) { + for (const varName of Object.keys(resolved)) { + // ToDo: namespace + exports.vars[varName.slice(2)] = resolved[varName]; + } + }, }); function handleCSSVarUse(context: FeatureContext, decl: postcss.Declaration) { @@ -158,3 +221,23 @@ function analyzeDeprecatedStGlobalCustomProperty(context: FeatureContext, atRule } } } + +export function scopeCSSVar(resolver: StylableResolver, meta: StylableMeta, symbolName: string) { + const importedVar = resolver.deepResolve(STSymbol.get(meta, symbolName)); + if ( + importedVar && + importedVar._kind === 'css' && + importedVar.symbol && + importedVar.symbol._kind === 'cssVar' + ) { + return importedVar.symbol.global + ? importedVar.symbol.name + : generateScopedCSSVar(importedVar.meta.namespace, importedVar.symbol.name.slice(2)); + } + const cssVar = meta.cssVars[symbolName]; + if (cssVar?.global) { + return symbolName; + } else { + return generateScopedCSSVar(meta.namespace, symbolName.slice(2)); + } +} diff --git a/packages/core/src/helpers/css-custom-property.ts b/packages/core/src/helpers/css-custom-property.ts index 318826248..3b6bcc944 100644 --- a/packages/core/src/helpers/css-custom-property.ts +++ b/packages/core/src/helpers/css-custom-property.ts @@ -114,3 +114,7 @@ export function validateAtProperty( export function isCSSVarProp(value: string) { return value.startsWith('--'); } + +export function generateScopedCSSVar(namespace: string, varName: string) { + return `--${namespace}-${varName}`; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d3375e5b1..27af3595d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,6 +19,7 @@ export type { VarSymbol, } from './features'; export { reservedKeyFrames } from './features/css-keyframes'; +export { scopeCSSVar } from './features/css-custom-property'; export { StylableProcessor, createEmptyMeta, @@ -29,7 +30,7 @@ export { } from './stylable-processor'; export { ensureStylableImports, parseStylableImport } from './helpers/import'; -export { isCSSVarProp } from './helpers/css-custom-property'; +export { isCSSVarProp, generateScopedCSSVar } from './helpers/css-custom-property'; export { globalValueRegExp } from './helpers/global'; import { GLOBAL_FUNC } from './helpers/global'; @@ -55,13 +56,11 @@ export { CUSTOM_SELECTOR_RE, expandCustomSelectors, findDeclaration, - generateScopedCSSVar, getAlias, getSourcePath, isValidClassName, isValidDeclaration, mergeRules, - scopeCSSVar, transformMatchesOnRule, } from './stylable-utils'; export { diff --git a/packages/core/src/stylable-transformer.ts b/packages/core/src/stylable-transformer.ts index 753375aea..88cd7f6b4 100644 --- a/packages/core/src/stylable-transformer.ts +++ b/packages/core/src/stylable-transformer.ts @@ -26,12 +26,11 @@ import { createWarningRule, isChildOfAtRule, getRuleScopeSelector } from './help import { namespace } from './helpers/namespace'; import { getOriginDefinition } from './helpers/resolve'; import { appendMixins } from './stylable-mixins'; -import { STImport, ClassSymbol, ElementSymbol } from './features'; +import { STImport, ClassSymbol, ElementSymbol, CSSCustomProperty } from './features'; import type { StylableMeta } from './stylable-meta'; import { STSymbol, STGlobal, CSSClass, CSSType, CSSKeyframes } from './features'; import type { SRule, SDecl } from './deprecated/postcss-ast-extension'; import { CSSResolve, StylableResolverCache, StylableResolver } from './stylable-resolver'; -import { generateScopedCSSVar } from './stylable-utils'; // ToDO: move import { isCSSVarProp } from './helpers/css-custom-property'; import { valueMapping } from './stylable-value-parsers'; import { unescapeCSS, namespaceEscape } from './helpers/escape'; @@ -164,7 +163,13 @@ export class StylableTransformer { resolver: this.resolver, }, }); - const cssVarsMapping = this.createCSSVarsMapping(ast, meta); + const cssVarsMapping = CSSCustomProperty.hooks.transformResolve({ + context: { + meta, + diagnostics: this.diagnostics, + resolver: this.resolver, + }, + }); ast.walkRules((rule) => { if (isChildOfAtRule(rule, 'keyframes')) { @@ -189,11 +194,15 @@ export class StylableTransformer { undefined ); } else if (name === 'property') { - if (atRule.nodes?.length) { - atRule.params = cssVarsMapping[atRule.params] ?? atRule.params; - } else { - atRule.remove(); - } + CSSCustomProperty.hooks.transformAtRuleNode({ + context: { + meta, + diagnostics: this.diagnostics, + resolver: this.resolver, + }, + atRule, + resolved: cssVarsMapping, + }); } else if (name === 'keyframes') { CSSKeyframes.hooks.transformAtRuleNode({ context: { @@ -259,7 +268,10 @@ export class StylableTransformer { exports: metaExports, resolved: keyframesResolve, }); - this.exportCSSVars(cssVarsMapping, metaExports.vars); + CSSCustomProperty.hooks.transformJSExports({ + exports: metaExports, + resolved: cssVarsMapping, + }); } } public exportLocalVars( @@ -279,53 +291,6 @@ export class StylableTransformer { stVarsExport[varSymbol.name] = topLevelType ? unbox(topLevelType) : outputValue; } } - public exportCSSVars( - cssVarsMapping: Record, - varsExport: Record - ) { - for (const varName of Object.keys(cssVarsMapping)) { - varsExport[varName.slice(2)] = cssVarsMapping[varName]; - } - } - public createCSSVarsMapping(_ast: postcss.Root, meta: StylableMeta) { - const cssVarsMapping: Record = {}; - - // imported vars - for (const imported of meta.getImportStatements()) { - for (const symbolName of Object.keys(imported.named)) { - if (isCSSVarProp(symbolName)) { - const importedVar = this.resolver.deepResolve(STSymbol.get(meta, symbolName)); - - if ( - importedVar && - importedVar._kind === 'css' && - importedVar.symbol && - importedVar.symbol._kind === 'cssVar' - ) { - cssVarsMapping[symbolName] = importedVar.symbol.global - ? importedVar.symbol.name - : generateScopedCSSVar( - importedVar.meta.namespace, - importedVar.symbol.name.slice(2) - ); - } - } - } - } - - // locally defined vars - for (const localVarName of Object.keys(meta.cssVars)) { - const cssVar = meta.cssVars[localVarName]; - - if (!cssVarsMapping[localVarName]) { - cssVarsMapping[localVarName] = cssVar.global - ? localVarName - : generateScopedCSSVar(meta.namespace, localVarName.slice(2)); - } - } - - return cssVarsMapping; - } public getScopedCSSVar( decl: postcss.Declaration, meta: StylableMeta, diff --git a/packages/core/src/stylable-utils.ts b/packages/core/src/stylable-utils.ts index 7fdb50dd8..7966c4f6c 100644 --- a/packages/core/src/stylable-utils.ts +++ b/packages/core/src/stylable-utils.ts @@ -2,12 +2,10 @@ import { isAbsolute } from 'path'; import type * as postcss from 'postcss'; import { replaceRuleSelector } from './replace-rule-selector'; import type { Diagnostics } from './diagnostics'; -import { STSymbol, Imported, ImportSymbol, StylableSymbol } from './features'; +import type { Imported, ImportSymbol, StylableSymbol } from './features'; import { isChildOfAtRule } from './helpers/rule'; import { scopeNestedSelector, parseSelectorWithCache } from './helpers/selector'; -import type { StylableMeta } from './stylable-meta'; import { valueMapping, mixinDeclRegExp } from './stylable-value-parsers'; -import type { StylableResolver } from './stylable-resolver'; export const CUSTOM_SELECTOR_RE = /:--[\w-]+/g; @@ -127,30 +125,6 @@ export function getAlias(symbol: StylableSymbol): ImportSymbol | undefined { return undefined; } -export function generateScopedCSSVar(namespace: string, varName: string) { - return `--${namespace}-${varName}`; -} - -export function scopeCSSVar(resolver: StylableResolver, meta: StylableMeta, symbolName: string) { - const importedVar = resolver.deepResolve(STSymbol.get(meta, symbolName)); - if ( - importedVar && - importedVar._kind === 'css' && - importedVar.symbol && - importedVar.symbol._kind === 'cssVar' - ) { - return importedVar.symbol.global - ? importedVar.symbol.name - : generateScopedCSSVar(importedVar.meta.namespace, importedVar.symbol.name.slice(2)); - } - const cssVar = meta.cssVars[symbolName]; - if (cssVar?.global) { - return symbolName; - } else { - return generateScopedCSSVar(meta.namespace, symbolName.slice(2)); - } -} - export function isValidClassName(className: string) { const test = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/g; // checks valid classname return !!className.match(test); From 648230b84ab99340f392731cf3c11c68ee18d7b1 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 14:50:24 +0200 Subject: [PATCH 08/20] refactor: move transform value `var()` to feature - `transformDeclarationValue` new hook - transform `var()` extract from `functions` to feaure - `stringifyFunction` from `functions` to `helpers/value` --- .../core/src/features/css-custom-property.ts | 15 +++++++++- packages/core/src/features/feature.ts | 9 ++++++ packages/core/src/functions.ts | 28 ++++++++----------- packages/core/src/helpers/value.ts | 4 +++ 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index babbfa471..45a85255c 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -5,7 +5,7 @@ import { isCSSVarProp, generateScopedCSSVar, } from '../helpers/css-custom-property'; -import { validateAllowedNodesUntil } from '../helpers/value'; +import { validateAllowedNodesUntil, stringifyFunction } from '../helpers/value'; import { globalValue, GLOBAL_FUNC } from '../helpers/global'; import type { StylableMeta } from '../stylable-meta'; import type { StylableResolver } from '../stylable-resolver'; @@ -107,6 +107,19 @@ export const hooks = createFeature<{ } // ToDo: move removal of `@st-global-custom-property` here }, + transformDeclarationValue({ node, resolved }) { + const { value } = node; + const varWithPrefix = node.nodes[0].value; + if (isCSSVarProp(varWithPrefix)) { + if (resolved && resolved[varWithPrefix]) { + node.nodes[0].value = resolved[varWithPrefix]; + } + } + // handle default values + if (node.nodes.length > 2) { + node.resolvedValue = stringifyFunction(value, node); + } + }, transformJSExports({ exports, resolved }) { for (const varName of Object.keys(resolved)) { // ToDo: namespace diff --git a/packages/core/src/features/feature.ts b/packages/core/src/features/feature.ts index 3c6e2b0cf..cc78afe09 100644 --- a/packages/core/src/features/feature.ts +++ b/packages/core/src/features/feature.ts @@ -4,6 +4,7 @@ import type { StylableResolver } from '../stylable-resolver'; import type * as postcss from 'postcss'; import type { ImmutableSelectorNode } from '@tokey/css-selector-parser'; import type { Diagnostics } from '../diagnostics'; +import type { ParsedValue } from '../types'; export type SelectorNodeContext = [ index: number, @@ -57,6 +58,11 @@ export interface FeatureHooks { decl: postcss.Declaration; resolved: T['RESOLVED']; }) => void; + transformDeclarationValue: (options: { + context: FeatureTransformContext; + node: ParsedValue; + resolved: T['RESOLVED']; + }) => void; transformJSExports: (options: { exports: StylableExports; resolved: T['RESOLVED'] }) => void; } const defaultHooks: FeatureHooks = { @@ -90,6 +96,9 @@ const defaultHooks: FeatureHooks = { transformDeclaration() { /**/ }, + transformDeclarationValue() { + /**/ + }, transformJSExports() { /**/ }, diff --git a/packages/core/src/functions.ts b/packages/core/src/functions.ts index b789dfaec..de77107dd 100644 --- a/packages/core/src/functions.ts +++ b/packages/core/src/functions.ts @@ -8,12 +8,11 @@ import { assureRelativeUrlPrefix } from './stylable-assets'; import type { StylableMeta } from './stylable-meta'; import type { CSSResolve, JSResolve, StylableResolver } from './stylable-resolver'; import type { replaceValueHook, StylableTransformer } from './stylable-transformer'; -import { isCSSVarProp } from './helpers/css-custom-property'; import { strategies, valueMapping } from './stylable-value-parsers'; -import { getFormatterArgs, getStringValue } from './helpers/value'; +import { getFormatterArgs, getStringValue, stringifyFunction } from './helpers/value'; import type { ParsedValue } from './types'; import { stripQuotation } from './utils'; -import { STSymbol } from './features'; +import { CSSCustomProperty, STSymbol } from './features'; export type ValueFormatter = (name: string) => string; export type ResolvedFormatter = Record; @@ -296,16 +295,15 @@ export function processDeclarationValue( } } } else if (value === 'var') { - const varWithPrefix = parsedNode.nodes[0].value; - if (isCSSVarProp(varWithPrefix)) { - if (cssVarsMapping && cssVarsMapping[varWithPrefix]) { - parsedNode.nodes[0].value = cssVarsMapping[varWithPrefix]; - } - } - // handle default values - if (parsedNode.nodes.length > 2) { - parsedNode.resolvedValue = stringifyFunction(value, parsedNode); - } + CSSCustomProperty.hooks.transformDeclarationValue({ + context: { + meta, + diagnostics: diagnostics || (null as unknown as Diagnostics), // ToDo: make sure context is available here + resolver, + }, + node: parsedNode, + resolved: cssVarsMapping || {}, + }); } else if (isCssNativeFunction(value)) { parsedNode.resolvedValue = stringifyFunction(value, parsedNode); } else if (diagnostics && node) { @@ -392,10 +390,6 @@ function handleCyclicValues( return stringifyFunction(value, parsedNode); } -function stringifyFunction(name: string, parsedNode: ParsedValue, perserveQuotes = false) { - return `${name}(${getFormatterArgs(parsedNode, false, undefined, perserveQuotes).join(', ')})`; -} - function createUniqID(source: string, varName: string) { return `${source}: ${varName}`; } diff --git a/packages/core/src/helpers/value.ts b/packages/core/src/helpers/value.ts index f1769c830..ecdb57a8b 100644 --- a/packages/core/src/helpers/value.ts +++ b/packages/core/src/helpers/value.ts @@ -25,6 +25,10 @@ export function getNamedArgs(node: ParsedValue) { return args.length && args[args.length - 1].length === 0 ? args.slice(0, -1) : args; } +export function stringifyFunction(name: string, parsedNode: ParsedValue, perserveQuotes = false) { + return `${name}(${getFormatterArgs(parsedNode, false, undefined, perserveQuotes).join(', ')})`; +} + export function getFormatterArgs( node: ParsedValue, allowComments = false, From 7ea105124f4a063114d361321c7019b07cc9ca1c Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 10 Jan 2022 15:02:07 +0200 Subject: [PATCH 09/20] refactor: `--X: ` declaration prop transform to feature - `transformDeclaration` - move `--X: ;` namespace to feature - `getScopedCSSVar` from `transformer` to `helpers/css-custom-property` - deprecate `transformer.getScopedCSSVar()` --- .../core/src/features/css-custom-property.ts | 4 ++++ .../core/src/helpers/css-custom-property.ts | 15 +++++++++++++ packages/core/src/stylable-transformer.ts | 21 +++++++++++-------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index 45a85255c..037f4666d 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -4,6 +4,7 @@ import { validateAtProperty, isCSSVarProp, generateScopedCSSVar, + getScopedCSSVar, } from '../helpers/css-custom-property'; import { validateAllowedNodesUntil, stringifyFunction } from '../helpers/value'; import { globalValue, GLOBAL_FUNC } from '../helpers/global'; @@ -107,6 +108,9 @@ export const hooks = createFeature<{ } // ToDo: move removal of `@st-global-custom-property` here }, + transformDeclaration({ decl, context, resolved }) { + decl.prop = getScopedCSSVar(decl, context.meta, resolved); + }, transformDeclarationValue({ node, resolved }) { const { value } = node; const varWithPrefix = node.nodes[0].value; diff --git a/packages/core/src/helpers/css-custom-property.ts b/packages/core/src/helpers/css-custom-property.ts index 3b6bcc944..63b036e7a 100644 --- a/packages/core/src/helpers/css-custom-property.ts +++ b/packages/core/src/helpers/css-custom-property.ts @@ -1,5 +1,6 @@ import type * as postcss from 'postcss'; import type { Diagnostics } from '../diagnostics'; +import type { StylableMeta } from '../stylable-meta'; import { stripQuotation } from '../utils'; const UNIVERSAL_SYNTAX_DEFINITION = '*'; @@ -118,3 +119,17 @@ export function isCSSVarProp(value: string) { export function generateScopedCSSVar(namespace: string, varName: string) { return `--${namespace}-${varName}`; } + +export function getScopedCSSVar( + decl: postcss.Declaration, + meta: StylableMeta, + cssVarsMapping: Record +) { + let prop = decl.prop; + + if (meta.cssVars[prop]) { + prop = cssVarsMapping[prop]; + } + + return prop; +} diff --git a/packages/core/src/stylable-transformer.ts b/packages/core/src/stylable-transformer.ts index 88cd7f6b4..f1a6b6abe 100644 --- a/packages/core/src/stylable-transformer.ts +++ b/packages/core/src/stylable-transformer.ts @@ -31,7 +31,7 @@ import type { StylableMeta } from './stylable-meta'; import { STSymbol, STGlobal, CSSClass, CSSType, CSSKeyframes } from './features'; import type { SRule, SDecl } from './deprecated/postcss-ast-extension'; import { CSSResolve, StylableResolverCache, StylableResolver } from './stylable-resolver'; -import { isCSSVarProp } from './helpers/css-custom-property'; +import { isCSSVarProp, getScopedCSSVar } from './helpers/css-custom-property'; import { valueMapping } from './stylable-value-parsers'; import { unescapeCSS, namespaceEscape } from './helpers/escape'; import type { ModuleResolver } from './types'; @@ -220,7 +220,15 @@ export class StylableTransformer { (decl as SDecl).stylable = { sourceValue: decl.value }; if (isCSSVarProp(decl.prop)) { - decl.prop = this.getScopedCSSVar(decl, meta, cssVarsMapping); + CSSCustomProperty.hooks.transformDeclaration({ + context: { + meta, + diagnostics: this.diagnostics, + resolver: this.resolver, + }, + decl, + resolved: cssVarsMapping, + }); } else if (decl.prop === `animation` || decl.prop === `animation-name`) { CSSKeyframes.hooks.transformDeclaration({ context: { @@ -291,18 +299,13 @@ export class StylableTransformer { stVarsExport[varSymbol.name] = topLevelType ? unbox(topLevelType) : outputValue; } } + /** @deprecated */ public getScopedCSSVar( decl: postcss.Declaration, meta: StylableMeta, cssVarsMapping: Record ) { - let prop = decl.prop; - - if (meta.cssVars[prop]) { - prop = cssVarsMapping[prop]; - } - - return prop; + return getScopedCSSVar(decl, meta, cssVarsMapping); } public transformGlobals(ast: postcss.Root, meta: StylableMeta) { ast.walkRules((r) => { From f596744a697231cf6bfecefc5c54602737e91f8c Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Sun, 16 Jan 2022 17:58:28 +0200 Subject: [PATCH 10/20] test: move tests from `css-vars.spec` to feature spec - refactored to new format with inline tests and `testStylableCore` - add internal `CSSCustomProperty.get(symbolName)` --- .../core/src/features/css-custom-property.ts | 7 + packages/core/test/css-vars.spec.ts | 1114 ----------------- .../test/features/css-custom-property.spec.ts | 584 +++++++++ 3 files changed, 591 insertions(+), 1114 deletions(-) delete mode 100644 packages/core/test/css-vars.spec.ts create mode 100644 packages/core/test/features/css-custom-property.spec.ts diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index 037f4666d..cb099736e 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -132,6 +132,13 @@ export const hooks = createFeature<{ }, }); +// API + +export function get(meta: StylableMeta, name: string): CSSVarSymbol | undefined { + // return STSymbol.get(meta, name, `class`); + return meta.cssVars[name]; +} + function handleCSSVarUse(context: FeatureContext, decl: postcss.Declaration) { const parsed = postcssValueParser(decl.value); parsed.walk((node) => { diff --git a/packages/core/test/css-vars.spec.ts b/packages/core/test/css-vars.spec.ts deleted file mode 100644 index f237f346e..000000000 --- a/packages/core/test/css-vars.spec.ts +++ /dev/null @@ -1,1114 +0,0 @@ -import { expect } from 'chai'; -import type * as postcss from 'postcss'; -import { - expectTransformDiagnostics, - generateStylableResult, - processSource, -} from '@stylable/core-test-kit'; -import { STSymbol, STImport, CSSCustomProperty } from '@stylable/core/dist/features'; - -describe('css custom-properties (vars)', () => { - describe('process', () => { - // What does it do? - // - locates all css var declarations, grouping them by name - // - exposes defined vars as stylesheet exports - - it('multiple different css var declarations', () => { - const { cssVars, diagnostics } = processSource( - ` - .root { - --myVar: blue; - --myOtherVar: green; - } - `, - { from: 'path/to/style.css' } - ); - - expect(diagnostics.reports.length, 'no reports').to.eql(0); - expect(cssVars).to.eql({ - '--myVar': { - _kind: 'cssVar', - name: '--myVar', - global: false, - }, - '--myOtherVar': { - _kind: 'cssVar', - name: '--myOtherVar', - global: false, - }, - }); - }); - - it('global (unscoped) declarations', () => { - const { cssVars, diagnostics } = processSource( - ` - @property st-global(--myGlobalVar); - - .root { - --myGlobalVar: red; - } - `, - { from: 'path/to/style.css' } - ); - - expect(diagnostics.reports.length, 'no reports').to.eql(0); - expect(cssVars).to.eql({ - '--myGlobalVar': { - _kind: 'cssVar', - name: '--myGlobalVar', - global: true, - }, - }); - }); - - it('multiple css var declarations with the same name', () => { - const { cssVars, diagnostics } = processSource( - ` - .root { - --myVar: blue; - } - .part { - --myVar: green; - } - `, - { from: 'path/to/style.css' } - ); - - expect(diagnostics.reports.length, 'no reports').to.eql(0); - expect(cssVars).to.eql({ - '--myVar': { - _kind: 'cssVar', - name: '--myVar', - global: false, - }, - }); - }); - }); - - describe('transform', () => { - // What does it do? - // - generates namespace for var declarations - - it('should hoist st-global-custom-property', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property --x; - @st-global-custom-property --x; - `, - }, - }, - }); - - expect(res.exports.vars).to.eql({ x: '--x' }); - }); - - it('css vars with their newly created namespace', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --myVar: blue; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.prop).to.equal('--entry-myVar'); - expect(decl.value).to.equal('blue'); - }); - - it('local css var usage', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --myVar: blue; - color: var(--myVar); - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl.prop).to.equal('color'); - expect(decl.value).to.equal('var(--entry-myVar)'); - }); - - it('css var usage with default, mid declaration value', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --myVar: solid; - border: 2px var(--myVar, black) black; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl.value).to.equal('2px var(--entry-myVar, black) black'); - }); - - it('with default and stylable variables together', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :vars { - borderStyle: dashed; - borderColor: black; - } - - .root { - --myVar: solid blue; - border: 2px var(--myVar, value(borderStyle) value(borderColor)); - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl.value).to.equal('2px var(--entry-myVar, dashed black)'); - }); - - it('with nested var default (scoped)', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :vars { - myColor: orange; - } - .root { - --top: green; - --mid: blue; - --base: purple; - background: var(--top, var(--mid, var(--base), value(myColor)), value(myColor)); - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[3] as postcss.Declaration; - expect(decl.value).to.equal( - 'var(--entry-top, var(--entry-mid, var(--entry-base), orange), orange)' - ); - }); - - it(`with formatter for default value`, () => { - const res = generateStylableResult({ - entry: `/style.st.css`, - files: { - '/style.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./formatter"; - -st-default: print; - } - .root { - --myVar: black; - background: var(--myVar, print(green)); - } - `, - }, - '/formatter.js': { - content: ` - module.exports = function(arg) { - return arg; - } - `, - }, - }, - }); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl.value).to.equal('var(--entry-myVar, green)'); - }); - - it(`with stylable var for var declaration initial value`, () => { - const res = generateStylableResult({ - entry: `/style.st.css`, - files: { - '/style.st.css': { - namespace: 'entry', - content: ` - :vars { - myColor: green; - } - - .root { - --myVar: value(myColor); - } - `, - }, - }, - }); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.value).to.equal('green'); - }); - - it(`with formatter for var declaration initial value`, () => { - const res = generateStylableResult({ - entry: `/style.st.css`, - files: { - '/style.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./formatter"; - -st-default: print; - } - .root { - --myVar: print(green); - } - `, - }, - '/formatter.js': { - content: ` - module.exports = function(arg) { - return arg; - } - `, - }, - }, - }); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.value).to.equal('green'); - }); - - it('multiple local css vars usage in the same declaration (1)', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - color: var(--var1) var(--var2); - --var1: red; - --var2: green; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.value).to.equal('var(--entry-var1) var(--entry-var2)'); - }); - - it('multiple local css vars usage in the same declaration (2)', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --size: 5px; - --type: dashed; - --color: blue; - border: var(--size) var( --type ) var(--color); - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[3] as postcss.Declaration; - expect(decl.value).to.equal('var(--entry-size) var( --entry-type ) var(--entry-color)'); - }); - - it('should transfrom css vars usage with no declaration (local scope)', () => { - const { meta } = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - color: var(--myVar, green); - } - `, - }, - }, - }); - - const decl = (meta.outputAst!.nodes[0] as postcss.Rule).nodes[0] as postcss.Declaration; - - expect(meta.diagnostics.reports.length).to.equal(0); - expect(meta.transformDiagnostics!.reports.length).to.equal(0); - - expect(decl.prop).to.equal('color'); - expect(decl.value).to.equal('var(--entry-myVar, green)'); - }); - - it('imported usage', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: './imported.st.css'; - -st-named: --myVar; - } - .root { - color: var(--myVar); - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --myVar: blue; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.prop).to.equal('color'); - expect(decl.value).to.equal('var(--imported-myVar)'); - }); - - it('imported usage with named as', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: './imported.st.css'; - -st-named: --myVar as --renamed; - } - .root { - color: var(--renamed); - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --myVar: blue; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.prop).to.equal('color'); - expect(decl.value).to.equal('var(--imported-myVar)'); - }); - - it('imported usage with local override', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: './imported.st.css'; - -st-named: --myVar; - } - .root { - --myVar: green; - color: var(--myVar); - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --myVar: blue; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl.prop).to.equal('color'); - expect(decl.value).to.equal('var(--imported-myVar)'); - }); - - it('mixed local and imports of re-exported definitions and usage', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./mid.st.css"; - -st-named: --baseVar; - } - .root { - prop1: var(--baseVar); - } - `, - }, - '/mid.st.css': { - namespace: 'mid', - content: ` - :import { - -st-from: "./base.st.css"; - -st-named: --baseVar; - } - `, - }, - '/base.st.css': { - namespace: 'base', - content: ` - .root { - --baseVar: red; - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const baseDecl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - - expect(baseDecl.prop).to.equal('prop1'); - expect(baseDecl.value).to.equal('var(--base-baseVar)'); - }); - - it('scoping var usages inside css mixin variable override', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property st-global(--myGlobal); - - :vars { - arg1: red; - arg2: blue; - } - - .root { - --myColor: green; - -st-mixin: mixin(arg1 var(--myColor)); - } - - .mixin { - color: value(arg1); - background: var(--myGlobal, value(arg2)); - } - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const rule = res.meta.outputAst!.nodes[0] as postcss.Rule; - const decl1 = rule.nodes[1] as postcss.Declaration; - const decl2 = rule.nodes[2] as postcss.Declaration; - expect(decl1.prop).to.equal('color'); - expect(decl1.value).to.equal('var(--entry-myColor)'); - expect(decl2.value).to.equal('var(--myGlobal, blue)'); - }); - - describe('global (unscoped)', () => { - it('does not scope css var declarations', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property st-global(--myVar1); - @property st-global(--myVar2); - - .root { - --myVar1: green; - --myVar2: red; - } - `, - }, - }, - }); - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const decl1 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - const decl2 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(decl1.prop).to.equal('--myVar1'); - expect(decl1.value).to.equal('green'); - expect(decl2.prop).to.equal('--myVar2'); - expect(decl2.value).to.equal('red'); - }); - - it('should support any spacing between global variable definitions', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@st-global-custom-property --myVar1 ,--myVar2, - --myVar3 , --myVar4 |; - .root { - --myVar1: 1; - --myVar2: 2; - --myVar3: 3; - --myVar4: 4; - } - `, - }, - }, - }; - - const res = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: - CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), - severity: 'info', - }, - ]); - - const decl1 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - const decl2 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - const decl3 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[2] as postcss.Declaration; - const decl4 = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[3] as postcss.Declaration; - expect(decl1.prop).to.equal('--myVar1'); - expect(decl1.value).to.equal('1'); - expect(decl2.prop).to.equal('--myVar2'); - expect(decl2.value).to.equal('2'); - expect(decl3.prop).to.equal('--myVar3'); - expect(decl3.value).to.equal('3'); - expect(decl4.prop).to.equal('--myVar4'); - expect(decl4.value).to.equal('4'); - }); - - it('mixed global and scoped var declarations', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property st-global(--myVar); - - .root { - --myVar: blue; - --myScopedVar: green; - } - `, - }, - }, - }); - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const globalDecl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - const scopedDecl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(globalDecl.prop).to.equal('--myVar'); - expect(scopedDecl.prop).to.equal('--entry-myScopedVar'); - }); - - it('should not scope global var usage', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property st-global(--myVar); - - .root { - --myVar: blue; - color: var(--myVar); - } - `, - }, - }, - }); - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const globalDecl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[1] as postcss.Declaration; - expect(globalDecl.value).to.equal('var(--myVar)'); - }); - - it('complex usage with imported global and scoped vars', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./imported.st.css"; - -st-named: --importedGlobal, --importedScoped; - } - - @property st-global(--localGlobal); - - .root { - x1: var(--localScoped); - x2: var(--localGlobal); - x3: var(--importedScoped); - x4: var(--importedGlobal); - --localScoped: blue; - --localGlobal: red; - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - @property st-global(--importedGlobal); - - .root { - --importedScoped: red; - --importedGlobal: blue; - } - `, - }, - }, - }); - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - - const rule = res.meta.outputAst!.nodes[0] as postcss.Rule; - const localScoped = (rule.nodes[0] as postcss.Declaration).value; - const localGlobal = (rule.nodes[1] as postcss.Declaration).value; - const importedScoped = (rule.nodes[2] as postcss.Declaration).value; - const importedGlobal = (rule.nodes[3] as postcss.Declaration).value; - - expect(localScoped).to.equal('var(--entry-localScoped)'); - expect(localGlobal).to.equal('var(--localGlobal)'); - expect(importedScoped).to.equal('var(--imported-importedScoped)'); - expect(importedGlobal).to.equal('var(--importedGlobal)'); - }); - }); - }); - - describe('diagnostics', () => { - it('should report on "@st-global-custom-property" deprecation', () => { - const config = { - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@st-global-custom-property --myVar|; - - .root { - --myVar: blue; - } - `, - }, - }, - }; - - const res = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), - severity: 'info', - }, - ]); - expect(res.exports.vars).to.eql({ - myVar: '--myVar', - }); - }); - - it('trying to use illegal css var syntax', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - |color: var($illegalVar$)|; - } - `, - }, - }, - }; - - const res = expectTransformDiagnostics(config, [ - { - message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE('illegalVar'), - file: '/entry.st.css', - }, - ]); - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.value).to.equal('var(illegalVar)'); - }); - - it('trying to use illegal css var syntax', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - |color: var($--value illegalHere, red$)|; - } - `, - }, - }, - }; - - const res = expectTransformDiagnostics(config, [ - { - message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_ARGS( - '--value illegalHere, red' - ), - file: '/entry.st.css', - }, - ]); - - const decl = (res.meta.outputAst!.nodes[0] as postcss.Rule) - .nodes[0] as postcss.Declaration; - expect(decl.value).to.equal('var(--entry-value illegalHere, red)'); - }); - - it('trying to import unknown css var', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./imported.st.css"; - |-st-named: $--unknownVar$;| - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ``, - }, - }, - }; - - expectTransformDiagnostics(config, [ - { - message: STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( - '--unknownVar', - './imported.st.css' - ), - file: '/entry.st.css', - }, - ]); - }); - - it('global css var declarations must begin with "--"', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property st-global($illegalVar$)|; - `, - }, - }, - }; - - expectTransformDiagnostics(config, [ - { - message: CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE('illegalVar'), - file: '/entry.st.css', - }, - ]); - }); - - it('global css var declarations must begin with "--" (deprecated)', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@st-global-custom-property illegalVar|; - `, - }, - }, - }; - - expectTransformDiagnostics(config, [ - { - message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), - file: '/entry.st.css', - severity: 'info', - }, - { - message: CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR('illegalVar'), - file: '/entry.st.css', - skipLocationCheck: true, - }, - ]); - }); - - it('global css vars must be separated by commas (deprecated)', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@st-global-custom-property --var1 --var2|; - `, - }, - }, - }; - - expectTransformDiagnostics(config, [ - { - message: CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), - file: '/entry.st.css', - severity: 'info', - }, - { - message: - CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA('--var1 --var2'), - file: '/entry.st.css', - skipLocationCheck: true, - }, - ]); - }); - - it('should override import with local definition @property', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property st-global(--before)|; - - @st-import [--before, --after] from "./imported.st.css"; - - @property st-global(--after); - - .root { - prop1: var(--before); - prop2: var(--after); - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --before: red; - --after: red; - } - `, - }, - }, - }; - - const { meta } = expectTransformDiagnostics(config, [ - { - message: STSymbol.diagnostics.REDECLARE_SYMBOL('--before'), - file: '/entry.st.css', - }, - { - message: STSymbol.diagnostics.REDECLARE_SYMBOL('--after'), - file: '/entry.st.css', - skipLocationCheck: true, - }, - ]); - - const rule = meta.outputAst!.nodes[0] as postcss.Rule; - const firstDecl = rule.nodes[0] as postcss.Declaration; - const secondDecl = rule.nodes[1] as postcss.Declaration; - - expect(firstDecl.value).to.equal('var(--before)'); - expect(secondDecl.value).to.equal('var(--after)'); - }); - - it('should override import with local @st-global-custom-property (deprecated)', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@st-global-custom-property --before|; - - @st-import [--before, --after] from "./imported.st.css"; - - |@st-global-custom-property --after|; - - .root { - prop1: var(--before); - prop2: var(--after); - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --before: red; - --after: red; - } - `, - }, - }, - }; - - const { meta } = expectTransformDiagnostics( - config, - [ - { - message: STSymbol.diagnostics.REDECLARE_SYMBOL('--before'), - file: '/entry.st.css', - }, - { - message: STSymbol.diagnostics.REDECLARE_SYMBOL('--after'), - file: '/entry.st.css', - skipLocationCheck: true, - }, - ], - { partial: true } - ); - - const rule = meta.outputAst!.nodes[0] as postcss.Rule; - const firstDecl = rule.nodes[0] as postcss.Declaration; - const secondDecl = rule.nodes[1] as postcss.Declaration; - - expect(firstDecl.value).to.equal('var(--before)'); - expect(secondDecl.value).to.equal('var(--after)'); - }); - }); -}); diff --git a/packages/core/test/features/css-custom-property.spec.ts b/packages/core/test/features/css-custom-property.spec.ts new file mode 100644 index 000000000..9c09f40ac --- /dev/null +++ b/packages/core/test/features/css-custom-property.spec.ts @@ -0,0 +1,584 @@ +import { STImport, CSSCustomProperty, STSymbol } from '@stylable/core/dist/features'; +import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { expect } from 'chai'; + +describe(`features/css-custom-property`, () => { + it(`should process css declaration prop`, () => { + const { sheets } = testStylableCore(` + .root { + /* @decl(A) --entry-propA: green */ + --propA: green; + + /* @decl(B) --entry-propB: blue */ + --propB: blue; + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + + expect(CSSCustomProperty.get(meta, `--propA`), `--propA symbol`).to.eql({ + _kind: `cssVar`, + name: `--propA`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--propB`), `--propB symbol`).to.eql({ + _kind: `cssVar`, + name: `--propB`, + global: false, + }); + + // deprecation + expect(meta.cssVars, `deprecated 'meta.cssVars'`).to.eql({ + '--propA': CSSCustomProperty.get(meta, `--propA`), + '--propB': CSSCustomProperty.get(meta, `--propB`), + }); + }); + it(`should process css declaration value var()`, () => { + const { sheets } = testStylableCore(` + .root { + /* @decl(basic) prop: var(--entry-colorA) */ + prop: var(--colorA); + + /* @decl(concat) prop: var(--entry-colorA) var(--entry-colorB) */ + prop: var(--colorA) var(--colorB); + + /* @decl(within value) prop: 2px var(--entry-colorB) solid */ + prop: 2px var(--colorB) solid; + + /* @decl(as default) prop: var(--entry-colorC, black) */ + prop: var(--colorC, black); + + /* @decl(nested fallbacks) + prop: var(--entry-a, var(--entry-b, var(--entry-c), var(--entry-d)), var(--entry-e)) + */ + prop: var( + --a, + var( + --b, + var(--c), + var(--d) + ), + var(--e) + ); + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + expect(CSSCustomProperty.get(meta, `--colorA`), `--colorA symbol`).to.eql({ + _kind: `cssVar`, + name: `--colorA`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--colorB`), `--colorB symbol`).to.eql({ + _kind: `cssVar`, + name: `--colorB`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--colorC`), `--colorC symbol`).to.eql({ + _kind: `cssVar`, + name: `--colorC`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--a`), `--a symbol`).to.eql({ + _kind: `cssVar`, + name: `--a`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--b`), `--b symbol`).to.eql({ + _kind: `cssVar`, + name: `--b`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--c`), `--c symbol`).to.eql({ + _kind: `cssVar`, + name: `--c`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--d`), `--d symbol`).to.eql({ + _kind: `cssVar`, + name: `--d`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--e`), `--e symbol`).to.eql({ + _kind: `cssVar`, + name: `--e`, + global: false, + }); + }); + it(`should process @property definitions`, () => { + const { sheets } = testStylableCore(` + /* @atRule(runtime def) --entry-propA */ + @property --propA { + syntax: ''; + initial-value: green; + inherits: false; + } + + /* @transform-remove(only type) */ + @property --propB; + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + + expect(CSSCustomProperty.get(meta, `--propA`), `--propA symbol`).to.eql({ + _kind: `cssVar`, + name: `--propA`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--propB`), `--propB symbol`).to.eql({ + _kind: `cssVar`, + name: `--propB`, + global: false, + }); + }); + it(`should reuse css prop symbol between declaration usages`, () => { + const { sheets } = testStylableCore(` + .root { + --prop: green; + } + .part { + --prop: blue; + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + + expect(CSSCustomProperty.get(meta, `--prop`), `--prop symbol`).to.eql({ + _kind: `cssVar`, + name: `--prop`, + global: false, + }); + }); + it(`should collect global css props`, () => { + const { sheets } = testStylableCore(` + @property st-global(--propX); + + .root { + /* @decl(prop) --propX: green */ + --propX: green; + + /* @decl(value) prop: var(--propX) */ + prop: var(--propX); + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should report malformed syntax`, () => { + testStylableCore(` + /* + @atrule(no-dashes) propY + @analyze-warn(no-dashes) word(propY) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + 'propY' + )} + */ + @property propY { + syntax: ''; + initial-value: green; + inherits: false; + }; + + /* + @atrule(no-dashes-global) st-global(propZ) + @analyze-warn(no-dashes-global) word(propZ) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + 'propZ' + )} + */ + @property st-global(propZ) { + syntax: ''; + initial-value: green; + inherits: false; + }; + + .root { + /* + @decl(no-dashes) prop: var(propA) + @analyze-warn(no-dashes) word(propA) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + 'propA' + )} + */ + prop: var(propA); + + /* + @decl(space+text) prop: var(--entry-propB notAllowed, fallback) + @analyze-warn(space+text) word(--propB notAllowed, fallback) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_ARGS( + '--propB notAllowed, fallback' + )} + */ + prop: var(--propB notAllowed, fallback); + } + `); + }); + describe(`@st-global-custom-property (deprecated)`, () => { + it(`should mark properties as global`, () => { + testStylableCore(` + /* @analyze-info(first) ${CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ + @st-global-custom-property --x; + + /* @analyze-info(second) ${CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ + @st-global-custom-property --a ,--b, + --c , --d ; + + .root { + /* @decl(single) --x: var(--x) */ + --x: var(--x); + + /* @decl(spaced multiple) prop: var(--a) var(--b) var(--c) var(--d) */ + prop: var(--a) var(--b) var(--c) var(--d); + } + `); + }); + it(`should conflict with @property`, () => { + const symbolDiag = STSymbol.diagnostics; + // ToDo: report redeclare on on all definitions + const { sheets } = testStylableCore(` + /* @ToDo-analyze-warn(@property before) word(--before) + ${symbolDiag.REDECLARE_SYMBOL(`--before`)}*/ + @property --before { + syntax: ''; + initial-value: green; + inherits: false; + }; + + /* + @analyze-warn(before) word(--before) ${symbolDiag.REDECLARE_SYMBOL(`--before`)} + @ToDo-analyze-warn(after) word(--after) ${symbolDiag.REDECLARE_SYMBOL(`--after`)} + */ + @st-global-custom-property --before, --after; + + /* @ToDo-analyze-warn(@property after) word(--after) + ${symbolDiag.REDECLARE_SYMBOL(`--after`)}*/ + @property --after{ + syntax: ''; + initial-value: green; + inherits: false; + }; + + .root { + /* @decl(before global applied) --before: var(--before) */ + --before: var(--before); + + /* @decl(after global applied) --after: var(--after) */ + --after: var(--after); + } + `); + sheets; + }); + it(`should report malformed syntax`, () => { + testStylableCore(` + /* + @transform-remove(no-dashes) + @analyze-warn(no-dashes) word(propA) ${CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR( + 'propA' + )} + */ + @st-global-custom-property propA; + + /* + @transform-remove(missing comma) + @analyze-warn(missing comma) word(--propB --propC) ${CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( + '--propB --propC' + )} + */ + @st-global-custom-property --propB --propC; + `); + }); + }); + describe(`st-import`, () => { + it(`should resolve imported property to be set/get`, () => { + const { sheets } = testStylableCore({ + '/props.st.css': ` + .root { + --before: red; + --after: red; + } + `, + '/entry.st.css': ` + .root { + /* @decl(set before) --props-before: green */ + --before: green; + + /* @decl(get before) prop: var(--props-before) */ + prop: var(--before); + + /* @decl(fallback before) prop: var(--entry-local, var(--props-before)) */ + prop: var(--local, var(--before)); + } + + @st-import [--before, --after] from './props.st.css'; + + .root { + /* @decl(set after) --props-after: green */ + --after: green; + + /* @decl(get after) prop: var(--props-after) */ + prop: var(--after); + + /* @decl(fallback after) prop: var(--entry-local, var(--props-after)) */ + prop: var(--local, var(--after)); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should override imported with local definition`, () => { + // ToDo: add redeclare diagnostics to import + testStylableCore({ + '/props.st.css': ` + .root { + --before: red; + --after: red; + } + `, + '/entry.st.css': ` + /* @analyze-warn(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`--before`)} */ + @property --before; + + /* + ToDo: fix + @ToDo-analyze-warn(imported before) word(--before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + `--before` + )} + @ToDo-analyze-warn(imported after) word(--after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + `--after` + )} + */ + @st-import [--before, --after] from "./props.st.css"; + + /* @analyze-warn(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`--after`)} */ + @property --after; + + .root { + /* @decl prop1: var(--entry-before)*/ + prop1: var(--before); + + /* @decl prop2: var(--entry-after)*/ + prop2: var(--after); + } + `, + }); + // ToDo: check symbols and exports + }); + it(`should resolve mapped property`, () => { + const { sheets } = testStylableCore({ + '/props.st.css': ` + .root { + --a: red; + } + `, + '/entry.st.css': ` + @st-import [--a as --mapped] from './props.st.css'; + + .root { + /* @decl(set) --props-a: green */ + --mapped: green; + + /* @decl(get) prop: var(--props-a) */ + prop: var(--mapped); + + /* @decl(fallback) prop: var(--entry-local, var(--props-a)) */ + prop: var(--local, var(--mapped)); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should resolve global property`, () => { + const { sheets } = testStylableCore({ + '/props.st.css': ` + @property st-global(--a); + `, + '/entry.st.css': ` + @st-import [--a] from './props.st.css'; + + .root { + /* @decl(set) --a: green */ + --a: green; + + /* @decl(get) prop: var(--a) */ + prop: var(--a); + + /* @decl(fallback) prop: var(--entry-local, var(--a)) */ + prop: var(--local, var(--a)); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should handle unresolved property`, () => { + testStylableCore({ + '/props.st.css': ``, + '/entry.st.css': ` + /* @transform-warn word(--unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + '--unknown', + './props.st.css' + )} */ + @st-import [--unknown] from './props.st.css'; + `, + }); + }); + }); + describe(`st-vars`, () => { + it(`should resolve a value to be set`, () => { + const { sheets } = testStylableCore(` + :vars { + blue: blue; + green: green; + } + + .root { + /* @decl(single) --entry-a: blue */ + --a: value(blue); + + /* @decl(concat vars) --entry-b: green blue */ + --b: value(green) value(blue); + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should resolve a fallback value`, () => { + const { sheets } = testStylableCore(` + :vars { + blue: blue; + green: green; + } + + .root { + /* @decl(single fallback) prop: var(--entry-color, blue) */ + prop: var(--color, value(blue)); + + /* @decl(concat vars) prop: var(--entry-twoColors, green blue) */ + prop: var(--twoColors, value(green) value(blue)); + + /* @decl(nested fallbacks) + prop: var(--entry-a, var(--entry-b, var(--entry-c), green), blue) + */ + prop: var( + --a, + var( + --b, + var(--c), + value(green) + ), + value(blue) + ); + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + }); + describe(`st-formatter`, () => { + it(`should resolve a value to be set`, () => { + const { sheets } = testStylableCore({ + '/formatter.js': ` + module.exports = function(arg) { + return arg; + } + `, + '/entry.st.css': ` + @st-import print from './formatter'; + + .root { + /* @decl --entry-a: blue */ + --a: print(blue); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should resolve a fallback value`, () => { + const { sheets } = testStylableCore({ + '/formatter.js': ` + module.exports = function(arg) { + return arg; + } + `, + '/entry.st.css': ` + @st-import print from './formatter'; + + .root { + /* @decl(single fallback) prop: var(--entry-color, blue) */ + prop: var(--color, print(blue)); + + /* @decl(nested fallbacks) + prop: var(--entry-a, var(--entry-b, var(--entry-c), green), blue) + */ + prop: var( + --a, + var( + --b, + var(--c), + print(green) + ), + print(blue) + ); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + }); + describe(`st-mixin`, () => { + it(`should resolve the mixin origin css property symbol`, () => { + const { sheets } = testStylableCore({ + '/imported.st.css': ` + .mix { + prop: var(--a); + } + `, + '/entry.st.css': ` + @st-import [mix as imported] from './imported.st.css'; + + .local-mix { + prop: var(--a); + } + + /* @rule(imported) .entry__root { prop: var(--imported-a) } */ + .root { + -st-mixin: imported; + } + + /* @rule(local) .entry__root { prop: var(--entry-a) } */ + .root { + -st-mixin: local-mix; + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + }); +}); From f61a55ece544ce07dd978023566f5a721eef16a4 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Sun, 16 Jan 2022 19:05:37 +0200 Subject: [PATCH 11/20] test: move more tests to feature spec - `exports.spec` css custom property tests - test for resolving mixed-in property override - moved skipped escape test --- .../test/features/css-custom-property.spec.ts | 74 ++++++- .../test/stylable-transformer/exports.spec.ts | 196 ------------------ 2 files changed, 72 insertions(+), 198 deletions(-) diff --git a/packages/core/test/features/css-custom-property.spec.ts b/packages/core/test/features/css-custom-property.spec.ts index 9c09f40ac..2f3a722d8 100644 --- a/packages/core/test/features/css-custom-property.spec.ts +++ b/packages/core/test/features/css-custom-property.spec.ts @@ -14,10 +14,11 @@ describe(`features/css-custom-property`, () => { } `); - const { meta } = sheets['/entry.st.css']; + const { meta, exports } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); + // symbols expect(CSSCustomProperty.get(meta, `--propA`), `--propA symbol`).to.eql({ _kind: `cssVar`, name: `--propA`, @@ -29,6 +30,10 @@ describe(`features/css-custom-property`, () => { global: false, }); + // JS exports + expect(exports.vars.propA, `propA JS export`).to.eql(`--entry-propA`); + expect(exports.vars.propB, `propB JS export`).to.eql(`--entry-propB`); + // deprecation expect(meta.cssVars, `deprecated 'meta.cssVars'`).to.eql({ '--propA': CSSCustomProperty.get(meta, `--propA`), @@ -219,6 +224,19 @@ describe(`features/css-custom-property`, () => { } `); }); + it.skip(`should escape`, () => { + const { sheets } = testStylableCore(` + .root { + /* @decl --entry-aa\\.bb: var(--entry-cc\\{dd) */ + --aa\\.bb: var(--cc\\{dd); + } + `); + + const { exports } = sheets['/entry.st.css']; + + expect(exports.vars[`aa.bb`], `JS export prop`).to.eql(`--entry-aa\\.bb`); + expect(exports.vars[`cc.dd`], `JS export value`).to.eql(`--entry-cc\\.dd`); + }); describe(`@st-global-custom-property (deprecated)`, () => { it(`should mark properties as global`, () => { testStylableCore(` @@ -295,7 +313,7 @@ describe(`features/css-custom-property`, () => { }); }); describe(`st-import`, () => { - it(`should resolve imported property to be set/get`, () => { + it(`should resolve imported property to set/get`, () => { const { sheets } = testStylableCore({ '/props.st.css': ` .root { @@ -334,6 +352,33 @@ describe(`features/css-custom-property`, () => { shouldReportNoDiagnostics(meta); }); + it(`should re-export imported symbols`, () => { + const { sheets } = testStylableCore({ + '/base.st.css': ` + .root { + --deepProp: red; + } + `, + '/props.st.css': ` + @st-import [--deepProp] from './base.st.css'; + .root { + --propA: red; + --propB: red; + } + `, + '/entry.st.css': ` + @st-import [--propA, --propB as --local, --deepProp] from './props.st.css'; + `, + }); + + const { meta, exports } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + + expect(exports.vars.propA, `propA export`).to.eql(`--props-propA`); + expect(exports.vars.local, `mapped export`).to.eql(`--props-propB`); + expect(exports.vars.deepProp, `deep export`).to.eql(`--base-deepProp`); + }); it(`should override imported with local definition`, () => { // ToDo: add redeclare diagnostics to import testStylableCore({ @@ -578,6 +623,31 @@ describe(`features/css-custom-property`, () => { const { meta } = sheets['/entry.st.css']; + shouldReportNoDiagnostics(meta); + }); + it(`should resolve property as stylable var replacement`, () => { + const { sheets } = testStylableCore(` + :vars { + stVar: green; + } + + .mix {} + .mix:hover { + color: var(--a, value(stVar)); + } + + /* @rule[1] .entry__root:hover { + color: var(--entry-a, var(--entry-b)) + } */ + .root { + -st-mixin: mix( + stVar var(--b) + ); + } + `); + + const { meta } = sheets['/entry.st.css']; + shouldReportNoDiagnostics(meta); }); }); diff --git a/packages/core/test/stylable-transformer/exports.spec.ts b/packages/core/test/stylable-transformer/exports.spec.ts index 786fdc71e..b2fd7fdd7 100644 --- a/packages/core/test/stylable-transformer/exports.spec.ts +++ b/packages/core/test/stylable-transformer/exports.spec.ts @@ -579,202 +579,6 @@ describe('Exports to js', () => { }); }); - describe('css vars', () => { - it('exports native css vars defined locally', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --myVar: green; - } - `, - }, - }, - }); - - expect(cssExports.vars).to.eql({ - myVar: '--entry-myVar', - }); - }); - - it('re-exports imported native css vars defined in a different stylesheet', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./imported.st.css"; - -st-named: --myVar; - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --myVar: green; - } - `, - }, - }, - }); - - expect(cssExports.vars).to.eql({ - myVar: '--imported-myVar', - }); - }); - - it('re-exports imported native css vars imported using "named as"', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./imported.st.css"; - -st-named: --myVar as --renamed; - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - .root { - --myVar: green; - } - `, - }, - }, - }); - - expect(cssExports.vars).to.eql({ - renamed: '--imported-myVar', - }); - }); - - it('exports css vars from mixed local and imported stylesheets with multiple levels', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./mid.st.css"; - -st-named: --midVar, --baseVar; - } - .root { - --topVar: blue; - } - `, - }, - '/mid.st.css': { - namespace: 'mid', - content: ` - :import { - -st-from: "./base.st.css"; - -st-named: --baseVar; - } - .root { - --midVar: green; - } - `, - }, - '/base.st.css': { - namespace: 'base', - content: ` - .root { - --baseVar: red; - } - `, - }, - }, - }); - - expect(cssExports.vars).to.eql({ - baseVar: '--base-baseVar', - midVar: '--mid-midVar', - topVar: '--entry-topVar', - }); - }); - - it('exports from mixed local and imported stylesheets with scoped and global css vars', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :import { - -st-from: "./imported.st.css"; - -st-named: --importedGlobal1, --importedGlobal2, --importedScoped1, --importedScoped2; - } - - @st-global-custom-property --localGlobal1, --localGlobal2; - - .root { - --localScoped1: 5; - --localScoped2: 6; - --localGlobal1: 7; - --localGlobal2: 8; - } - `, - }, - '/imported.st.css': { - namespace: 'imported', - content: ` - @st-global-custom-property --importedGlobal1, --importedGlobal2; - - .root { - --importedScoped1: 1; - --importedScoped2: 2; - --importedGlobal1: 3; - --importedGlobal2: 4; - } - `, - }, - }, - }); - - expect(cssExports.vars).to.eql({ - localScoped1: '--entry-localScoped1', - localScoped2: '--entry-localScoped2', - localGlobal1: '--localGlobal1', - localGlobal2: '--localGlobal2', - importedScoped1: '--imported-importedScoped1', - importedScoped2: '--imported-importedScoped2', - importedGlobal1: '--importedGlobal1', - importedGlobal2: '--importedGlobal2', - }); - }); - - it.skip('exports escaped css variable', () => { - const cssExports = generateStylableExports({ - entry: '/entry.st.css', - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .root { - --my\\.Var: green; - } - `, - }, - }, - }); - // ToDo: fix for https://github.com/wix/stylable/issues/1932 - expect(cssExports.vars).to.eql({ - 'my\\.Var': `--entry-my.Var`, - }); - }); - }); - describe('complex example', () => { it('with classes, vars, st-vars, and keyframes', () => { const cssExports = generateStylableExports({ From 495482f22dcb13815a27ce73b11cc05cde6fdf42 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 17 Jan 2022 11:37:35 +0200 Subject: [PATCH 12/20] test: add expectations for symbols & exports --- .../test/features/css-custom-property.spec.ts | 132 +++++++++++------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/packages/core/test/features/css-custom-property.spec.ts b/packages/core/test/features/css-custom-property.spec.ts index 2f3a722d8..7fbd73888 100644 --- a/packages/core/test/features/css-custom-property.spec.ts +++ b/packages/core/test/features/css-custom-property.spec.ts @@ -70,48 +70,22 @@ describe(`features/css-custom-property`, () => { } `); - const { meta } = sheets['/entry.st.css']; + const { meta, exports } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); - expect(CSSCustomProperty.get(meta, `--colorA`), `--colorA symbol`).to.eql({ - _kind: `cssVar`, - name: `--colorA`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--colorB`), `--colorB symbol`).to.eql({ - _kind: `cssVar`, - name: `--colorB`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--colorC`), `--colorC symbol`).to.eql({ - _kind: `cssVar`, - name: `--colorC`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--a`), `--a symbol`).to.eql({ - _kind: `cssVar`, - name: `--a`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--b`), `--b symbol`).to.eql({ - _kind: `cssVar`, - name: `--b`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--c`), `--c symbol`).to.eql({ - _kind: `cssVar`, - name: `--c`, - global: false, - }); - expect(CSSCustomProperty.get(meta, `--d`), `--d symbol`).to.eql({ - _kind: `cssVar`, - name: `--d`, - global: false, + + // symbols + [`--colorA`, `--colorB`, `--colorC`, `--a`, `--b`, `--c`, `--d`, `--e`].forEach((name) => { + expect(CSSCustomProperty.get(meta, name), `${name} symbol`).to.eql({ + _kind: `cssVar`, + name, + global: false, + }); }); - expect(CSSCustomProperty.get(meta, `--e`), `--e symbol`).to.eql({ - _kind: `cssVar`, - name: `--e`, - global: false, + + // JS exports + [`colorA`, `colorB`, `colorC`, `a`, `b`, `c`, `d`, `e`].forEach((name) => { + expect(exports.vars[name], `${name} JS export`).to.eql(`--entry-${name}`); }); }); it(`should process @property definitions`, () => { @@ -127,10 +101,11 @@ describe(`features/css-custom-property`, () => { @property --propB; `); - const { meta } = sheets['/entry.st.css']; + const { meta, exports } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); + // symbols expect(CSSCustomProperty.get(meta, `--propA`), `--propA symbol`).to.eql({ _kind: `cssVar`, name: `--propA`, @@ -141,13 +116,19 @@ describe(`features/css-custom-property`, () => { name: `--propB`, global: false, }); + + // JS exports + expect(exports.vars.propA, `propA JS export`).to.eql(`--entry-propA`); + expect(exports.vars.propB, `propB JS export`).to.eql(`--entry-propB`); }); it(`should reuse css prop symbol between declaration usages`, () => { const { sheets } = testStylableCore(` .root { + /* @decl --entry-prop: green */ --prop: green; } .part { + /* @decl --entry-prop: blue */ --prop: blue; } `); @@ -155,12 +136,6 @@ describe(`features/css-custom-property`, () => { const { meta } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); - - expect(CSSCustomProperty.get(meta, `--prop`), `--prop symbol`).to.eql({ - _kind: `cssVar`, - name: `--prop`, - global: false, - }); }); it(`should collect global css props`, () => { const { sheets } = testStylableCore(` @@ -259,7 +234,7 @@ describe(`features/css-custom-property`, () => { it(`should conflict with @property`, () => { const symbolDiag = STSymbol.diagnostics; // ToDo: report redeclare on on all definitions - const { sheets } = testStylableCore(` + testStylableCore(` /* @ToDo-analyze-warn(@property before) word(--before) ${symbolDiag.REDECLARE_SYMBOL(`--before`)}*/ @property --before { @@ -290,7 +265,6 @@ describe(`features/css-custom-property`, () => { --after: var(--after); } `); - sheets; }); it(`should report malformed syntax`, () => { testStylableCore(` @@ -348,9 +322,25 @@ describe(`features/css-custom-property`, () => { `, }); - const { meta } = sheets['/entry.st.css']; + const { meta, exports } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); + + // symbols + expect(CSSCustomProperty.get(meta, `--before`), `--before symbol`).to.eql({ + _kind: `cssVar`, + name: `--before`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--after`), `--after symbol`).to.eql({ + _kind: `cssVar`, + name: `--after`, + global: false, + }); + + // JS exports + expect(exports.vars.before, `before JS export`).to.eql(`--props-before`); + expect(exports.vars.after, `after JS export`).to.eql(`--props-after`); }); it(`should re-export imported symbols`, () => { const { sheets } = testStylableCore({ @@ -375,13 +365,14 @@ describe(`features/css-custom-property`, () => { shouldReportNoDiagnostics(meta); + // JS exports expect(exports.vars.propA, `propA export`).to.eql(`--props-propA`); expect(exports.vars.local, `mapped export`).to.eql(`--props-propB`); expect(exports.vars.deepProp, `deep export`).to.eql(`--base-deepProp`); }); it(`should override imported with local definition`, () => { // ToDo: add redeclare diagnostics to import - testStylableCore({ + const { sheets } = testStylableCore({ '/props.st.css': ` .root { --before: red; @@ -415,7 +406,24 @@ describe(`features/css-custom-property`, () => { } `, }); - // ToDo: check symbols and exports + + const { meta, exports } = sheets['/entry.st.css']; + + // symbols + expect(CSSCustomProperty.get(meta, `--before`), `--before symbol`).to.eql({ + _kind: `cssVar`, + name: `--before`, + global: false, + }); + expect(CSSCustomProperty.get(meta, `--after`), `--after symbol`).to.eql({ + _kind: `cssVar`, + name: `--after`, + global: false, + }); + + // JS exports + expect(exports.vars.before, `before JS export`).to.eql(`--entry-before`); + expect(exports.vars.after, `after JS export`).to.eql(`--entry-after`); }); it(`should resolve mapped property`, () => { const { sheets } = testStylableCore({ @@ -440,9 +448,19 @@ describe(`features/css-custom-property`, () => { `, }); - const { meta } = sheets['/entry.st.css']; + const { meta, exports } = sheets['/entry.st.css']; shouldReportNoDiagnostics(meta); + + // symbols + expect(CSSCustomProperty.get(meta, `--mapped`), `--mapped symbol`).to.eql({ + _kind: `cssVar`, + name: `--mapped`, + global: false, + }); + + // JS exports + expect(exports.vars.mapped, `mapped JS export`).to.eql(`--props-a`); }); it(`should resolve global property`, () => { const { sheets } = testStylableCore({ @@ -470,7 +488,7 @@ describe(`features/css-custom-property`, () => { shouldReportNoDiagnostics(meta); }); it(`should handle unresolved property`, () => { - testStylableCore({ + const { sheets } = testStylableCore({ '/props.st.css': ``, '/entry.st.css': ` /* @transform-warn word(--unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( @@ -480,6 +498,14 @@ describe(`features/css-custom-property`, () => { @st-import [--unknown] from './props.st.css'; `, }); + + const { meta, exports } = sheets['/entry.st.css']; + + // symbols + expect(CSSCustomProperty.get(meta, `--unknown`), `--unknown symbol`).to.eql(undefined); + + // JS exports + expect(exports.vars.unknown, `unknown JS export`).to.eql(undefined); }); }); describe(`st-vars`, () => { From f7a04f3062d84b697079d11422be6e61773a1a27 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Tue, 18 Jan 2022 17:29:25 +0200 Subject: [PATCH 13/20] test: move `at-property.spec` -> feature spec --- .../test/features/css-custom-property.spec.ts | 73 ++++ .../stylable-transformer/at-property.spec.ts | 317 ------------------ 2 files changed, 73 insertions(+), 317 deletions(-) delete mode 100644 packages/core/test/stylable-transformer/at-property.spec.ts diff --git a/packages/core/test/features/css-custom-property.spec.ts b/packages/core/test/features/css-custom-property.spec.ts index 7fbd73888..ee2e36d0a 100644 --- a/packages/core/test/features/css-custom-property.spec.ts +++ b/packages/core/test/features/css-custom-property.spec.ts @@ -212,6 +212,79 @@ describe(`features/css-custom-property`, () => { expect(exports.vars[`aa.bb`], `JS export prop`).to.eql(`--entry-aa\\.bb`); expect(exports.vars[`cc.dd`], `JS export value`).to.eql(`--entry-cc\\.dd`); }); + describe(`@property validation`, () => { + it(`should report on missing syntax`, () => { + const { sheets } = testStylableCore(` + /* @analyze-warn(syntax) word(--a) ${atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR( + 'syntax' + )} */ + @property --a { + inherits: true; + initial-value: #c0ffee; + } + + /* @analyze-warn(inherits) word(--b) ${atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR( + 'inherits' + )} */ + @property --b { + syntax: ''; + initial-value: #c0ffee; + } + + /* @analyze-warn(inherits) word(--c) ${atPropertyValidationWarnings.MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR()} */ + @property --c { + syntax: ''; + inherits: false; + } + + /* no error for syntax="*" and missing initial-value */ + @property --d { + syntax: '*'; + inherits: false; + } + `); + + const { meta } = sheets['/entry.st.css']; + + expect(meta.diagnostics.reports.length, `no unexpected`).to.eql(3); + }); + it(`should report invlid descriptor node type`, () => { + testStylableCore(` + @property --x { + syntax: '*'; + inherits: false; + + /* @analyze-warn(atrule) word(abc) ${atPropertyValidationWarnings.INVALID_DESCRIPTOR_TYPE( + 'atrule' + )} */ + @some-at-rule abc{} + } + + @property --x { + syntax: '*'; + inherits: false; + + /* @analyze-warn(rule) word(div) ${atPropertyValidationWarnings.INVALID_DESCRIPTOR_TYPE( + 'rule' + )} */ + div {} + } + `); + }); + it(`should report invalid descriptor`, () => { + testStylableCore(` + @property --x { + syntax: '*'; + inherits: false; + + /* @analyze-warn word(initialValue) ${atPropertyValidationWarnings.INVALID_DESCRIPTOR_NAME( + 'initialValue' + )} */ + initialValue: red; + } + `); + }); + }); describe(`@st-global-custom-property (deprecated)`, () => { it(`should mark properties as global`, () => { testStylableCore(` diff --git a/packages/core/test/stylable-transformer/at-property.spec.ts b/packages/core/test/stylable-transformer/at-property.spec.ts deleted file mode 100644 index e9baa0b48..000000000 --- a/packages/core/test/stylable-transformer/at-property.spec.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { expectTransformDiagnostics, generateStylableResult } from '@stylable/core-test-kit'; -import { expect } from 'chai'; -import type * as postcss from 'postcss'; -import { atPropertyValidationWarnings } from '@stylable/core/dist/helpers/css-custom-property'; -import { STSymbol } from '@stylable/core/dist/features'; - -describe('@property support', () => { - it('should transform @property definition', () => { - const { meta } = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @st-global-custom-property --global; - - @property --global { - syntax: ""; - inherits: false; - initial-value: 0px; - } - - @property --radius { - syntax: ""; - inherits: false; - initial-value: 0px; - } - - .root { - --radius: 10px; - --global: 20px; - } - - `, - }, - }, - }); - - const prop1 = meta.outputAst!.nodes[0] as postcss.AtRule; - const prop2 = meta.outputAst!.nodes[1] as postcss.AtRule; - - expect(prop1.params).to.equal('--global'); - expect(prop2.params).to.equal('--entry-radius'); - }); - - it('should remove at property when used without a body', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property --x { - syntax: ''; - inherits: false; - initial-value: #c0ffee; - } - - @property --y; - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, []); - - const { nodes } = result.meta.outputAst!; - const atProperty = nodes[0] as postcss.AtRule; - - expect(nodes).to.have.length(1); - expect(atProperty.params).to.eql('--entry-x'); - expect(atProperty.nodes.length).to.be.greaterThan(0); - expect(result.exports.vars).to.eql({ - x: '--entry-x', - y: '--entry-y', - }); - }); - - it('should detect and export @property definition', () => { - const { exports, meta } = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - - @property --my-var { - syntax: ""; - inherits: false; - initial-value: 0px; - } - - `, - }, - }, - }); - - const prop1 = meta.outputAst!.nodes[0] as postcss.AtRule; - - expect(prop1.params).to.equal('--entry-my-var'); - - expect(exports.vars).to.eql({ - 'my-var': '--entry-my-var', - }); - }); - it('should detect existing css variable and show warning', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/a.st.css': { - namespace: 'a', - content: ` - .root { - --my-var: red; - } - `, - }, - '/entry.st.css': { - namespace: 'entry', - content: ` - @st-import [--my-var] from "./a.st.css"; - - |@property $--my-var$|; - `, - }, - }, - }; - - expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: STSymbol.diagnostics.REDECLARE_SYMBOL('--my-var'), - }, - ]); - }); - - describe('validation', () => { - it('should emit warning when used invalid descriptor type', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @st-import [--y] from "./secondary.st.css"; - - @property --x { - syntax: '*'; - inherits: false; - |@st-scope $.root$ {| - - } - } - - `, - }, - '/secondary.st.css': { - namespace: 'entry', - content: ` - @property --y { - syntax: '*'; - inherits: false; - |$.my-class$ {| - color: red; - } - } - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: atPropertyValidationWarnings.INVALID_DESCRIPTOR_TYPE('atrule'), - }, - { - file: '/secondary.st.css', - message: atPropertyValidationWarnings.INVALID_DESCRIPTOR_TYPE('rule'), - }, - ]); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - - it('should emit warning when used invalid descriptor name', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - @property --x { - syntax: '*'; - inherits: false; - |$initialValue$: red;| - } - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: atPropertyValidationWarnings.INVALID_DESCRIPTOR_NAME('initialValue'), - }, - ]); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - - it('should emit warning when used without "syntax" descriptor', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property $--x$ {| - inherits: true; - initial-value: #c0ffee; - } - - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('syntax'), - }, - ]); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - - it('should emit warning when used without "inherits" descriptor', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property $--x$ {| - syntax: ''; - initial-value: #c0ffee; - } - - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('inherits'), - }, - ]); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - - it('should emit warning when used without "initial-value" descriptor and "syntax" descriptor is not "*"', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property $--x$ {| - syntax: ''; - inherits: false; - } - - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, [ - { - file: '/entry.st.css', - message: - atPropertyValidationWarnings.MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR(), - }, - ]); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - - it('should detect valid at-property when used without "initial-value" descriptor and "syntax" descriptor is "*"', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - |@property $--x$ {| - syntax: '*'; - inherits: false; - } - - `, - }, - }, - }; - - const result = expectTransformDiagnostics(config, []); - - expect(result.meta.outputAst!.nodes).to.have.length(1); - }); - }); -}); From ea9d2f9467fc854681de588db2351e5dd60f6a28 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Wed, 19 Jan 2022 09:42:00 +0200 Subject: [PATCH 14/20] fix(core-test-kit): fix parse of multiline diagnostic expectation --- .../core-test-kit/src/inline-expectation.ts | 2 +- .../test/inline-expectations.spec.ts | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core-test-kit/src/inline-expectation.ts b/packages/core-test-kit/src/inline-expectation.ts index 14d50ea7a..a7b2bd74d 100644 --- a/packages/core-test-kit/src/inline-expectation.ts +++ b/packages/core-test-kit/src/inline-expectation.ts @@ -377,7 +377,7 @@ function diagnosticTest( errors: [], }; const matchResult = expectation.match( - /-(?\w+)(?