refactor(css/lints): Simplify error reporting API (#3781)

This commit is contained in:
Pig Fang 2022-03-01 00:54:01 +08:00 committed by GitHub
parent bfc31c4bd1
commit b8211da1c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 140 additions and 253 deletions

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::rules::{
at_rule_no_unknown::AtRuleNoUnknownConfig, color_hex_length::ColorHexLengthConfig,
no_invalid_position_at_import_rule::NoInvalidPositionAtImportRuleConfig,
unit_no_unknown::UnitNoUnknownConfig,
selector_max_class::SelectorMaxClassConfig, unit_no_unknown::UnitNoUnknownConfig,
};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -64,10 +64,6 @@ impl<T: Debug + Clone + Serialize + Default> RuleConfig<T> {
pub(crate) fn get_rule_config(&self) -> &T {
&self.1
}
pub(crate) fn is_enabled(&self) -> bool {
!matches!(self.get_rule_reaction(), LintRuleReaction::Off)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -93,7 +89,7 @@ pub struct RulesConfig {
pub no_invalid_position_at_import_rule: RuleConfig<NoInvalidPositionAtImportRuleConfig>,
#[serde(default, alias = "selectorMaxClass")]
pub selector_max_class: RuleConfig<Option<usize>>,
pub selector_max_class: RuleConfig<SelectorMaxClassConfig>,
#[serde(default, alias = "colorHexLength")]
pub color_hex_length: RuleConfig<ColorHexLengthConfig>,

View File

@ -3,10 +3,16 @@ use std::{fmt::Debug, sync::Arc};
use auto_impl::auto_impl;
use parking_lot::Mutex;
use rayon::prelude::*;
use swc_common::errors::{Diagnostic, DiagnosticBuilder, Emitter, Handler, HANDLER};
use serde::Serialize;
use swc_common::{
errors::{Diagnostic, DiagnosticBuilder, Emitter, Handler, HANDLER},
Spanned,
};
use swc_css_ast::Stylesheet;
use swc_css_visit::{Visit, VisitWith};
use super::config::{LintRuleReaction, RuleConfig};
/// A lint rule.
///
/// # Implementation notes
@ -83,3 +89,50 @@ where
stylesheet.visit_with(&mut self.0);
}
}
#[derive(Debug, Clone, Default)]
pub struct LintRuleContext<C>
where
C: Debug + Clone + Serialize + Default,
{
reaction: LintRuleReaction,
config: C,
}
impl<C> LintRuleContext<C>
where
C: Debug + Clone + Serialize + Default,
{
pub(crate) fn report<N, S>(&self, ast_node: N, message: S)
where
N: Spanned,
S: AsRef<str>,
{
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler
.struct_span_err(ast_node.span(), message.as_ref())
.emit(),
LintRuleReaction::Warning => handler
.struct_span_warn(ast_node.span(), message.as_ref())
.emit(),
_ => {}
});
}
#[inline]
pub(crate) fn config(&self) -> &C {
&self.config
}
}
impl<C> From<&RuleConfig<C>> for LintRuleContext<C>
where
C: Debug + Clone + Serialize + Default,
{
fn from(config: &RuleConfig<C>) -> Self {
Self {
reaction: config.get_rule_reaction(),
config: config.get_rule_config().clone(),
}
}
}

View File

