Skip to content

Commit

Permalink
Support raw values in visitors that return tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Feb 12, 2023
1 parent 6a53944 commit f9ed30f
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 10 deletions.
22 changes: 14 additions & 8 deletions node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,20 @@ type DeclarationVisitors = MappedDeclarationVisitors & {
custom?: CustomPropertyVisitors | DeclarationVisitor<CustomProperty>
}

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<Token, Name>) => TokenOrValue | TokenOrValue[] | void;
[Name in VisitableTokenTypes]?: (token: FindByType<Token, Name>) => 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
};
Expand Down Expand Up @@ -186,8 +192,8 @@ export interface Visitor<C extends CustomAtRules> {
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;
}
Expand Down Expand Up @@ -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 = {
Expand Down
37 changes: 35 additions & 2 deletions node/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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<TokensOrRaw> = env.from_js_value(res).map(serde_detach::detach)?;
Ok(res.map(|r| r.0))
} else {
Ok(None)
}
Expand Down Expand Up @@ -761,6 +765,35 @@ impl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de
}
}

struct TokensOrRaw<'i>(ValueOrVec<TokenOrValue<'i>>);

impl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<D::Error> = 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<V>: Index<usize, Output = V> + IndexMut<usize, Output = V> {
fn len(&self) -> usize;
fn remove(&mut self, i: usize);
Expand Down
45 changes: 45 additions & 0 deletions node/test/visitor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

0 comments on commit f9ed30f

Please sign in to comment.