swc_css_lints/
rule.rs

1use std::{fmt::Debug, sync::Arc};
2
3use auto_impl::auto_impl;
4use par_iter::prelude::*;
5use parking_lot::Mutex;
6use serde::Serialize;
7use swc_common::{
8    errors::{Diagnostic, DiagnosticBuilder, Emitter, Handler, HANDLER},
9    Spanned,
10};
11use swc_css_ast::Stylesheet;
12use swc_css_visit::{Visit, VisitWith};
13
14use super::config::{LintRuleReaction, RuleConfig};
15
16/// A lint rule.
17///
18/// # Implementation notes
19///
20/// Must report error to [swc_common::HANDLER]
21#[auto_impl(Box, &mut)]
22pub trait LintRule: Debug + Send + Sync {
23    fn lint_stylesheet(&mut self, stylesheet: &Stylesheet);
24}
25
26/// This preserves the order of errors.
27impl<R> LintRule for Vec<R>
28where
29    R: LintRule,
30{
31    fn lint_stylesheet(&mut self, stylesheet: &Stylesheet) {
32        if cfg!(target_arch = "wasm32") {
33            for rule in self {
34                rule.lint_stylesheet(stylesheet);
35            }
36        } else {
37            let errors = self
38                .par_iter_mut()
39                .flat_map(|rule| {
40                    let emitter = Capturing::default();
41                    {
42                        let handler = Handler::with_emitter(true, false, Box::new(emitter.clone()));
43                        HANDLER.set(&handler, || {
44                            rule.lint_stylesheet(stylesheet);
45                        });
46                    }
47
48                    Arc::try_unwrap(emitter.errors).unwrap().into_inner()
49                })
50                .collect::<Vec<_>>();
51
52            HANDLER.with(|handler| {
53                for error in errors {
54                    DiagnosticBuilder::new_diagnostic(handler, error).emit();
55                }
56            });
57        }
58    }
59}
60
61#[derive(Default, Clone)]
62struct Capturing {
63    errors: Arc<Mutex<Vec<Diagnostic>>>,
64}
65
66impl Emitter for Capturing {
67    fn emit(&mut self, db: &mut DiagnosticBuilder<'_>) {
68        self.errors.lock().push(db.take());
69    }
70}
71
72pub(crate) fn visitor_rule<V>(reaction: LintRuleReaction, v: V) -> Box<dyn LintRule>
73where
74    V: 'static + Send + Sync + Visit + Default + Debug,
75{
76    Box::new(VisitorRule(v, reaction))
77}
78
79#[derive(Debug)]
80struct VisitorRule<V>(V, LintRuleReaction)
81where
82    V: Send + Sync + Visit;
83
84impl<V> LintRule for VisitorRule<V>
85where
86    V: Send + Sync + Visit + Debug,
87{
88    fn lint_stylesheet(&mut self, stylesheet: &Stylesheet) {
89        if !matches!(self.1, LintRuleReaction::Off) {
90            stylesheet.visit_with(&mut self.0);
91        }
92    }
93}
94
95#[derive(Debug, Clone, Default)]
96pub struct LintRuleContext<C>
97where
98    C: Debug + Clone + Serialize + Default,
99{
100    reaction: LintRuleReaction,
101    config: C,
102}
103
104impl<C> LintRuleContext<C>
105where
106    C: Debug + Clone + Serialize + Default,
107{
108    pub(crate) fn report<N, S>(&self, ast_node: N, message: S)
109    where
110        N: Spanned,
111        S: AsRef<str>,
112    {
113        HANDLER.with(|handler| match self.reaction {
114            LintRuleReaction::Error => handler
115                .struct_span_err(ast_node.span(), message.as_ref())
116                .emit(),
117            LintRuleReaction::Warning => handler
118                .struct_span_warn(ast_node.span(), message.as_ref())
119                .emit(),
120            _ => {}
121        });
122    }
123
124    #[inline]
125    pub(crate) fn config(&self) -> &C {
126        &self.config
127    }
128
129    #[inline]
130    pub(crate) fn reaction(&self) -> LintRuleReaction {
131        self.reaction
132    }
133}
134
135impl<C> From<&RuleConfig<C>> for LintRuleContext<C>
136where
137    C: Debug + Clone + Serialize + Default,
138{
139    fn from(config: &RuleConfig<C>) -> Self {
140        Self {
141            reaction: config.get_rule_reaction(),
142            config: config.get_rule_config().clone(),
143        }
144    }
145}