@ -1,12 +1,8 @@
use serde::{Deserialize, Serialize};
use swc_common::{errors::HANDLER, Spanned};
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -14,20 +10,14 @@ pub struct AtRuleNoUnknownConfig {
ignore_at_rules: Option<Vec<String>>,
}
pub fn at_rule_no_unknown(config: &RuleConfig<AtRuleNoUnknownConfig>) -> Box<dyn LintRule> {
visitor_rule(AtRuleNoUnknown {
reaction: config.get_rule_reaction(),
ignored: config
.get_rule_config()
.ignore_at_rules
.clone()
.unwrap_or_default(),
})
pub fn at_rule_no_unknown(ctx: LintRuleContext<AtRuleNoUnknownConfig>) -> Box<dyn LintRule> {
let ignored = ctx.config().ignore_at_rules.clone().unwrap_or_default();
visitor_rule(AtRuleNoUnknown { ctx, ignored })
}
#[derive(Debug, Default)]
struct AtRuleNoUnknown {
reaction: LintRuleReaction,
ctx: LintRuleContext<AtRuleNoUnknownConfig>,
ignored: Vec<String>,
}
@ -40,16 +30,7 @@ impl Visit for AtRuleNoUnknown {
if self.ignored.iter().all(|item| name != item) {
let message = format!("Unexpected unknown at-rule \"@{}\".", name);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler
.struct_span_err(unknown_at_rule.name.span(), &message)
.emit(),
LintRuleReaction::Warning => handler
.struct_span_warn(unknown_at_rule.name.span(), &message)
.emit(),
_ => {}
});
self.ctx.report(&unknown_at_rule.name, message);
}
unknown_at_rule.visit_children_with(self);

View File

@ -1,37 +1,23 @@
use swc_common::errors::HANDLER;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn block_no_empty(config: &RuleConfig<()>) -> Box<dyn LintRule> {
visitor_rule(BlockNoEmpty {
reaction: config.get_rule_reaction(),
})
pub fn block_no_empty(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
visitor_rule(BlockNoEmpty { ctx })
}
const MESSAGE: &str = "Unexpected empty block.";
#[derive(Debug, Default)]
struct BlockNoEmpty {
reaction: LintRuleReaction,
ctx: LintRuleContext<()>,
}
impl Visit for BlockNoEmpty {
fn visit_simple_block(&mut self, simple_block: &SimpleBlock) {
if simple_block.value.is_empty() {
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => {
handler.struct_span_err(simple_block.span, MESSAGE).emit()
}
LintRuleReaction::Warning => {
handler.struct_span_warn(simple_block.span, MESSAGE).emit()
}
_ => {}
});
self.ctx.report(simple_block, MESSAGE);
}
simple_block.visit_children_with(self);

View File

@ -1,12 +1,8 @@
use serde::{Deserialize, Serialize};
use swc_common::errors::HANDLER;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub type ColorHexLengthConfig = Option<HexForm>;
@ -23,16 +19,14 @@ impl Default for HexForm {
}
}
pub fn color_hex_length(config: &RuleConfig<ColorHexLengthConfig>) -> Box<dyn LintRule> {
visitor_rule(ColorHexLength {
reaction: config.get_rule_reaction(),
form: config.get_rule_config().clone().unwrap_or_default(),
})
pub fn color_hex_length(ctx: LintRuleContext<ColorHexLengthConfig>) -> Box<dyn LintRule> {
let form = ctx.config().clone().unwrap_or_default();
visitor_rule(ColorHexLength { ctx, form })
}
#[derive(Debug, Default)]
struct ColorHexLength {
reaction: LintRuleReaction,
ctx: LintRuleContext<ColorHexLengthConfig>,
form: HexForm,
}
@ -51,29 +45,13 @@ impl Visit for ColorHexLength {
HexForm::Long => {
if let Some(lengthened) = lengthen(&hex_color.value) {
let message = self.build_message(&hex_color.value, &lengthened);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => {
handler.struct_span_err(hex_color.span, &message).emit()
}
LintRuleReaction::Warning => {
handler.struct_span_warn(hex_color.span, &message).emit()
}
_ => {}
});
self.ctx.report(hex_color, message);
}
}
HexForm::Short => {
if let Some(shortened) = shorten(&hex_color.value) {
let message = self.build_message(&hex_color.value, &shortened);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => {
handler.struct_span_err(hex_color.span, &message).emit()
}
LintRuleReaction::Warning => {
handler.struct_span_warn(hex_color.span, &message).emit()
}
_ => {}
});
self.ctx.report(hex_color, message);
}
}
}

View File

