Skip to content

Commit 74cf822

Browse files
authored
Implement support for parsing custom at rules in JS bindings (parcel-bundler#395)
1 parent 813e98c commit 74cf822

37 files changed

+1764
-1011
lines changed

c/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ pub extern "C" fn lightningcss_stylesheet_parse(
285285
error_recovery: options.error_recovery,
286286
source_index: 0,
287287
warnings: Some(warnings.clone()),
288-
at_rule_parser: None,
289288
};
290289

291290
let stylesheet = unwrap!(StyleSheet::parse(code, opts), error, std::ptr::null_mut());

examples/custom_at_rule.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use lightningcss::{
1010
selector::{Component, Selector},
1111
stylesheet::{ParserOptions, PrinterOptions, StyleSheet},
1212
targets::Browsers,
13-
traits::ToCss,
13+
traits::{AtRuleParser, ToCss},
1414
values::{color::CssColor, length::LengthValue},
1515
vendor_prefix::VendorPrefix,
1616
visit_types,
@@ -21,17 +21,11 @@ fn main() {
2121
let args: Vec<String> = std::env::args().collect();
2222
let source = std::fs::read_to_string(&args[1]).unwrap();
2323
let opts = ParserOptions {
24-
at_rule_parser: Some(TailwindAtRuleParser),
2524
filename: args[1].clone(),
26-
nesting: true,
27-
custom_media: false,
28-
css_modules: None,
29-
error_recovery: false,
30-
warnings: None,
31-
source_index: 0,
25+
..Default::default()
3226
};
3327

34-
let mut stylesheet = StyleSheet::parse(&source, opts).unwrap();
28+
let mut stylesheet = StyleSheet::parse_with(&source, opts, &mut TailwindAtRuleParser).unwrap();
3529

3630
println!("{:?}", stylesheet);
3731

@@ -103,6 +97,7 @@ impl<'i> AtRuleParser<'i> for TailwindAtRuleParser {
10397
&mut self,
10498
name: CowRcStr<'i>,
10599
input: &mut Parser<'i, 't>,
100+
_options: &ParserOptions<'_, 'i>,
106101
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
107102
match_ignore_ascii_case! {&*name,
108103
"tailwind" => {
@@ -135,7 +130,12 @@ impl<'i> AtRuleParser<'i> for TailwindAtRuleParser {
135130
}
136131
}
137132

138-
fn rule_without_block(&mut self, prelude: Self::Prelude, start: &ParserState) -> Result<Self::AtRule, ()> {
133+
fn rule_without_block(
134+
&mut self,
135+
prelude: Self::Prelude,
136+
start: &ParserState,
137+
_options: &ParserOptions<'_, 'i>,
138+
) -> Result<Self::AtRule, ()> {
139139
let loc = start.source_location();
140140
match prelude {
141141
Prelude::Tailwind(directive) => Ok(AtRule::Tailwind(TailwindRule { directive, loc })),

node/ast.d.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6039,22 +6039,16 @@ export type SelectorComponent =
60396039
| (
60406040
| {
60416041
type: "namespace";
6042-
value: "none";
6042+
kind: "none";
60436043
}
60446044
| {
60456045
type: "namespace";
6046-
value: "any";
6047-
}
6048-
| {
6049-
type: "namespace";
6050-
url: string;
6051-
value: "default";
6046+
kind: "any";
60526047
}
60536048
| {
60546049
type: "namespace";
6050+
kind: "named";
60556051
prefix: string;
6056-
url: string;
6057-
value: "some";
60586052
}
60596053
)
60606054
| {
@@ -7042,7 +7036,7 @@ export interface StyleRule<D = Declaration> {
70427036
/**
70437037
* The declarations within the style rule.
70447038
*/
7045-
declarations: DeclarationBlock<D>;
7039+
declarations?: DeclarationBlock<D>;
70467040
/**
70477041
* The location of the rule in the source file.
70487042
*/

node/index.d.ts

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type { Angle, CssColor, Rule, CustomProperty, EnvironmentVariable, Function, Image, LengthValue, MediaQuery, Declaration, Ratio, Resolution, Selector, SupportsCondition, Time, Token, TokenOrValue, UnknownAtRule, Url, Variable, StyleRule, DeclarationBlock } from './ast';
1+
import type { Angle, CssColor, Rule, CustomProperty, EnvironmentVariable, Function, Image, LengthValue, MediaQuery, Declaration, Ratio, Resolution, Selector, SupportsCondition, Time, Token, TokenOrValue, UnknownAtRule, Url, Variable, StyleRule, DeclarationBlock, ParsedComponent, Multiplier } from './ast';
22
import type { Targets } from './targets';
33

44
export * from './ast';
55

6-
export interface TransformOptions {
6+
export interface TransformOptions<C extends CustomAtRules> {
77
/** The filename being transformed. Used for error messages and source maps. */
88
filename: string,
99
/** The source code to transform. */
@@ -55,7 +55,14 @@ export interface TransformOptions {
5555
* For optimal performance, visitors should be as specific as possible about what types of values
5656
* they care about so that JavaScript has to be called as little as possible.
5757
*/
58-
visitor?: Visitor
58+
visitor?: Visitor<C>,
59+
/**
60+
* Defines how to parse custom CSS at-rules. Each at-rule can have a prelude, defined using a CSS
61+
* [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings), and
62+
* a block body. The body can be a declaration list, rule list, or style block as defined in the
63+
* [css spec](https://drafts.csswg.org/css-syntax/#declaration-rule-list).
64+
*/
65+
customAtRules?: C
5966
}
6067

6168
// This is a hack to make TS still provide autocomplete for `property` vs. just making it `string`.
@@ -79,12 +86,54 @@ type MappedRuleVisitors = {
7986
[Name in Exclude<Rule['type'], 'unknown' | 'custom'>]?: RuleVisitor<RequiredValue<FindByType<Rule, Name>>>;
8087
}
8188

82-
type UnknownVisitors = {
83-
[name: string]: RuleVisitor<UnknownAtRule>
89+
type UnknownVisitors<T> = {
90+
[name: string]: RuleVisitor<T>
8491
}
8592

86-
type RuleVisitors = MappedRuleVisitors & {
87-
unknown?: UnknownVisitors | RuleVisitor<UnknownAtRule>
93+
type CustomVisitors<T extends CustomAtRules> = {
94+
[Name in keyof T]?: RuleVisitor<CustomAtRule<Name, T[Name]>>
95+
};
96+
97+
type AnyCustomAtRule<C extends CustomAtRules> = {
98+
[Key in keyof C]: CustomAtRule<Key, C[Key]>
99+
}[keyof C];
100+
101+
type RuleVisitors<C extends CustomAtRules> = MappedRuleVisitors & {
102+
unknown?: UnknownVisitors<UnknownAtRule> | Omit<RuleVisitor<UnknownAtRule>, keyof CallableFunction>,
103+
custom?: CustomVisitors<C> | Omit<RuleVisitor<AnyCustomAtRule<C>>, keyof CallableFunction>
104+
};
105+
106+
type PreludeTypes = Exclude<ParsedComponent['type'], 'literal' | 'repeated' | 'token'>;
107+
type SyntaxString = `<${PreludeTypes}>` | `<${PreludeTypes}>+` | `<${PreludeTypes}>#` | (string & {});
108+
type ComponentTypes = {
109+
[Key in PreludeTypes as `<${Key}>`]: FindByType<ParsedComponent, Key>
110+
};
111+
112+
type Repetitions = {
113+
[Key in PreludeTypes as `<${Key}>+` | `<${Key}>#`]: {
114+
type: "repeated",
115+
value: {
116+
components: FindByType<ParsedComponent, Key>[],
117+
multiplier: Multiplier
118+
}
119+
}
120+
};
121+
122+
type MappedPrelude = ComponentTypes & Repetitions;
123+
type MappedBody<P extends CustomAtRuleDefinition['body']> = P extends 'style-block' ? 'rule-list' : P;
124+
interface CustomAtRule<N, R extends CustomAtRuleDefinition> {
125+
name: N,
126+
prelude: R['prelude'] extends keyof MappedPrelude ? MappedPrelude[R['prelude']] : ParsedComponent,
127+
body: FindByType<CustomAtRuleBody, MappedBody<R['body']>>,
128+
loc: Location
129+
}
130+
131+
type CustomAtRuleBody = {
132+
type: 'declaration-list',
133+
value: Required<DeclarationBlock>
134+
} | {
135+
type: 'rule-list',
136+
value: RequiredValue<Rule>[]
88137
};
89138

90139
type FindProperty<Union, Name> = Union extends { property: Name } ? Union : never;
@@ -113,9 +162,9 @@ type EnvironmentVariableVisitors = {
113162
[name: string]: EnvironmentVariableVisitor
114163
};
115164

116-
export interface Visitor {
117-
Rule?: RuleVisitor | RuleVisitors;
118-
RuleExit?: RuleVisitor | RuleVisitors;
165+
export interface Visitor<C extends CustomAtRules> {
166+
Rule?: RuleVisitor | RuleVisitors<C>;
167+
RuleExit?: RuleVisitor | RuleVisitors<C>;
119168
Declaration?: DeclarationVisitor | DeclarationVisitors;
120169
DeclarationExit?: DeclarationVisitor | DeclarationVisitors;
121170
Url?(url: Url): Url | void;
@@ -143,14 +192,38 @@ export interface Visitor {
143192
EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;
144193
}
145194

195+
export interface CustomAtRules {
196+
[name: string]: CustomAtRuleDefinition
197+
}
198+
199+
export interface CustomAtRuleDefinition {
200+
/**
201+
* Defines the syntax for a custom at-rule prelude. The value should be a
202+
* CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings)
203+
* representing the types of values that are accepted. This property may be omitted or
204+
* set to null to indicate that no prelude is accepted.
205+
*/
206+
prelude?: SyntaxString | null,
207+
/**
208+
* Defines the type of body contained within the at-rule block.
209+
* - declaration-list: A CSS declaration list, as in a style rule.
210+
* - rule-list: A list of CSS rules, as supported within a non-nested
211+
* at-rule such as `@media` or `@supports`.
212+
* - style-block: Both a declaration list and rule list, as accepted within
213+
* a nested at-rule within a style rule (e.g. `@media` inside a style rule
214+
* with directly nested declarations).
215+
*/
216+
body?: 'declaration-list' | 'rule-list' | 'style-block' | null
217+
}
218+
146219
export interface DependencyOptions {
147220
/** Whether to preserve `@import` rules rather than removing them. */
148221
preserveImports?: boolean
149222
}
150223

151-
export type BundleOptions = Omit<TransformOptions, 'code'>;
224+
export type BundleOptions<C extends CustomAtRules> = Omit<TransformOptions<C>, 'code'>;
152225

153-
export interface BundleAsyncOptions extends BundleOptions {
226+
export interface BundleAsyncOptions<C extends CustomAtRules> extends BundleOptions<C> {
154227
resolver?: Resolver;
155228
}
156229

@@ -299,7 +372,7 @@ export interface ErrorLocation extends Location {
299372
* Compiles a CSS file, including optionally minifying and lowering syntax to the given
300373
* targets. A source map may also be generated, but this is not enabled by default.
301374
*/
302-
export declare function transform(options: TransformOptions): TransformResult;
375+
export declare function transform<C extends CustomAtRules>(options: TransformOptions<C>): TransformResult;
303376

304377
export interface TransformAttributeOptions {
305378
/** The filename in which the style attribute appeared. Used for error messages and dependencies. */
@@ -329,7 +402,7 @@ export interface TransformAttributeOptions {
329402
* For optimal performance, visitors should be as specific as possible about what types of values
330403
* they care about so that JavaScript has to be called as little as possible.
331404
*/
332-
visitor?: Visitor
405+
visitor?: Visitor<never>
333406
}
334407

335408
export interface TransformAttributeResult {
@@ -355,14 +428,14 @@ export declare function browserslistToTargets(browserslist: string[]): Targets;
355428
/**
356429
* Bundles a CSS file and its dependencies, inlining @import rules.
357430
*/
358-
export declare function bundle(options: BundleOptions): TransformResult;
431+
export declare function bundle<C extends CustomAtRules>(options: BundleOptions<C>): TransformResult;
359432

360433
/**
361434
* Bundles a CSS file and its dependencies asynchronously, inlining @import rules.
362435
*/
363-
export declare function bundleAsync(options: BundleAsyncOptions): Promise<TransformResult>;
436+
export declare function bundleAsync<C extends CustomAtRules>(options: BundleAsyncOptions<C>): Promise<TransformResult>;
364437

365438
/**
366439
* Composes multiple visitor objects into a single one.
367440
*/
368-
export declare function composeVisitors(visitors: Visitor[]): Visitor;
441+
export declare function composeVisitors<C extends CustomAtRules>(visitors: Visitor<C>[]): Visitor<C>;

0 commit comments

Comments
 (0)