feat(css/lints): Add selector-max-combinators rule (#3789)

This commit is contained in:
Pig Fang 2022-03-01 15:45:15 +08:00 committed by GitHub
parent 761fb9c244
commit e389bef3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 132 additions and 2 deletions

View File

@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
use crate::rules::{ use crate::rules::{
at_rule_no_unknown::AtRuleNoUnknownConfig, color_hex_length::ColorHexLengthConfig, at_rule_no_unknown::AtRuleNoUnknownConfig, color_hex_length::ColorHexLengthConfig,
no_invalid_position_at_import_rule::NoInvalidPositionAtImportRuleConfig, no_invalid_position_at_import_rule::NoInvalidPositionAtImportRuleConfig,
selector_max_class::SelectorMaxClassConfig, unit_no_unknown::UnitNoUnknownConfig, selector_max_class::SelectorMaxClassConfig,
selector_max_combinators::SelectorMaxCombinatorsConfig, unit_no_unknown::UnitNoUnknownConfig,
}; };
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -99,6 +100,9 @@ pub struct RulesConfig {
#[serde(default, alias = "unitNoUnknown")] #[serde(default, alias = "unitNoUnknown")]
pub unit_no_unknown: RuleConfig<UnitNoUnknownConfig>, pub unit_no_unknown: RuleConfig<UnitNoUnknownConfig>,
#[serde(default, alias = "selectorMaxCombinators")]
pub selector_max_combinators: RuleConfig<SelectorMaxCombinatorsConfig>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]

View File

@ -8,7 +8,8 @@ use crate::{
keyframe_declaration_no_important::keyframe_declaration_no_important, keyframe_declaration_no_important::keyframe_declaration_no_important,
no_empty_source::no_empty_source, no_empty_source::no_empty_source,
no_invalid_position_at_import_rule::no_invalid_position_at_import_rule, no_invalid_position_at_import_rule::no_invalid_position_at_import_rule,
selector_max_class::selector_max_class, unit_no_unknown::unit_no_unknown, selector_max_class::selector_max_class, selector_max_combinators::selector_max_combinators,
unit_no_unknown::unit_no_unknown,
}, },
}; };
@ -21,6 +22,7 @@ pub mod keyframe_declaration_no_important;
pub mod no_empty_source; pub mod no_empty_source;
pub mod no_invalid_position_at_import_rule; pub mod no_invalid_position_at_import_rule;
pub mod selector_max_class; pub mod selector_max_class;
pub mod selector_max_combinators;
pub mod unit_no_unknown; pub mod unit_no_unknown;
pub struct LintParams<'a> { pub struct LintParams<'a> {
@ -53,6 +55,9 @@ pub fn get_rules(LintParams { lint_config }: &LintParams) -> Vec<Box<dyn LintRul
(&rules_config.color_no_invalid_hex).into(), (&rules_config.color_no_invalid_hex).into(),
)); ));
rules.push(unit_no_unknown((&rules_config.unit_no_unknown).into())); rules.push(unit_no_unknown((&rules_config.unit_no_unknown).into()));
rules.push(selector_max_combinators(
(&rules_config.selector_max_combinators).into(),
));
rules rules
} }

View File

@ -0,0 +1,49 @@
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
pub type SelectorMaxCombinatorsConfig = Option<usize>;
pub fn selector_max_combinators(
ctx: LintRuleContext<SelectorMaxCombinatorsConfig>,
) -> Box<dyn LintRule> {
let max = ctx.config().unwrap_or(3);
visitor_rule(SelectorMaxCombinators { ctx, max })
}
#[derive(Debug, Default)]
struct SelectorMaxCombinators {
ctx: LintRuleContext<SelectorMaxCombinatorsConfig>,
max: usize,
}
impl SelectorMaxCombinators {
fn build_message(&self, count: usize) -> String {
let combinators = if self.max == 1 {
"combinator"
} else {
"combinators"
};
format!(
"Expected selector to have no more than {} {}, but {} actually.",
self.max, combinators, count
)
}
}
impl Visit for SelectorMaxCombinators {
fn visit_complex_selector(&mut self, complex_selector: &ComplexSelector) {
let count = complex_selector
.children
.iter()
.filter(|child| matches!(child, ComplexSelectorChildren::Combinator(..)))
.count();
if count > self.max {
self.ctx.report(complex_selector, self.build_message(count));
}
complex_selector.visit_children_with(self);
}
}

View File

@ -0,0 +1,5 @@
{
"rules": {
"selector-max-combinators": ["error", 2]
}
}

View File

@ -0,0 +1,5 @@
.a {}
.a .b {}
.a .b .c .d {}
.a + .b > .c ~ .d {}
.a ~ .b || .c + .d {}

View File

@ -0,0 +1,18 @@
error: Expected selector to have no more than 2 combinators, but 3 actually.
--> $DIR/tests/rules/fail/selector-max-combinators/custom/input.css:3:1
|
3 | .a .b .c .d {}
| ^^^^^^^^^^^
error: Expected selector to have no more than 2 combinators, but 3 actually.
--> $DIR/tests/rules/fail/selector-max-combinators/custom/input.css:4:1
|
4 | .a + .b > .c ~ .d {}
| ^^^^^^^^^^^^^^^^^
error: Expected selector to have no more than 2 combinators, but 3 actually.
--> $DIR/tests/rules/fail/selector-max-combinators/custom/input.css:5:1
|
5 | .a ~ .b || .c + .d {}
| ^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
{
"rules": {
"selector-max-combinators": ["error"]
}
}

View File

@ -0,0 +1,2 @@
.a ~ .b + .c .d > .e {}
.a .b .c .d .e {}

View File

@ -0,0 +1,12 @@
error: Expected selector to have no more than 3 combinators, but 4 actually.
--> $DIR/tests/rules/fail/selector-max-combinators/default/input.css:1:1
|
1 | .a ~ .b + .c .d > .e {}
| ^^^^^^^^^^^^^^^^^^^^
error: Expected selector to have no more than 3 combinators, but 4 actually.
--> $DIR/tests/rules/fail/selector-max-combinators/default/input.css:2:1
|
2 | .a .b .c .d .e {}
| ^^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
{
"rules": {
"selector-max-combinators": ["error", 5]
}
}

View File

@ -0,0 +1,7 @@
.a {}
.a .b {}
.a .b .c {}
.a .b .c .d {}
.a .b .c .d .e {}
.a + .b ~ .c > .d .e {}
.a + .b ~ .c > .d .e || .f {}

View File

@ -0,0 +1,5 @@
{
"rules": {
"selector-max-combinators": ["error"]
}
}

View File

@ -0,0 +1,8 @@
.a.b.c.d {}
.a .b.c.d {}
.a .b .c.d {}
.a .b .c .d {}
.a + .b .c > .d {}
.a ~ .b > .c + .d {}
.a .b + .c {}
.a + .b ~ .c > .d, .e > .f + .g ~ .h {}