@ -1,26 +1,20 @@
use swc_common::errors::HANDLER;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn color_no_invalid_hex(config: &RuleConfig<()>) -> Box<dyn LintRule> {
visitor_rule(ColorNoInvalidHex {
reaction: config.get_rule_reaction(),
})
pub fn color_no_invalid_hex(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
visitor_rule(ColorNoInvalidHex { ctx })
}
#[derive(Debug, Default)]
struct ColorNoInvalidHex {
reaction: LintRuleReaction,
ctx: LintRuleContext<()>,
}
impl Visit for ColorNoInvalidHex {
fn visit_hex_color(&mut self, hex_color: &HexColor) {
let HexColor { span, value, .. } = hex_color;
let HexColor { value, .. } = hex_color;
let length = value.len();
if (length == 3 || length == 4 || length == 6 || length == 8)
&& value.chars().all(|c| c.is_ascii_hexdigit())
@ -30,11 +24,7 @@ impl Visit for ColorNoInvalidHex {
}
let message = format!("Unexpected invalid hex color '#{}'.", value);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler.struct_span_err(*span, &message).emit(),
LintRuleReaction::Warning => handler.struct_span_warn(*span, &message).emit(),
_ => {}
});
self.ctx.report(hex_color, message);
hex_color.visit_children_with(self);
}

View File

@ -1,15 +1,12 @@
use swc_common::{errors::HANDLER, Span};
use swc_common::Span;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn declaration_no_important(config: &RuleConfig<()>) -> Box<dyn LintRule> {
pub fn declaration_no_important(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
visitor_rule(DeclarationNoImportant {
reaction: config.get_rule_reaction(),
ctx,
keyframe_rules: vec![],
})
}
@ -18,7 +15,7 @@ const MESSAGE: &str = "Unexpected '!important'.";
#[derive(Debug, Default)]
struct DeclarationNoImportant {
reaction: LintRuleReaction,
ctx: LintRuleContext<()>,
// rule interal
keyframe_rules: Vec<Span>,
@ -38,17 +35,7 @@ impl Visit for DeclarationNoImportant {
Some(span) if span.contains(important_flag.span) => {
// This rule doesn't check `!important` flag inside `@keyframe`.
}
_ => {
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => {
handler.struct_span_err(important_flag.span, MESSAGE).emit()
}
LintRuleReaction::Warning => handler
.struct_span_warn(important_flag.span, MESSAGE)
.emit(),
_ => {}
});
}
_ => self.ctx.report(important_flag, MESSAGE),
}
important_flag.visit_children_with(self);

View File

