From d0f7fd3fb33b12d16ca9f5cea05d7250cef4aadd Mon Sep 17 00:00:00 2001 From: mdm317 Date: Sat, 18 May 2024 23:24:51 +0900 Subject: [PATCH 1/5] feat : noInvalidDirectionInLinearGradient --- Cargo.lock | 1 + .../biome_configuration/src/linter/rules.rs | 133 +++-- crates/biome_css_analyze/Cargo.toml | 1 + crates/biome_css_analyze/src/lint/nursery.rs | 2 + ...no_invalid_direction_in_linear_gradient.rs | 148 +++++ crates/biome_css_analyze/src/options.rs | 1 + .../invalid.css | 83 +++ .../invalid.css.snap | 533 ++++++++++++++++++ .../valid.css | 99 ++++ .../valid.css.snap | 107 ++++ .../src/categories.rs | 1 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 + 13 files changed, 1068 insertions(+), 53 deletions(-) create mode 100644 crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css.snap diff --git a/Cargo.lock b/Cargo.lock index f8bb9e8e52d0..67ce4c548dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -258,6 +258,7 @@ dependencies = [ "biome_test_utils", "insta", "lazy_static", + "regex", "schemars", "serde", "tests_macros", diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index d4c3cdc1ff2a..654f634bb2c5 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -3340,6 +3340,10 @@ pub struct Nursery { #[doc = "Disallow invalid !important within keyframe declarations"] #[serde(skip_serializing_if = "Option::is_none")] pub no_important_in_keyframe: Option>, + #[doc = "Disallow non-standard direction values for linear gradient functions."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_invalid_direction_in_linear_gradient: + Option>, #[doc = "Disallow the use of @import at-rules in invalid positions."] #[serde(skip_serializing_if = "Option::is_none")] pub no_invalid_position_at_import_rule: @@ -3450,6 +3454,7 @@ impl Nursery { "noEvolvingAny", "noFlatMapIdentity", "noImportantInKeyframe", + "noInvalidDirectionInLinearGradient", "noInvalidPositionAtImportRule", "noMisplacedAssertion", "noNodejsModules", @@ -3487,6 +3492,7 @@ impl Nursery { "noEvolvingAny", "noFlatMapIdentity", "noImportantInKeyframe", + "noInvalidDirectionInLinearGradient", "noInvalidPositionAtImportRule", "noUnknownFunction", "noUnknownSelectorPseudoElement", @@ -3507,12 +3513,13 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3552,6 +3559,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3628,131 +3636,136 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_misplaced_assertion.as_ref() { + if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_nodejs_modules.as_ref() { + if let Some(rule) = self.no_misplaced_assertion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_react_specific_props.as_ref() { + if let Some(rule) = self.no_nodejs_modules.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_react_specific_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_undeclared_dependencies.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_unknown_function.as_ref() { + if let Some(rule) = self.no_undeclared_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { + if let Some(rule) = self.no_unknown_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_unknown_property.as_ref() { + if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_property.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { + if let Some(rule) = self.no_unknown_unit.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_array_literals.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_array_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } + if let Some(rule) = self.use_top_level_regex.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3817,131 +3830,136 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_misplaced_assertion.as_ref() { + if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_nodejs_modules.as_ref() { + if let Some(rule) = self.no_misplaced_assertion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_react_specific_props.as_ref() { + if let Some(rule) = self.no_nodejs_modules.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_react_specific_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_undeclared_dependencies.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_unknown_function.as_ref() { + if let Some(rule) = self.no_undeclared_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { + if let Some(rule) = self.no_unknown_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_unknown_property.as_ref() { + if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_property.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { + if let Some(rule) = self.no_unknown_unit.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_array_literals.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_array_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } + if let Some(rule) = self.use_top_level_regex.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4026,6 +4044,10 @@ impl Nursery { .no_important_in_keyframe .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noInvalidDirectionInLinearGradient" => self + .no_invalid_direction_in_linear_gradient + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noInvalidPositionAtImportRule" => self .no_invalid_position_at_import_rule .as_ref() @@ -4191,6 +4213,11 @@ impl Nursery { rule_conf.set_level(severity); } } + "noInvalidDirectionInLinearGradient" => { + if let Some(rule_conf) = &mut self.no_invalid_direction_in_linear_gradient { + rule_conf.set_level(severity); + } + } "noInvalidPositionAtImportRule" => { if let Some(rule_conf) = &mut self.no_invalid_position_at_import_rule { rule_conf.set_level(severity); diff --git a/crates/biome_css_analyze/Cargo.toml b/crates/biome_css_analyze/Cargo.toml index 187489002128..63c87d6e313f 100644 --- a/crates/biome_css_analyze/Cargo.toml +++ b/crates/biome_css_analyze/Cargo.toml @@ -21,6 +21,7 @@ biome_deserialize_macros = { workspace = true } biome_diagnostics = { workspace = true } biome_rowan = { workspace = true } lazy_static = { workspace = true } +regex = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/biome_css_analyze/src/lint/nursery.rs b/crates/biome_css_analyze/src/lint/nursery.rs index 70c0be28246b..4aa2fc2296a2 100644 --- a/crates/biome_css_analyze/src/lint/nursery.rs +++ b/crates/biome_css_analyze/src/lint/nursery.rs @@ -7,6 +7,7 @@ pub mod no_duplicate_at_import_rules; pub mod no_duplicate_font_names; pub mod no_duplicate_selectors_keyframe_block; pub mod no_important_in_keyframe; +pub mod no_invalid_direction_in_linear_gradient; pub mod no_invalid_position_at_import_rule; pub mod no_unknown_function; pub mod no_unknown_media_feature_name; @@ -25,6 +26,7 @@ declare_group! { self :: no_duplicate_font_names :: NoDuplicateFontNames , self :: no_duplicate_selectors_keyframe_block :: NoDuplicateSelectorsKeyframeBlock , self :: no_important_in_keyframe :: NoImportantInKeyframe , + self :: no_invalid_direction_in_linear_gradient :: NoInvalidDirectionInLinearGradient , self :: no_invalid_position_at_import_rule :: NoInvalidPositionAtImportRule , self :: no_unknown_function :: NoUnknownFunction , self :: no_unknown_media_feature_name :: NoUnknownMediaFeatureName , diff --git a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs new file mode 100644 index 000000000000..bb7a86f44477 --- /dev/null +++ b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs @@ -0,0 +1,148 @@ +use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_css_syntax::{CssFunction, CssParameter}; +use biome_rowan::AstNode; +use lazy_static::lazy_static; +use regex::Regex; + +declare_rule! { + /// Disallow non-standard direction values for linear gradient functions. + /// + /// A valid and standard direction value is one of the following: + /// - an angle + /// - to plus a side-or-corner (to top, to bottom, to left, to right; to top right, to right top, to bottom left, etc.) + /// + /// A common mistake (matching outdated non-standard syntax) is to use just a side-or-corner without the preceding to. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```css,expect_diagnostic + /// .foo { background: linear-gradient(top, #fff, #000); } + /// ``` + /// + /// ```css,expect_diagnostic + /// .foo { background: linear-gradient(45, #fff, #000); } + /// ``` + /// + /// ### Valid + /// + /// ```css + /// .foo { background: linear-gradient(to top, #fff, #000); } + /// ``` + /// + /// ```css + /// .foo { background: linear-gradient(45deg, #fff, #000); } + /// ``` + /// + pub NoInvalidDirectionInLinearGradient { + version: "next", + name: "noInvalidDirectionInLinearGradient", + language: "css", + recommended: true, + sources: &[RuleSource::Stylelint("function-linear-gradient-no-nonstandard-direction")], + } +} + +lazy_static! { + pub static ref GET_PREFIX_REGEX: Regex = Regex::new(r"^(-\w+-)").unwrap(); + pub static ref LINEAR_GRADIENT_FUNCTION_NAME: Regex = + Regex::new(r"^(?i)(-webkit-|-moz-|-o-|-ms-)?linear-gradient").unwrap(); + pub static ref IN_KEYWORD: Regex = Regex::new(r"(?i)\bin\b").unwrap(); + pub static ref ANGLE: Regex = Regex::new(r"^[\d.]+(?:deg|grad|rad|turn)$").unwrap(); + pub static ref DIRECTION: Regex = Regex::new(r"(?i)top|left|bottom|right").unwrap(); + pub static ref DIRECTION_WITH_TO: Regex = Regex::new(&format!( + r"(?i)^to ({})(?: ({}))?$", + DIRECTION.as_str(), + DIRECTION.as_str() + )) + .unwrap(); + pub static ref DIRECTION_WITHOUT_TO: Regex = Regex::new(&format!( + r"(?i)^({})(?: ({}))?$", + DIRECTION.as_str(), + DIRECTION.as_str() + )) + .unwrap(); + pub static ref DIGIT: Regex = Regex::new(r"[\d.]").unwrap(); +} + +impl Rule for NoInvalidDirectionInLinearGradient { + type Query = Ast; + type State = CssParameter; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Option { + let node = ctx.query(); + let node_name = node.name().ok()?.text(); + let is_linear_gradient = LINEAR_GRADIENT_FUNCTION_NAME.is_match(&node_name); + if !is_linear_gradient { + return None; + } + let css_parameter = node.items(); + + if let Some(Ok(first_css_parameter)) = css_parameter.into_iter().next() { + let first_css_parameter_text = first_css_parameter.text(); + if IN_KEYWORD.is_match(&first_css_parameter_text) { + return None; + } + if let Some(first_char) = first_css_parameter_text.chars().next() { + if first_char.is_ascii_digit() { + if ANGLE.is_match(&first_css_parameter_text) { + return None; + } + return Some(first_css_parameter); + } + } + if !DIRECTION.is_match(&first_css_parameter_text) { + return None; + } + let has_prefix = LINEAR_GRADIENT_FUNCTION_NAME + .captures(&node_name) + .and_then(|caps| caps.get(1)) + .is_some(); + if !is_standdard_direction(&first_css_parameter_text, !has_prefix) { + return Some(first_css_parameter); + } + return None; + } + None + } + + fn diagnostic(_: &RuleContext, node: &Self::State) -> Option { + let span = node.range(); + Some( + RuleDiagnostic::new( + rule_category!(), + span, + markup! { + "Unexpected nonstandard direction" + }, + ).note(markup! { + "See ""MDN web docs"" for more details." + }) + ) + } +} + +fn is_standdard_direction(direction: &str, with_to_prefix: bool) -> bool { + let matches = match with_to_prefix { + true => DIRECTION_WITH_TO.captures(direction), + false => DIRECTION_WITHOUT_TO.captures(direction), + }; + if let Some(matches) = matches { + match (matches.get(1), matches.get(2)) { + (Some(_), None) => { + return true; + } + (Some(first_direction), Some(second_direction)) => { + if first_direction.as_str() != second_direction.as_str() { + return true; + } + } + _ => return true, + } + } + false +} diff --git a/crates/biome_css_analyze/src/options.rs b/crates/biome_css_analyze/src/options.rs index a04005c52ce6..eeded2cb2a36 100644 --- a/crates/biome_css_analyze/src/options.rs +++ b/crates/biome_css_analyze/src/options.rs @@ -9,6 +9,7 @@ pub type NoDuplicateFontNames = ::Options; pub type NoDuplicateSelectorsKeyframeBlock = < lint :: nursery :: no_duplicate_selectors_keyframe_block :: NoDuplicateSelectorsKeyframeBlock as biome_analyze :: Rule > :: Options ; pub type NoImportantInKeyframe = < lint :: nursery :: no_important_in_keyframe :: NoImportantInKeyframe as biome_analyze :: Rule > :: Options ; +pub type NoInvalidDirectionInLinearGradient = < lint :: nursery :: no_invalid_direction_in_linear_gradient :: NoInvalidDirectionInLinearGradient as biome_analyze :: Rule > :: Options ; pub type NoInvalidPositionAtImportRule = < lint :: nursery :: no_invalid_position_at_import_rule :: NoInvalidPositionAtImportRule as biome_analyze :: Rule > :: Options ; pub type NoUnknownFunction = ::Options; diff --git a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css new file mode 100644 index 000000000000..0cf3357d66ff --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css @@ -0,0 +1,83 @@ +.foo { + background: linear-gradient(bottom, #fff, #000); +} +.foo { + background: lInEaR-gRaDiEnT(bottom, #fff, #000); +} +.foo { + background: LINEAR-GRADIENT(bottom, #fff, #000); +} +.foo { + background: linear-gradient(bOtToM, #fff, #000); +} +.foo { + background: linear-gradient(BOTTOM, #fff, #000); +} +.foo { + background: linear-gradient(top, #fff, #000); +} +.foo { + background: linear-gradient(left, #fff, #000); +} +.foo { + background: linear-gradient(right, #fff, #000); +} +.foo { + background: linear-gradient(to top top, #fff, #000); +} +.foo { + background: linear-gradient(45, #fff, #000); +} +.foo { + background: linear-gradient(0.25, #fff, #000); +} +.foo { + background: linear-gradient(1.577, #fff, #000); +} +.foo { + background: linear-gradient(topin, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -moz-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -o-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(to bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + url(foo.png); +} +.foo { + background: -o-linear-gradient(to bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -ms-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} diff --git a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap new file mode 100644 index 000000000000..0a3d27506e55 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap @@ -0,0 +1,533 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: invalid.css +--- +# Input +```css +.foo { + background: linear-gradient(bottom, #fff, #000); +} +.foo { + background: lInEaR-gRaDiEnT(bottom, #fff, #000); +} +.foo { + background: LINEAR-GRADIENT(bottom, #fff, #000); +} +.foo { + background: linear-gradient(bOtToM, #fff, #000); +} +.foo { + background: linear-gradient(BOTTOM, #fff, #000); +} +.foo { + background: linear-gradient(top, #fff, #000); +} +.foo { + background: linear-gradient(left, #fff, #000); +} +.foo { + background: linear-gradient(right, #fff, #000); +} +.foo { + background: linear-gradient(to top top, #fff, #000); +} +.foo { + background: linear-gradient(45, #fff, #000); +} +.foo { + background: linear-gradient(0.25, #fff, #000); +} +.foo { + background: linear-gradient(1.577, #fff, #000); +} +.foo { + background: linear-gradient(topin, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -moz-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -o-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(to bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + url(foo.png); +} +.foo { + background: -o-linear-gradient(to bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -ms-linear-gradient(to bottom, #fff, #000), + url(bar.png); +} + +``` + +# Diagnostics +``` +invalid.css:2:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 1 │ .foo { + > 2 │ background: linear-gradient(bottom, #fff, #000); + │ ^^^^^^ + 3 │ } + 4 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:5:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 3 │ } + 4 │ .foo { + > 5 │ background: lInEaR-gRaDiEnT(bottom, #fff, #000); + │ ^^^^^^ + 6 │ } + 7 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:8:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 6 │ } + 7 │ .foo { + > 8 │ background: LINEAR-GRADIENT(bottom, #fff, #000); + │ ^^^^^^ + 9 │ } + 10 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:11:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 9 │ } + 10 │ .foo { + > 11 │ background: linear-gradient(bOtToM, #fff, #000); + │ ^^^^^^ + 12 │ } + 13 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:14:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 12 │ } + 13 │ .foo { + > 14 │ background: linear-gradient(BOTTOM, #fff, #000); + │ ^^^^^^ + 15 │ } + 16 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:17:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 15 │ } + 16 │ .foo { + > 17 │ background: linear-gradient(top, #fff, #000); + │ ^^^ + 18 │ } + 19 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:20:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 18 │ } + 19 │ .foo { + > 20 │ background: linear-gradient(left, #fff, #000); + │ ^^^^ + 21 │ } + 22 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:23:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 21 │ } + 22 │ .foo { + > 23 │ background: linear-gradient(right, #fff, #000); + │ ^^^^^ + 24 │ } + 25 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:26:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 24 │ } + 25 │ .foo { + > 26 │ background: linear-gradient(to top top, #fff, #000); + │ ^^^^^^^^^^ + 27 │ } + 28 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:29:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 27 │ } + 28 │ .foo { + > 29 │ background: linear-gradient(45, #fff, #000); + │ ^^ + 30 │ } + 31 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:32:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 30 │ } + 31 │ .foo { + > 32 │ background: linear-gradient(0.25, #fff, #000); + │ ^^^^ + 33 │ } + 34 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:35:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 33 │ } + 34 │ .foo { + > 35 │ background: linear-gradient(1.577, #fff, #000); + │ ^^^^^ + 36 │ } + 37 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:38:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 36 │ } + 37 │ .foo { + > 38 │ background: linear-gradient(topin, #fff, #000); + │ ^^^^^ + 39 │ } + 40 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:41:38 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 39 │ } + 40 │ .foo { + > 41 │ background: -webkit-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 42 │ } + 43 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:44:35 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 42 │ } + 43 │ .foo { + > 44 │ background: -moz-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 45 │ } + 46 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:47:33 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 45 │ } + 46 │ .foo { + > 47 │ background: -o-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 48 │ } + 49 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:50:52 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 48 │ } + 49 │ .foo { + > 50 │ background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 51 │ } + 52 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:53:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 51 │ } + 52 │ .foo { + > 53 │ background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 54 │ } + 55 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:56:47 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 54 │ } + 55 │ .foo { + > 56 │ background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000); + │ ^^^^^^^^^ + 57 │ } + 58 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:59:38 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 57 │ } + 58 │ .foo { + > 59 │ background: -webkit-linear-gradient(to bottom, #fff, #000), url(foo.png); + │ ^^^^^^^^^ + 60 │ } + 61 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:62:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 60 │ } + 61 │ .foo { + > 62 │ background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + │ ^^^^^^^^^ + 63 │ url(foo.png); + 64 │ } + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:66:33 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 64 │ } + 65 │ .foo { + > 66 │ background: -o-linear-gradient(to bottom, #fff, #000), url(foo.png); + │ ^^^^^^^^^ + 67 │ } + 68 │ .foo { + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:69:52 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 67 │ } + 68 │ .foo { + > 69 │ background: url(foo.png), -webkit-linear-gradient(to bottom, #fff, #000), + │ ^^^^^^^^^ + 70 │ url(bar.png); + 71 │ } + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:73:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 71 │ } + 72 │ .foo { + > 73 │ background: url(foo.png), -moz-linear-gradient(to bottom, #fff, #000), + │ ^^^^^^^^^ + 74 │ url(bar.png); + 75 │ } + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:77:47 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 75 │ } + 76 │ .foo { + > 77 │ background: url(foo.png), -o-linear-gradient(to bottom, #fff, #000), + │ ^^^^^^^^^ + 78 │ url(bar.png); + 79 │ } + + i See MDN web docs for more details. + + +``` + +``` +invalid.css:81:48 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected nonstandard direction + + 79 │ } + 80 │ .foo { + > 81 │ background: url(foo.png), -ms-linear-gradient(to bottom, #fff, #000), + │ ^^^^^^^^^ + 82 │ url(bar.png); + 83 │ } + + i See MDN web docs for more details. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css new file mode 100644 index 000000000000..33d057230384 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css @@ -0,0 +1,99 @@ +.foo { + background: linear-gradient(to top, #fff, #000); +} +.foo { + background: lInEaR-gRaDiEnT(to top, #fff, #000); +} +.foo { + background: LINEAR-GRADIENT(to top, #fff, #000); +} +.foo { + background: linear-gradient(to bottom, #fff, #000); +} +.foo { + background: linear-gradient(to right, #fff, #000); +} +.foo { + background: linear-gradient(to left, #fff, #000); +} +.foo { + background: linear-gradient(to top left, #fff, #000); +} +.foo { + background: linear-gradient(to left top, #fff, #000); +} +.foo { + background: linear-gradient(to bottom right, #fff, #000); +} +.foo { + background: linear-gradient(to right bottom, #fff, #000); +} +.foo { + background: linear-gradient(45deg, #fff, #000); +} +.foo { + background: linear-gradient(100grad, #fff, #000); +} +.foo { + background: linear-gradient(0.25turn, #fff, #000); +} +.foo { + background: linear-gradient(1.57rad, #fff, #000); +} +.foo { + background: linear-gradient(#fff, #000); +} +.foo { + background: linear-gradient(black, white); +} +.foo { + background: linear-gradient(in srgb to top, #fff, #000); +} +.foo { + background: linear-gradient(to top in srgb, #fff, #000); +} +.foo { + background: linear-gradient(rgba(255, 255, 255, 0.5) 0%, #000); +} +.foo { + background: -webkit-linear-gradient(top, #fff, #000); +} +.foo { + background: -moz-linear-gradient(top, #fff, #000); +} +.foo { + background: -o-linear-gradient(top, #fff, #000); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -moz-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -o-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(bottom, #fff, #000), url(bar.png); +} +.foo { + background: url(foo.png), -ms-linear-gradient(bottom, #fff, #000), + url(bar.png); +} diff --git a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css.snap new file mode 100644 index 000000000000..41d31bb9052c --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/valid.css.snap @@ -0,0 +1,107 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: valid.css +--- +# Input +```css +.foo { + background: linear-gradient(to top, #fff, #000); +} +.foo { + background: lInEaR-gRaDiEnT(to top, #fff, #000); +} +.foo { + background: LINEAR-GRADIENT(to top, #fff, #000); +} +.foo { + background: linear-gradient(to bottom, #fff, #000); +} +.foo { + background: linear-gradient(to right, #fff, #000); +} +.foo { + background: linear-gradient(to left, #fff, #000); +} +.foo { + background: linear-gradient(to top left, #fff, #000); +} +.foo { + background: linear-gradient(to left top, #fff, #000); +} +.foo { + background: linear-gradient(to bottom right, #fff, #000); +} +.foo { + background: linear-gradient(to right bottom, #fff, #000); +} +.foo { + background: linear-gradient(45deg, #fff, #000); +} +.foo { + background: linear-gradient(100grad, #fff, #000); +} +.foo { + background: linear-gradient(0.25turn, #fff, #000); +} +.foo { + background: linear-gradient(1.57rad, #fff, #000); +} +.foo { + background: linear-gradient(#fff, #000); +} +.foo { + background: linear-gradient(black, white); +} +.foo { + background: linear-gradient(in srgb to top, #fff, #000); +} +.foo { + background: linear-gradient(to top in srgb, #fff, #000); +} +.foo { + background: linear-gradient(rgba(255, 255, 255, 0.5) 0%, #000); +} +.foo { + background: -webkit-linear-gradient(top, #fff, #000); +} +.foo { + background: -moz-linear-gradient(top, #fff, #000); +} +.foo { + background: -o-linear-gradient(top, #fff, #000); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -webkit-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -moz-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(bottom, #fff, #000); +} +.foo { + background: -o-linear-gradient(bottom, #fff, #000), url(foo.png); +} +.foo { + background: url(foo.png), -webkit-linear-gradient(bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -moz-linear-gradient(bottom, #fff, #000), + url(bar.png); +} +.foo { + background: url(foo.png), -o-linear-gradient(bottom, #fff, #000), url(bar.png); +} +.foo { + background: url(foo.png), -ms-linear-gradient(bottom, #fff, #000), + url(bar.png); +} + +``` diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index dd9d421f2adc..c52a69487740 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -121,6 +121,7 @@ define_categories! { "lint/nursery/noEvolvingAny": "https://biomejs.dev/linter/rules/no-evolving-any", "lint/nursery/noFlatMapIdentity": "https://biomejs.dev/linter/rules/no-flat-map-identity", "lint/nursery/noImportantInKeyframe": "https://biomejs.dev/linter/rules/no-important-in-keyframe", + "lint/nursery/noInvalidDirectionInLinearGradient": "https://biomejs.dev/linter/rules/no-invalid-direction-in-linear-gradient", "lint/nursery/noInvalidPositionAtImportRule": "https://biomejs.dev/linter/rules/no-invalid-position-at-import-rule", "lint/nursery/noMisplacedAssertion": "https://biomejs.dev/linter/rules/no-misplaced-assertion", "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 78522455762e..e6a1ac513a8e 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1001,6 +1001,10 @@ export interface Nursery { * Disallow invalid !important within keyframe declarations */ noImportantInKeyframe?: RuleConfiguration_for_Null; + /** + * Disallow non-standard direction values for linear gradient functions. + */ + noInvalidDirectionInLinearGradient?: RuleConfiguration_for_Null; /** * Disallow the use of @import at-rules in invalid positions. */ @@ -2277,6 +2281,7 @@ export type Category = | "lint/nursery/noEvolvingAny" | "lint/nursery/noFlatMapIdentity" | "lint/nursery/noImportantInKeyframe" + | "lint/nursery/noInvalidDirectionInLinearGradient" | "lint/nursery/noInvalidPositionAtImportRule" | "lint/nursery/noMisplacedAssertion" | "lint/nursery/noMissingGenericFamilyKeyword" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 53ab31c0ac50..6865f0c4bbf7 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1703,6 +1703,13 @@ { "type": "null" } ] }, + "noInvalidDirectionInLinearGradient": { + "description": "Disallow non-standard direction values for linear gradient functions.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noInvalidPositionAtImportRule": { "description": "Disallow the use of @import at-rules in invalid positions.", "anyOf": [ From ceae2047fcfae406ff2d83f86357b118eb6382ee Mon Sep 17 00:00:00 2001 From: mdm317 Date: Mon, 20 May 2024 00:28:08 +0900 Subject: [PATCH 2/5] review : Option to Self::Signals --- .../nursery/no_invalid_direction_in_linear_gradient.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs index bb7a86f44477..462816a50c93 100644 --- a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs +++ b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs @@ -5,6 +5,8 @@ use biome_rowan::AstNode; use lazy_static::lazy_static; use regex::Regex; +use crate::utils::vendor_prefixed; + declare_rule! { /// Disallow non-standard direction values for linear gradient functions. /// @@ -46,7 +48,6 @@ declare_rule! { } lazy_static! { - pub static ref GET_PREFIX_REGEX: Regex = Regex::new(r"^(-\w+-)").unwrap(); pub static ref LINEAR_GRADIENT_FUNCTION_NAME: Regex = Regex::new(r"^(?i)(-webkit-|-moz-|-o-|-ms-)?linear-gradient").unwrap(); pub static ref IN_KEYWORD: Regex = Regex::new(r"(?i)\bin\b").unwrap(); @@ -73,7 +74,7 @@ impl Rule for NoInvalidDirectionInLinearGradient { type Signals = Option; type Options = (); - fn run(ctx: &RuleContext) -> Option { + fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); let node_name = node.name().ok()?.text(); let is_linear_gradient = LINEAR_GRADIENT_FUNCTION_NAME.is_match(&node_name); @@ -98,10 +99,7 @@ impl Rule for NoInvalidDirectionInLinearGradient { if !DIRECTION.is_match(&first_css_parameter_text) { return None; } - let has_prefix = LINEAR_GRADIENT_FUNCTION_NAME - .captures(&node_name) - .and_then(|caps| caps.get(1)) - .is_some(); + let has_prefix = vendor_prefixed(&node_name); if !is_standdard_direction(&first_css_parameter_text, !has_prefix) { return Some(first_css_parameter); } From 101ed129bd97367eb2ae8130d06fcc86dabec9fb Mon Sep 17 00:00:00 2001 From: mdm317 Date: Mon, 20 May 2024 23:55:08 +0900 Subject: [PATCH 3/5] chore: apply code review --- ...no_invalid_direction_in_linear_gradient.rs | 64 ++++++++++++------- .../invalid.css.snap | 52 +++++++++++++++ 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs index 462816a50c93..a9c193d27823 100644 --- a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs +++ b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs @@ -2,6 +2,7 @@ use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnosti use biome_console::markup; use biome_css_syntax::{CssFunction, CssParameter}; use biome_rowan::AstNode; +use biome_rowan::AstSeparatedList; use lazy_static::lazy_static; use regex::Regex; @@ -12,7 +13,7 @@ declare_rule! { /// /// A valid and standard direction value is one of the following: /// - an angle - /// - to plus a side-or-corner (to top, to bottom, to left, to right; to top right, to right top, to bottom left, etc.) + /// - to plus a side-or-corner (`to top`, `to bottom`, `to left`, `to right`; `to top right`, `to right top`, `to bottom left`, etc.) /// /// A common mistake (matching outdated non-standard syntax) is to use just a side-or-corner without the preceding to. /// @@ -48,24 +49,40 @@ declare_rule! { } lazy_static! { + // It is necessary to find case-insensitive string. + // For example, both 'linear-gradinet' and 'Linear-gradient' should pass the check. pub static ref LINEAR_GRADIENT_FUNCTION_NAME: Regex = Regex::new(r"^(?i)(-webkit-|-moz-|-o-|-ms-)?linear-gradient").unwrap(); + + // It is necessary to find case-insensitive string. + // Also Check if 'in' is a word. + // For examples,`to top in srgb` is valid but `to top insrgb` is not valid. pub static ref IN_KEYWORD: Regex = Regex::new(r"(?i)\bin\b").unwrap(); + + // This regex checks if a string consists of a number immediately followed by a unit, with no space between them. pub static ref ANGLE: Regex = Regex::new(r"^[\d.]+(?:deg|grad|rad|turn)$").unwrap(); + + // It is necessary to find case-insensitive string. + // For example, both 'top' and 'TOP' should pass the check. pub static ref DIRECTION: Regex = Regex::new(r"(?i)top|left|bottom|right").unwrap(); + + // This need for capture 'side-or-corner' keyword from linear-gradient function. + // Ensure starts with the keyword 'to' and ends with the keyword 'side-or-corner'. pub static ref DIRECTION_WITH_TO: Regex = Regex::new(&format!( r"(?i)^to ({})(?: ({}))?$", DIRECTION.as_str(), DIRECTION.as_str() )) .unwrap(); + + // This need for capture 'side-or-corner' keyword from linear-gradient function + // Ensure starts with the keyword 'side-or-corner' and ends with the keyword 'side-or-corner'. pub static ref DIRECTION_WITHOUT_TO: Regex = Regex::new(&format!( r"(?i)^({})(?: ({}))?$", DIRECTION.as_str(), DIRECTION.as_str() )) .unwrap(); - pub static ref DIGIT: Regex = Regex::new(r"[\d.]").unwrap(); } impl Rule for NoInvalidDirectionInLinearGradient { @@ -83,28 +100,26 @@ impl Rule for NoInvalidDirectionInLinearGradient { } let css_parameter = node.items(); - if let Some(Ok(first_css_parameter)) = css_parameter.into_iter().next() { - let first_css_parameter_text = first_css_parameter.text(); - if IN_KEYWORD.is_match(&first_css_parameter_text) { - return None; - } - if let Some(first_char) = first_css_parameter_text.chars().next() { - if first_char.is_ascii_digit() { - if ANGLE.is_match(&first_css_parameter_text) { - return None; - } - return Some(first_css_parameter); + let first_css_parameter = css_parameter.first()?.ok()?; + let first_css_parameter_text = first_css_parameter.text(); + if IN_KEYWORD.is_match(&first_css_parameter_text) { + return None; + } + if let Some(first_char) = first_css_parameter_text.chars().next() { + if first_char.is_ascii_digit() { + if ANGLE.is_match(&first_css_parameter_text) { + return None; } - } - if !DIRECTION.is_match(&first_css_parameter_text) { - return None; - } - let has_prefix = vendor_prefixed(&node_name); - if !is_standdard_direction(&first_css_parameter_text, !has_prefix) { return Some(first_css_parameter); } + } + if !DIRECTION.is_match(&first_css_parameter_text) { return None; } + let has_prefix = vendor_prefixed(&node_name); + if !is_standdard_direction(&first_css_parameter_text, has_prefix) { + return Some(first_css_parameter); + } None } @@ -118,16 +133,19 @@ impl Rule for NoInvalidDirectionInLinearGradient { "Unexpected nonstandard direction" }, ).note(markup! { + "You should fix the direction value to follow the syntax." + }) + .note(markup! { "See ""MDN web docs"" for more details." }) ) } } -fn is_standdard_direction(direction: &str, with_to_prefix: bool) -> bool { - let matches = match with_to_prefix { - true => DIRECTION_WITH_TO.captures(direction), - false => DIRECTION_WITHOUT_TO.captures(direction), +fn is_standdard_direction(direction: &str, has_prefix: bool) -> bool { + let matches = match has_prefix { + true => DIRECTION_WITHOUT_TO.captures(direction), + false => DIRECTION_WITH_TO.captures(direction), }; if let Some(matches) = matches { match (matches.get(1), matches.get(2)) { diff --git a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap index 0a3d27506e55..e04654352ed5 100644 --- a/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap +++ b/crates/biome_css_analyze/tests/specs/nursery/noInvalidDirectionInLinearGradient/invalid.css.snap @@ -102,6 +102,8 @@ invalid.css:2:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━ 3 │ } 4 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -119,6 +121,8 @@ invalid.css:5:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━ 6 │ } 7 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -136,6 +140,8 @@ invalid.css:8:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━━ 9 │ } 10 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -153,6 +159,8 @@ invalid.css:11:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 12 │ } 13 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -170,6 +178,8 @@ invalid.css:14:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 15 │ } 16 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -187,6 +197,8 @@ invalid.css:17:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 18 │ } 19 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -204,6 +216,8 @@ invalid.css:20:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 21 │ } 22 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -221,6 +235,8 @@ invalid.css:23:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 24 │ } 25 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -238,6 +254,8 @@ invalid.css:26:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 27 │ } 28 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -255,6 +273,8 @@ invalid.css:29:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 30 │ } 31 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -272,6 +292,8 @@ invalid.css:32:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 33 │ } 34 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -289,6 +311,8 @@ invalid.css:35:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 36 │ } 37 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -306,6 +330,8 @@ invalid.css:38:30 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 39 │ } 40 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -323,6 +349,8 @@ invalid.css:41:38 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 42 │ } 43 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -340,6 +368,8 @@ invalid.css:44:35 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 45 │ } 46 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -357,6 +387,8 @@ invalid.css:47:33 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 48 │ } 49 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -374,6 +406,8 @@ invalid.css:50:52 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 51 │ } 52 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -391,6 +425,8 @@ invalid.css:53:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 54 │ } 55 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -408,6 +444,8 @@ invalid.css:56:47 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 57 │ } 58 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -425,6 +463,8 @@ invalid.css:59:38 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 60 │ } 61 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -442,6 +482,8 @@ invalid.css:62:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 63 │ url(foo.png); 64 │ } + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -459,6 +501,8 @@ invalid.css:66:33 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 67 │ } 68 │ .foo { + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -476,6 +520,8 @@ invalid.css:69:52 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 70 │ url(bar.png); 71 │ } + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -493,6 +539,8 @@ invalid.css:73:49 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 74 │ url(bar.png); 75 │ } + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -510,6 +558,8 @@ invalid.css:77:47 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 78 │ url(bar.png); 79 │ } + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. @@ -527,6 +577,8 @@ invalid.css:81:48 lint/nursery/noInvalidDirectionInLinearGradient ━━━━ 82 │ url(bar.png); 83 │ } + i You should fix the direction value to follow the syntax. + i See MDN web docs for more details. From 364ca930f86403054cdbbf497008b6efd79ce9fa Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 24 May 2024 18:46:10 +0900 Subject: [PATCH 4/5] chore : change rule doc --- crates/biome_configuration/src/linter/rules.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index 7a61385a25ff..7de4f8bd0b73 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -3340,7 +3340,7 @@ pub struct Nursery { #[doc = "Disallow invalid !important within keyframe declarations"] #[serde(skip_serializing_if = "Option::is_none")] pub no_important_in_keyframe: Option>, - #[doc = "Succinct description of the rule."] + #[doc = "Disallow non-standard direction values for linear gradient functions."] #[serde(skip_serializing_if = "Option::is_none")] pub no_invalid_direction_in_linear_gradient: Option>, From 81bb0c8e0e1a4a4733db5601e07e5d95fdf39631 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 14 Jun 2024 17:43:59 +0900 Subject: [PATCH 5/5] refactor : reduce regex --- ...no_invalid_direction_in_linear_gradient.rs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs index a9c193d27823..036e1fc2cae1 100644 --- a/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs +++ b/crates/biome_css_analyze/src/lint/nursery/no_invalid_direction_in_linear_gradient.rs @@ -50,39 +50,20 @@ declare_rule! { lazy_static! { // It is necessary to find case-insensitive string. - // For example, both 'linear-gradinet' and 'Linear-gradient' should pass the check. - pub static ref LINEAR_GRADIENT_FUNCTION_NAME: Regex = - Regex::new(r"^(?i)(-webkit-|-moz-|-o-|-ms-)?linear-gradient").unwrap(); - - // It is necessary to find case-insensitive string. - // Also Check if 'in' is a word. + // Also Check if 'in' is a word boundary. // For examples,`to top in srgb` is valid but `to top insrgb` is not valid. pub static ref IN_KEYWORD: Regex = Regex::new(r"(?i)\bin\b").unwrap(); // This regex checks if a string consists of a number immediately followed by a unit, with no space between them. + // For examples, `45deg`, `45grad` is valid but `45 deg`, `45de` is not valid. pub static ref ANGLE: Regex = Regex::new(r"^[\d.]+(?:deg|grad|rad|turn)$").unwrap(); - // It is necessary to find case-insensitive string. - // For example, both 'top' and 'TOP' should pass the check. - pub static ref DIRECTION: Regex = Regex::new(r"(?i)top|left|bottom|right").unwrap(); - - // This need for capture 'side-or-corner' keyword from linear-gradient function. - // Ensure starts with the keyword 'to' and ends with the keyword 'side-or-corner'. - pub static ref DIRECTION_WITH_TO: Regex = Regex::new(&format!( - r"(?i)^to ({})(?: ({}))?$", - DIRECTION.as_str(), - DIRECTION.as_str() - )) - .unwrap(); - - // This need for capture 'side-or-corner' keyword from linear-gradient function - // Ensure starts with the keyword 'side-or-corner' and ends with the keyword 'side-or-corner'. + // This need for capture `side-or-corner` keyword from linear-gradient function. + // Ensure starts `side-or-corner` keyword `to` and ends with the keyword `side-or-corner`. pub static ref DIRECTION_WITHOUT_TO: Regex = Regex::new(&format!( - r"(?i)^({})(?: ({}))?$", - DIRECTION.as_str(), - DIRECTION.as_str() - )) - .unwrap(); + r"(?i)^({0})(?: ({0}))?$", + "top|left|bottom|right" + )).unwrap(); } impl Rule for NoInvalidDirectionInLinearGradient { @@ -94,8 +75,14 @@ impl Rule for NoInvalidDirectionInLinearGradient { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); let node_name = node.name().ok()?.text(); - let is_linear_gradient = LINEAR_GRADIENT_FUNCTION_NAME.is_match(&node_name); - if !is_linear_gradient { + let linear_gradient_property = [ + "linear-gradient", + "-webkit-linear-gradient", + "-moz-linear-gradient", + "-o-linear-gradient", + "-ms-linear-gradient", + ]; + if !linear_gradient_property.contains(&node_name.to_lowercase().as_str()) { return None; } let css_parameter = node.items(); @@ -113,7 +100,11 @@ impl Rule for NoInvalidDirectionInLinearGradient { return Some(first_css_parameter); } } - if !DIRECTION.is_match(&first_css_parameter_text) { + let direction_property = ["top", "left", "bottom", "right"]; + if !direction_property + .iter() + .any(|&keyword| first_css_parameter_text.to_lowercase().contains(keyword)) + { return None; } let has_prefix = vendor_prefixed(&node_name); @@ -143,9 +134,10 @@ impl Rule for NoInvalidDirectionInLinearGradient { } fn is_standdard_direction(direction: &str, has_prefix: bool) -> bool { - let matches = match has_prefix { - true => DIRECTION_WITHOUT_TO.captures(direction), - false => DIRECTION_WITH_TO.captures(direction), + let matches = match (has_prefix, direction.starts_with("to ")) { + (true, false) => DIRECTION_WITHOUT_TO.captures(direction), + (false, true) => DIRECTION_WITHOUT_TO.captures(&direction[3..]), + _ => None, }; if let Some(matches) = matches { match (matches.get(1), matches.get(2)) {