From f9ed30f9ed8399d1599a9724eb55ce7e0c0ee8ba Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 12 Feb 2023 00:36:43 -0500 Subject: [PATCH] Support raw values in visitors that return tokens --- node/index.d.ts | 22 ++++++++++++------- node/src/transformer.rs | 37 +++++++++++++++++++++++++++++-- node/test/visitor.test.mjs | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/node/index.d.ts b/node/index.d.ts index 02319582..67946c1f 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -150,14 +150,20 @@ type DeclarationVisitors = MappedDeclarationVisitors & { custom?: CustomPropertyVisitors | DeclarationVisitor } -type TokenVisitor = (token: Token) => TokenOrValue | TokenOrValue[] | void; +interface RawValue { + /** A raw string value which will be parsed like CSS. */ + raw: string +} + +type TokenReturnValue = TokenOrValue | TokenOrValue[] | RawValue | void; +type TokenVisitor = (token: Token) => TokenReturnValue; type VisitableTokenTypes = 'ident' | 'at-keyword' | 'hash' | 'id-hash' | 'string' | 'number' | 'percentage' | 'dimension'; type TokenVisitors = { - [Name in VisitableTokenTypes]?: (token: FindByType) => TokenOrValue | TokenOrValue[] | void; + [Name in VisitableTokenTypes]?: (token: FindByType) => TokenReturnValue; } -type FunctionVisitor = (fn: Function) => TokenOrValue | TokenOrValue[] | void; -type EnvironmentVariableVisitor = (env: EnvironmentVariable) => TokenOrValue | TokenOrValue[] | void; +type FunctionVisitor = (fn: Function) => TokenReturnValue; +type EnvironmentVariableVisitor = (env: EnvironmentVariable) => TokenReturnValue; type EnvironmentVariableVisitors = { [name: string]: EnvironmentVariableVisitor }; @@ -186,8 +192,8 @@ export interface Visitor { Token?: TokenVisitor | TokenVisitors; Function?: FunctionVisitor | { [name: string]: FunctionVisitor }; FunctionExit?: FunctionVisitor | { [name: string]: FunctionVisitor }; - Variable?(variable: Variable): TokenOrValue | TokenOrValue[] | void; - VariableExit?(variable: Variable): TokenOrValue | TokenOrValue[] | void; + Variable?(variable: Variable): TokenReturnValue; + VariableExit?(variable: Variable): TokenReturnValue; EnvironmentVariable?: EnvironmentVariableVisitor | EnvironmentVariableVisitors; EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors; } @@ -278,9 +284,9 @@ export interface Warning { export interface CSSModulesConfig { /** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */ - pattern: string, + pattern?: string, /** Whether to rename dashed identifiers, e.g. custom properties. */ - dashedIdents: boolean + dashedIdents?: boolean } export type CSSModuleExports = { diff --git a/node/src/transformer.rs b/node/src/transformer.rs index ee9a34a6..5063ac07 100644 --- a/node/src/transformer.rs +++ b/node/src/transformer.rs @@ -6,13 +6,16 @@ use std::{ use lightningcss::{ media_query::MediaFeatureValue, properties::{ - custom::{Token, TokenOrValue}, + custom::{Token, TokenList, TokenOrValue}, Property, }, rules::{CssRule, CssRuleList}, + stylesheet::ParserOptions, + traits::ParseWithOptions, values::{ ident::Ident, length::{Length, LengthValue}, + string::CowArcStr, }, visitor::{Visit, VisitTypes, Visitor}, }; @@ -556,7 +559,8 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor { }; let res = visit.call(None, &[js_value])?; - env.from_js_value(res).map(serde_detach::detach) + let res: Option = env.from_js_value(res).map(serde_detach::detach)?; + Ok(res.map(|r| r.0)) } else { Ok(None) } @@ -761,6 +765,35 @@ impl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de } } +struct TokensOrRaw<'i>(ValueOrVec>); + +impl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::__private::de::ContentRefDeserializer; + + #[derive(serde::Deserialize)] + struct Raw<'i> { + #[serde(borrow)] + raw: CowArcStr<'i>, + } + + let content = serde::__private::de::Content::deserialize(deserializer)?; + let de: ContentRefDeserializer = ContentRefDeserializer::new(&content); + + if let Ok(res) = Raw::deserialize(de) { + let res = TokenList::parse_string_with_options(res.raw.as_ref(), ParserOptions::default()) + .map_err(|_| serde::de::Error::custom("Could not parse value"))?; + return Ok(TokensOrRaw(ValueOrVec::Vec(res.into_owned().0))); + } + + let de = ContentRefDeserializer::new(&content); + Ok(TokensOrRaw(ValueOrVec::deserialize(de)?)) + } +} + trait List: Index + IndexMut { fn len(&self) -> usize; fn remove(&mut self, i: usize); diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs index ae72c49b..49c09075 100644 --- a/node/test/visitor.test.mjs +++ b/node/test/visitor.test.mjs @@ -899,4 +899,49 @@ test('errors on invalid dashed idents', () => { }, 'Dashed idents must start with --'); }); +test('supports returning raw values for tokens', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + color: theme('red'); + } + `), + visitor: { + Function: { + theme() { + return { raw: 'rgba(255, 0, 0)' }; + } + } + } + }); + + assert.equal(res.code.toString(), '.foo{color:red}'); +}); + +test('supports returning raw values as variables', () => { + let res = transform({ + filename: 'test.css', + minify: true, + cssModules: { + dashedIdents: true + }, + code: Buffer.from(` + .foo { + color: theme('foo'); + } + `), + visitor: { + Function: { + theme() { + return { raw: 'var(--foo)' }; + } + } + } + }); + + assert.equal(res.code.toString(), '.EgL3uq_foo{color:var(--EgL3uq_foo)}'); +}); + test.run();