@ -1,15 +1,12 @@
use swc_common::{errors::HANDLER, Span};
use swc_common::Span;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn keyframe_declaration_no_important(config: &RuleConfig<()>) -> Box<dyn LintRule> {
pub fn keyframe_declaration_no_important(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
visitor_rule(KeyframeDeclarationNoImportant {
reaction: config.get_rule_reaction(),
ctx,
keyframe_rules: vec![],
})
}
@ -18,9 +15,9 @@ const MESSAGE: &str = "Unexpected '!important'.";
#[derive(Debug, Default)]
struct KeyframeDeclarationNoImportant {
reaction: LintRuleReaction,
ctx: LintRuleContext<()>,
// rule interal
// rule internal
keyframe_rules: Vec<Span>,
}
@ -36,15 +33,7 @@ impl Visit for KeyframeDeclarationNoImportant {
fn visit_important_flag(&mut self, important_flag: &ImportantFlag) {
match self.keyframe_rules.last() {
Some(span) if span.contains(important_flag.span) => {
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => {
handler.struct_span_err(important_flag.span, MESSAGE).emit()
}
LintRuleReaction::Warning => handler
.struct_span_warn(important_flag.span, MESSAGE)
.emit(),
_ => {}
});
self.ctx.report(important_flag, MESSAGE);
}
_ => {}
}

View File

@ -31,42 +31,28 @@ pub fn get_rules(LintParams { lint_config }: &LintParams) -> Vec<Box<dyn LintRul
let mut rules = vec![];
let rules_config = &lint_config.rules;
if rules_config.block_no_empty.is_enabled() {
rules.push(block_no_empty(&rules_config.block_no_empty));
}
if rules_config.at_rule_no_unknown.is_enabled() {
rules.push(at_rule_no_unknown(&rules_config.at_rule_no_unknown));
}
if rules_config.no_empty_source.is_enabled() {
rules.push(no_empty_source(&rules_config.no_empty_source));
}
if rules_config.declaration_no_important.is_enabled() {
rules.push(block_no_empty((&rules_config.block_no_empty).into()));
rules.push(at_rule_no_unknown(
(&rules_config.at_rule_no_unknown).into(),
));
rules.push(no_empty_source((&rules_config.no_empty_source).into()));
rules.push(declaration_no_important(
&rules_config.declaration_no_important,
(&rules_config.declaration_no_important).into(),
));
}
if rules_config.keyframe_declaration_no_important.is_enabled() {
rules.push(keyframe_declaration_no_important(
&rules_config.keyframe_declaration_no_important,
(&rules_config.keyframe_declaration_no_important).into(),
));
}
if rules_config.no_invalid_position_at_import_rule.is_enabled() {
rules.push(no_invalid_position_at_import_rule(
&rules_config.no_invalid_position_at_import_rule,
(&rules_config.no_invalid_position_at_import_rule).into(),
));
}
if rules_config.selector_max_class.is_enabled() {
rules.push(selector_max_class(&rules_config.selector_max_class));
}
if rules_config.color_hex_length.is_enabled() {
rules.push(color_hex_length(&rules_config.color_hex_length));
}
if rules_config.color_no_invalid_hex.is_enabled() {
rules.push(color_no_invalid_hex(&rules_config.color_no_invalid_hex));
}
if rules_config.unit_no_unknown.is_enabled() {
rules.push(unit_no_unknown(&rules_config.unit_no_unknown));
}
rules.push(selector_max_class(
(&rules_config.selector_max_class).into(),
));
rules.push(color_hex_length((&rules_config.color_hex_length).into()));
rules.push(color_no_invalid_hex(
(&rules_config.color_no_invalid_hex).into(),
));
rules.push(unit_no_unknown((&rules_config.unit_no_unknown).into()));
rules
}

View File

