mirror of
https://github.com/swc-project/swc.git
synced 2024-10-04 04:07:18 +03:00
feat(css/lints): Allow using regex in ignore list (#3855)
This commit is contained in:
parent
3034e355ed
commit
dc0de58a46
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2924,11 +2924,13 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"swc_atoms",
|
||||
"swc_cached",
|
||||
"swc_common",
|
||||
"swc_css_ast",
|
||||
"swc_css_parser",
|
||||
"swc_css_visit",
|
||||
"testing",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
pub use anyhow::Error;
|
||||
use anyhow::{Context, Result};
|
||||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{de::Error, Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A regex which can be used as a configuration.
|
||||
///
|
||||
@ -47,6 +48,8 @@ impl<'de> Deserialize<'de> for CachedRegex {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
let s = String::deserialize(deserializer)?;
|
||||
|
||||
Self::new(&s).map_err(|err| D::Error::custom(err.to_string()))
|
||||
|
@ -17,9 +17,11 @@ parking_lot = "0.12.0"
|
||||
rayon = "1.5.1"
|
||||
serde = {version = "1.0.133", features = ["derive"]}
|
||||
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
|
||||
swc_cached = {version = "0.1.0", path = "../swc_cached"}
|
||||
swc_common = {version = "0.17.0", path = "../swc_common"}
|
||||
swc_css_ast = {version = "0.89.0", path = "../swc_css_ast"}
|
||||
swc_css_visit = {version = "0.88.0", path = "../swc_css_visit"}
|
||||
thiserror = "1.0.30"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.79"
|
||||
|
7
crates/swc_css_lints/src/error.rs
Normal file
7
crates/swc_css_lints/src/error.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ConfigError {
|
||||
#[error("invalid regex")]
|
||||
Regex(#[from] swc_cached::regex::Error),
|
||||
}
|
@ -2,9 +2,12 @@
|
||||
|
||||
mod config;
|
||||
mod dataset;
|
||||
mod error;
|
||||
mod pattern;
|
||||
mod rule;
|
||||
mod rules;
|
||||
|
||||
pub use config::LintConfig;
|
||||
pub(crate) use error::ConfigError;
|
||||
pub use rule::LintRule;
|
||||
pub use rules::{get_rules, LintParams};
|
||||
|
41
crates/swc_css_lints/src/pattern.rs
Normal file
41
crates/swc_css_lints/src/pattern.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use swc_cached::regex::CachedRegex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum NamePattern {
|
||||
Str(String),
|
||||
Regex(CachedRegex),
|
||||
}
|
||||
|
||||
impl NamePattern {
|
||||
pub(crate) fn is_match<S>(&self, name: S) -> bool
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let name = name.as_ref();
|
||||
match self {
|
||||
Self::Str(s) => s == name,
|
||||
Self::Regex(regex) => regex.is_match(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for NamePattern {
|
||||
type Error = swc_cached::regex::Error;
|
||||
|
||||
fn try_from(pattern: String) -> Result<Self, Self::Error> {
|
||||
if let Some(pattern) = pattern
|
||||
.strip_prefix('/')
|
||||
.and_then(|pattern| pattern.strip_suffix('/'))
|
||||
{
|
||||
CachedRegex::new(pattern).map(Self::Regex)
|
||||
} else {
|
||||
Ok(Self::Str(pattern))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NamePattern {
|
||||
fn default() -> Self {
|
||||
Self::Str(String::new())
|
||||
}
|
||||
}
|
@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||
use swc_css_ast::*;
|
||||
use swc_css_visit::{Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
|
||||
use crate::{
|
||||
pattern::NamePattern,
|
||||
rule::{visitor_rule, LintRule, LintRuleContext},
|
||||
ConfigError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -10,15 +14,27 @@ pub struct AtRuleNoUnknownConfig {
|
||||
ignore_at_rules: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
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(ctx.reaction(), AtRuleNoUnknown { ctx, ignored })
|
||||
pub fn at_rule_no_unknown(
|
||||
ctx: LintRuleContext<AtRuleNoUnknownConfig>,
|
||||
) -> Result<Box<dyn LintRule>, ConfigError> {
|
||||
let ignored = ctx
|
||||
.config()
|
||||
.ignore_at_rules
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(NamePattern::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(visitor_rule(
|
||||
ctx.reaction(),
|
||||
AtRuleNoUnknown { ctx, ignored },
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct AtRuleNoUnknown {
|
||||
ctx: LintRuleContext<AtRuleNoUnknownConfig>,
|
||||
ignored: Vec<String>,
|
||||
ignored: Vec<NamePattern>,
|
||||
}
|
||||
|
||||
impl Visit for AtRuleNoUnknown {
|
||||
@ -28,7 +44,7 @@ impl Visit for AtRuleNoUnknown {
|
||||
AtRuleName::Ident(ident) => &ident.value,
|
||||
};
|
||||
|
||||
if self.ignored.iter().all(|item| name != item) {
|
||||
if self.ignored.iter().all(|item| !item.is_match(name)) {
|
||||
let message = format!("Unexpected unknown at-rule \"@{}\".", name);
|
||||
self.ctx.report(&unknown_at_rule.name, message);
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ use swc_css_visit::{Visit, VisitWith};
|
||||
|
||||
use crate::{
|
||||
dataset::is_generic_font_keyword,
|
||||
pattern::NamePattern,
|
||||
rule::{visitor_rule, LintRule, LintRuleContext},
|
||||
ConfigError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
@ -17,19 +19,25 @@ pub struct FontFamilyNoDuplicateNamesConfig {
|
||||
|
||||
pub fn font_family_no_duplicate_names(
|
||||
ctx: LintRuleContext<FontFamilyNoDuplicateNamesConfig>,
|
||||
) -> Box<dyn LintRule> {
|
||||
) -> Result<Box<dyn LintRule>, ConfigError> {
|
||||
let ignored = ctx
|
||||
.config()
|
||||
.ignore_font_family_names
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
visitor_rule(ctx.reaction(), FontFamilyNoDuplicateNames { ctx, ignored })
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(NamePattern::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(visitor_rule(
|
||||
ctx.reaction(),
|
||||
FontFamilyNoDuplicateNames { ctx, ignored },
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct FontFamilyNoDuplicateNames {
|
||||
ctx: LintRuleContext<FontFamilyNoDuplicateNamesConfig>,
|
||||
ignored: Vec<String>,
|
||||
ignored: Vec<NamePattern>,
|
||||
}
|
||||
|
||||
impl FontFamilyNoDuplicateNames {
|
||||
@ -73,7 +81,7 @@ impl FontFamilyNoDuplicateNames {
|
||||
AHashSet::with_capacity(values.len()),
|
||||
|mut seen, (font, span)| {
|
||||
let name = font.name();
|
||||
if seen.contains(&font) && self.ignored.iter().all(|item| name != item) {
|
||||
if seen.contains(&font) && self.ignored.iter().all(|item| !item.is_match(name)) {
|
||||
self.ctx
|
||||
.report(span, format!("Unexpected duplicate name '{}'.", name));
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
config::LintConfig,
|
||||
error::ConfigError,
|
||||
rule::LintRule,
|
||||
rules::{
|
||||
at_rule_no_unknown::at_rule_no_unknown, block_no_empty::block_no_empty,
|
||||
@ -33,24 +34,28 @@ pub struct LintParams<'a> {
|
||||
pub lint_config: &'a LintConfig,
|
||||
}
|
||||
|
||||
pub fn get_rules(LintParams { lint_config }: &LintParams) -> Vec<Box<dyn LintRule>> {
|
||||
pub fn get_rules(
|
||||
LintParams { lint_config }: &LintParams,
|
||||
) -> Result<Vec<Box<dyn LintRule>>, ConfigError> {
|
||||
let rules_config = &lint_config.rules;
|
||||
|
||||
vec![
|
||||
let rules = vec![
|
||||
block_no_empty((&rules_config.block_no_empty).into()),
|
||||
at_rule_no_unknown((&rules_config.at_rule_no_unknown).into()),
|
||||
at_rule_no_unknown((&rules_config.at_rule_no_unknown).into())?,
|
||||
no_empty_source((&rules_config.no_empty_source).into()),
|
||||
declaration_no_important((&rules_config.declaration_no_important).into()),
|
||||
keyframe_declaration_no_important((&rules_config.keyframe_declaration_no_important).into()),
|
||||
no_invalid_position_at_import_rule(
|
||||
(&rules_config.no_invalid_position_at_import_rule).into(),
|
||||
),
|
||||
)?,
|
||||
selector_max_class((&rules_config.selector_max_class).into()),
|
||||
color_hex_length((&rules_config.color_hex_length).into()),
|
||||
color_no_invalid_hex((&rules_config.color_no_invalid_hex).into()),
|
||||
unit_no_unknown((&rules_config.unit_no_unknown).into()),
|
||||
unit_no_unknown((&rules_config.unit_no_unknown).into())?,
|
||||
selector_max_combinators((&rules_config.selector_max_combinators).into()),
|
||||
font_family_no_duplicate_names((&rules_config.font_family_no_duplicate_names).into()),
|
||||
font_family_no_duplicate_names((&rules_config.font_family_no_duplicate_names).into())?,
|
||||
color_hex_alpha((&rules_config.color_hex_alpha).into()),
|
||||
]
|
||||
];
|
||||
|
||||
Ok(rules)
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||
use swc_css_ast::*;
|
||||
use swc_css_visit::{Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
|
||||
use crate::{
|
||||
pattern::NamePattern,
|
||||
rule::{visitor_rule, LintRule, LintRuleContext},
|
||||
ConfigError,
|
||||
};
|
||||
|
||||
const MESSAGE: &str = "Unexpected invalid position '@import' rule.";
|
||||
|
||||
@ -14,18 +18,25 @@ pub struct NoInvalidPositionAtImportRuleConfig {
|
||||
|
||||
pub fn no_invalid_position_at_import_rule(
|
||||
ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
|
||||
) -> Box<dyn LintRule> {
|
||||
let ignored = ctx.config().ignore_at_rules.clone().unwrap_or_default();
|
||||
visitor_rule(
|
||||
) -> Result<Box<dyn LintRule>, ConfigError> {
|
||||
let ignored = ctx
|
||||
.config()
|
||||
.ignore_at_rules
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(NamePattern::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(visitor_rule(
|
||||
ctx.reaction(),
|
||||
NoInvalidPositionAtImportRule { ctx, ignored },
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct NoInvalidPositionAtImportRule {
|
||||
ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
|
||||
ignored: Vec<String>,
|
||||
ignored: Vec<NamePattern>,
|
||||
}
|
||||
|
||||
impl Visit for NoInvalidPositionAtImportRule {
|
||||
@ -47,7 +58,7 @@ impl Visit for NoInvalidPositionAtImportRule {
|
||||
AtRuleName::DashedIdent(dashed_ident) => &dashed_ident.value,
|
||||
AtRuleName::Ident(ident) => &ident.value,
|
||||
};
|
||||
if self.ignored.iter().any(|item| name == item) {
|
||||
if self.ignored.iter().any(|item| item.is_match(name)) {
|
||||
seen
|
||||
} else {
|
||||
true
|
||||
|
@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||
use swc_css_ast::*;
|
||||
use swc_css_visit::{Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, LintRule, LintRuleContext};
|
||||
use crate::{
|
||||
pattern::NamePattern,
|
||||
rule::{visitor_rule, LintRule, LintRuleContext},
|
||||
ConfigError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -10,22 +14,34 @@ pub struct UnitNoUnknownConfig {
|
||||
ignore_units: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub fn unit_no_unknown(ctx: LintRuleContext<UnitNoUnknownConfig>) -> Box<dyn LintRule> {
|
||||
let ignored_units = ctx.config().ignore_units.clone().unwrap_or_default();
|
||||
visitor_rule(ctx.reaction(), UnitNoUnknown { ctx, ignored_units })
|
||||
pub fn unit_no_unknown(
|
||||
ctx: LintRuleContext<UnitNoUnknownConfig>,
|
||||
) -> Result<Box<dyn LintRule>, ConfigError> {
|
||||
let ignored_units = ctx
|
||||
.config()
|
||||
.ignore_units
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(NamePattern::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(visitor_rule(
|
||||
ctx.reaction(),
|
||||
UnitNoUnknown { ctx, ignored_units },
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct UnitNoUnknown {
|
||||
ctx: LintRuleContext<UnitNoUnknownConfig>,
|
||||
ignored_units: Vec<String>,
|
||||
ignored_units: Vec<NamePattern>,
|
||||
}
|
||||
|
||||
impl Visit for UnitNoUnknown {
|
||||
fn visit_unknown_dimension(&mut self, unknown_dimension: &UnknownDimension) {
|
||||
let unit = &unknown_dimension.unit.value;
|
||||
|
||||
if self.ignored_units.iter().all(|item| unit != item) {
|
||||
if self.ignored_units.iter().all(|item| !item.is_match(unit)) {
|
||||
let message = format!("Unexpected unknown unit \"{}\".", unit);
|
||||
self.ctx.report(&unknown_dimension.unit, message);
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ fn pass(input: PathBuf) {
|
||||
|
||||
let mut rules = get_rules(&LintParams {
|
||||
lint_config: &lint_config,
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
HANDLER.set(&handler, || {
|
||||
rules.lint_stylesheet(&stylesheet);
|
||||
@ -75,7 +76,8 @@ fn fail(input: PathBuf) {
|
||||
|
||||
let mut rules = get_rules(&LintParams {
|
||||
lint_config: &lint_config,
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
HANDLER.set(&handler, || {
|
||||
rules.lint_stylesheet(&stylesheet);
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"rules": {
|
||||
"at-rule-no-unknown": ["error", { "ignoreAtRules": ["custom"] }]
|
||||
"at-rule-no-unknown": [
|
||||
"error",
|
||||
{ "ignoreAtRules": ["custom", "/^my-/"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,5 @@
|
||||
@custom {
|
||||
}
|
||||
|
||||
@my-at-rule {
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"rules": {
|
||||
"font-family-no-duplicate-names": [
|
||||
"error",
|
||||
{ "ignoreFontFamilyNames": ["monospace"] }
|
||||
{ "ignoreFontFamilyNames": ["monospace", "/^my-/"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pre { font-family: monospace, monospace; }
|
||||
pre { font: 1em monospace, monospace; }
|
||||
pre { font-family: monospace, "Roberto Mono", monospace; }
|
||||
pre { font-family: my-font, my-font; }
|
||||
|
@ -2,7 +2,7 @@
|
||||
"rules": {
|
||||
"no-invalid-position-at-import-rule": [
|
||||
"error",
|
||||
{ "ignoreAtRules": ["hibike"] }
|
||||
{ "ignoreAtRules": ["hibike", "/^x-/"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
@hibike {
|
||||
}
|
||||
|
||||
@x-rule {
|
||||
}
|
||||
|
||||
@import "kumiko.css";
|
||||
@import "reina.css";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"unit-no-unknown": ["error", { "ignoreUnits": ["lightyear"] }]
|
||||
"unit-no-unknown": ["error", { "ignoreUnits": ["lightyear", "/x$/"] }]
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
a {
|
||||
width: 2lightyear;
|
||||
width: 6ax;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user