Skip to content

Commit

Permalink
feat(biome_css_analyzer): implement function-linear-gradient-no-nonst…
Browse files Browse the repository at this point in the history
…andard-direction (#2911)

Co-authored-by: mdm317 <[email protected]>
  • Loading branch information
mdm317 and gen-ip-1 authored Jun 18, 2024
1 parent 42bc279 commit 6f8cab7
Show file tree
Hide file tree
Showing 13 changed files with 1,149 additions and 79 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

180 changes: 101 additions & 79 deletions crates/biome_configuration/src/linter/rules.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/biome_css_analyze/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ biome_diagnostics = { workspace = true }
biome_rowan = { workspace = true }
biome_suppression = { workspace = true }
lazy_static = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_css_analyze/src/lint/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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 biome_rowan::AstSeparatedList;
use lazy_static::lazy_static;
use regex::Regex;

use crate::utils::vendor_prefixed;

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! {
// It is necessary to find case-insensitive string.
// 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();

// 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)^({0})(?: ({0}))?$",
"top|left|bottom|right"
)).unwrap();
}

impl Rule for NoInvalidDirectionInLinearGradient {
type Query = Ast<CssFunction>;
type State = CssParameter;
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
let node_name = node.name().ok()?.text();
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();

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;
}
return Some(first_css_parameter);
}
}
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);
if !is_standdard_direction(&first_css_parameter_text, has_prefix) {
return Some(first_css_parameter);
}
None
}

fn diagnostic(_: &RuleContext<Self>, node: &Self::State) -> Option<RuleDiagnostic> {
let span = node.range();
Some(
RuleDiagnostic::new(
rule_category!(),
span,
markup! {
"Unexpected nonstandard direction"
},
).note(markup! {
"You should fix the direction value to follow the syntax."
})
.note(markup! {
"See "<Hyperlink href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient">"MDN web docs"</Hyperlink>" for more details."
})
)
}
}

fn is_standdard_direction(direction: &str, has_prefix: bool) -> bool {
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)) {
(Some(_), None) => {
return true;
}
(Some(first_direction), Some(second_direction)) => {
if first_direction.as_str() != second_direction.as_str() {
return true;
}
}
_ => return true,
}
}
false
}
1 change: 1 addition & 0 deletions crates/biome_css_analyze/src/options.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit 6f8cab7

Please sign in to comment.