@ -1,23 +1,17 @@
use swc_common::errors::HANDLER;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn no_empty_source(config: &RuleConfig<()>) -> Box<dyn LintRule> {
visitor_rule(NoEmptySource {
reaction: config.get_rule_reaction(),
})
pub fn no_empty_source(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
visitor_rule(NoEmptySource { ctx })
}
const MESSAGE: &str = "Unexpected empty source.";
#[derive(Debug, Default)]
struct NoEmptySource {
reaction: LintRuleReaction,
ctx: LintRuleContext<()>,
}
impl Visit for NoEmptySource {
@ -25,13 +19,7 @@ impl Visit for NoEmptySource {
// TODO: we should allow comments here,
// but parser doesn't handle comments currently.
if stylesheet.rules.is_empty() {
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler.struct_span_err(stylesheet.span, MESSAGE).emit(),
LintRuleReaction::Warning => {
handler.struct_span_warn(stylesheet.span, MESSAGE).emit()
}
_ => {}
});
self.ctx.report(stylesheet, MESSAGE);
}
stylesheet.visit_children_with(self);

View File

@ -1,12 +1,8 @@
use serde::{Deserialize, Serialize};
use swc_common::{errors::HANDLER, Spanned};
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
const MESSAGE: &str = "Unexpected invalid position '@import' rule.";
@ -17,21 +13,15 @@ pub struct NoInvalidPositionAtImportRuleConfig {
}
pub fn no_invalid_position_at_import_rule(
config: &RuleConfig<NoInvalidPositionAtImportRuleConfig>,
ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
) -> Box<dyn LintRule> {
visitor_rule(NoInvalidPositionAtImportRule {
reaction: config.get_rule_reaction(),
ignored: config
.get_rule_config()
.ignore_at_rules
.clone()
.unwrap_or_default(),
})
let ignored = ctx.config().ignore_at_rules.clone().unwrap_or_default();
visitor_rule(NoInvalidPositionAtImportRule { ctx, ignored })
}
#[derive(Debug, Default)]
struct NoInvalidPositionAtImportRule {
reaction: LintRuleReaction,
ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
ignored: Vec<String>,
}
@ -39,13 +29,7 @@ impl Visit for NoInvalidPositionAtImportRule {
fn visit_stylesheet(&mut self, stylesheet: &Stylesheet) {
stylesheet.rules.iter().fold(false, |seen, rule| {
if seen && matches!(rule, Rule::AtRule(AtRule::Import(..))) {
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler.struct_span_err(rule.span(), MESSAGE).emit(),
LintRuleReaction::Warning => {
handler.struct_span_warn(rule.span(), MESSAGE).emit()
}
_ => {}
});
self.ctx.report(rule, MESSAGE);
}
match rule {

View File

@ -1,22 +1,18 @@
use swc_common::errors::HANDLER;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub fn selector_max_class(config: &RuleConfig<Option<usize>>) -> Box<dyn LintRule> {
visitor_rule(SelectorMaxClass {
reaction: config.get_rule_reaction(),
max: config.get_rule_config().unwrap_or(3),
})
pub(crate) type SelectorMaxClassConfig = Option<usize>;
pub fn selector_max_class(ctx: LintRuleContext<SelectorMaxClassConfig>) -> Box<dyn LintRule> {
let max = ctx.config().unwrap_or(3);
visitor_rule(SelectorMaxClass { ctx, max })
}
#[derive(Debug, Default)]
struct SelectorMaxClass {
reaction: LintRuleReaction,
ctx: LintRuleContext<SelectorMaxClassConfig>,
max: usize,
}
@ -51,15 +47,7 @@ impl Visit for SelectorMaxClass {
if count > self.max {
let message = self.build_message(count);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler
.struct_span_err(complex_selector.span, &message)
.emit(),
LintRuleReaction::Warning => handler
.struct_span_warn(complex_selector.span, &message)
.emit(),
_ => {}
});
self.ctx.report(complex_selector, message);
}
complex_selector.visit_children_with(self);

View File

@ -1,12 +1,8 @@
use serde::{Deserialize, Serialize};
use swc_common::{errors::HANDLER, Spanned};
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, LintRule},
};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -14,20 +10,14 @@ pub struct UnitNoUnknownConfig {
ignore_units: Option<Vec<String>>,
}
pub fn unit_no_unknown(config: &RuleConfig<UnitNoUnknownConfig>) -> Box<dyn LintRule> {
visitor_rule(UnitNoUnknown {
reaction: config.get_rule_reaction(),
ignored_units: config
.get_rule_config()
.ignore_units
.clone()
.unwrap_or_default(),
})
pub fn unit_no_unknown(ctx: LintRuleContext<UnitNoUnknownConfig>) -> Box<dyn LintRule> {
let ignored_units = ctx.config().ignore_units.clone().unwrap_or_default();
visitor_rule(UnitNoUnknown { ctx, ignored_units })
}
#[derive(Debug, Default)]
struct UnitNoUnknown {
reaction: LintRuleReaction,
ctx: LintRuleContext<UnitNoUnknownConfig>,
ignored_units: Vec<String>,
}
@ -37,16 +27,7 @@ impl Visit for UnitNoUnknown {
if self.ignored_units.iter().all(|item| unit != item) {
let message = format!("Unexpected unknown unit \"{}\".", unit);
HANDLER.with(|handler| match self.reaction {
LintRuleReaction::Error => handler
.struct_span_err(unknown_dimension.unit.span(), &message)
.emit(),
LintRuleReaction::Warning => handler
.struct_span_warn(unknown_dimension.unit.span(), &message)
.emit(),
_ => {}
});
self.ctx.report(&unknown_dimension.unit, message);
}
unknown_dimension.visit_children_with(self);