Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(format/grit): add basic formatting for where pattern #4095

Merged
merged 35 commits into from
Oct 8, 2024
Merged
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bf2781f
Update tests
branberry Sep 26, 2024
c331c41
Update Cargo.toml
branberry Sep 26, 2024
8faaa59
fix test
branberry Sep 28, 2024
9a7ae07
Merge branch 'main' into feat/like
branberry Sep 28, 2024
083e1bf
fix(js-api): update code (#4111)
ematipico Sep 28, 2024
38fcbd3
fix(deps): update rust crate libc to 0.2.159 (#4126)
renovate[bot] Sep 30, 2024
4388e1e
fix(bench): typo in `.prettierignore` (#4134)
Jayllyz Sep 30, 2024
f783130
Resolve conflicts
branberry Sep 30, 2024
9b1757e
Update formatting for definition list
branberry Sep 30, 2024
45cb896
chore(deps): update pnpm to v9.11.0 (#4132)
renovate[bot] Sep 30, 2024
0037040
chore(deps): update dependency eslint to v9.11.1 (#4130)
renovate[bot] Sep 30, 2024
ab75e3d
chore(deps): update rust crate oxc_resolver to 1.12.0 (#4133)
renovate[bot] Sep 30, 2024
0e853fc
chore(deps): update actions/checkout action to v4.2.0 (#4127)
renovate[bot] Sep 30, 2024
5dea2f5
chore(deps): update rust:1.81.0 docker digest to a21d540 (#4123)
renovate[bot] Sep 30, 2024
af988a2
chore(deps): update dependency @typescript-eslint/eslint-plugin to v8…
renovate[bot] Sep 30, 2024
2e0173e
chore(deps): update @biomejs packages (#4124)
renovate[bot] Sep 30, 2024
aab92ef
fix(noMisleadingCharacterClass): properly handle escaping and improve…
Conaclos Sep 30, 2024
71d547e
ci: update codspeed action (#4139)
ematipico Sep 30, 2024
4a22da8
feat(biome_css_analyze): implement `noDescendingSpecificity` (#4097)
tunamaguro Sep 30, 2024
69faa78
Add formatting for predicate list and pattern where
branberry Oct 2, 2024
6061b25
Resolve merge conflicts
branberry Oct 2, 2024
fe7667d
Reorder tokens
branberry Oct 3, 2024
29ca5c4
Add where predicate
branberry Oct 5, 2024
de98431
Remove comments
branberry Oct 5, 2024
3cb4ad4
Add simple whitespace
branberry Oct 5, 2024
8949a61
Merge branch 'main' into feat/like
branberry Oct 5, 2024
73bb691
Create specific test for where pattern
branberry Oct 5, 2024
9d60af7
Undo change to package.json
branberry Oct 5, 2024
a6593fa
Add missing changes to get grit formatter options read in
branberry Oct 6, 2024
47c4f8a
Add basic error handling for predicate and definition lists
branberry Oct 6, 2024
07eb7bb
Revert test change
branberry Oct 7, 2024
092662f
Address PR comments and remove quote style
branberry Oct 7, 2024
a984aac
Update crates/biome_grit_formatter/src/grit/lists/definition_list.rs
branberry Oct 7, 2024
d44ea04
Remove comment
branberry Oct 7, 2024
834f340
format
branberry Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(biome_css_analyze): implement noDescendingSpecificity (#4097)
tunamaguro authored and branberry committed Sep 30, 2024

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
commit 4a22da8f4d0ab2b5b250a91f37ae6221fb1c576b
164 changes: 93 additions & 71 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/biome_css_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

use biome_analyze::declare_lint_group;

pub mod no_descending_specificity;
pub mod no_duplicate_custom_properties;
pub mod no_irregular_whitespace;
pub mod no_missing_var_function;
@@ -13,6 +14,7 @@ declare_lint_group! {
pub Nursery {
name : "nursery" ,
rules : [
self :: no_descending_specificity :: NoDescendingSpecificity ,
self :: no_duplicate_custom_properties :: NoDuplicateCustomProperties ,
self :: no_irregular_whitespace :: NoIrregularWhitespace ,
self :: no_missing_var_function :: NoMissingVarFunction ,
203 changes: 203 additions & 0 deletions crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use rustc_hash::{FxHashMap, FxHashSet};

use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
use biome_console::markup;
use biome_css_semantic::model::{Rule as CssSemanticRule, RuleId, SemanticModel, Specificity};
use biome_css_syntax::{AnyCssSelector, CssRoot};
use biome_rowan::TextRange;

use biome_rowan::AstNode;

use crate::services::semantic::Semantic;

declare_lint_rule! {
/// Disallow a lower specificity selector from coming after a higher specificity selector.
///
/// This rule prohibits placing selectors with lower specificity after selectors with higher specificity.
/// By maintaining the order of the source and specificity as consistently as possible, it enhances readability.
///
/// ## Examples
///
/// ### Invalid
///
/// ```css,expect_diagnostic
/// b a { color: red; }
/// a { color: red; }
/// ```
///
/// ```css,expect_diagnostic
/// a {
/// & > b { color: red; }
/// }
/// b { color: red; }
/// ```
///
/// ```css,expect_diagnostic
/// :root input {
/// color: red;
/// }
/// html input {
/// color: red;
/// }
/// ```
///
///
/// ### Valid
///
/// ```css
/// a { color: red; }
/// b a { color: red; }
/// ```
///
/// ```css
/// b { color: red; }
/// a {
/// & > b { color: red; }
/// }
/// ```
///
/// ```css
/// a:hover { color: red; }
/// a { color: red; }
/// ```
///
/// ```css
/// a b {
/// color: red;
/// }
/// /* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */
/// :where(a) :is(b) {
/// color: blue;
/// }
/// ```
///
pub NoDescendingSpecificity {
version: "next",
name: "noDescendingSpecificity",
language: "css",
recommended: true,
sources: &[RuleSource::Stylelint("no-descending-specificity")],
}
}

#[derive(Debug)]
pub struct DescendingSelector {
high: (TextRange, Specificity),
low: (TextRange, Specificity),
}
/// find tail selector
/// ```css
/// a b:hover {
/// ^^^^^^^
/// }
/// ```
fn find_tail_selector(selector: &AnyCssSelector) -> Option<String> {
match selector {
AnyCssSelector::CssCompoundSelector(s) => {
let simple = s
.simple_selector()
.map_or(String::new(), |s| s.syntax().text_trimmed().to_string());
let sub = s.sub_selectors().syntax().text_trimmed().to_string();

let last_selector = [simple, sub].join("");
Some(last_selector)
}
AnyCssSelector::CssComplexSelector(s) => {
s.right().as_ref().ok().and_then(find_tail_selector)
}
_ => None,
}
}

/// This function traverses the CSS rules starting from the given rule and checks for selectors that have the same tail selector.
/// For each selector, it compares its specificity with the previously encountered specificity of the same tail selector.
/// If a lower specificity selector is found after a higher specificity selector with the same tail selector, it records this as a descending selector.
fn find_descending_selector(
rule: &CssSemanticRule,
model: &SemanticModel,
visited_rules: &mut FxHashSet<RuleId>,
visited_selectors: &mut FxHashMap<String, (TextRange, Specificity)>,
descending_selectors: &mut Vec<DescendingSelector>,
) {
if visited_rules.contains(&rule.id) {
return;
} else {
visited_rules.insert(rule.id);
};

for selector in &rule.selectors {
let tail_selector = if let Some(s) = find_tail_selector(&selector.original) {
s
} else {
continue;
};

if let Some((last_textrange, last_specificity)) = visited_selectors.get(&tail_selector) {
if last_specificity > &selector.specificity {
descending_selectors.push(DescendingSelector {
high: (*last_textrange, last_specificity.clone()),
low: (selector.range, selector.specificity.clone()),
});
}
} else {
visited_selectors.insert(
tail_selector,
(selector.range, selector.specificity.clone()),
);
}
}

for child_id in &rule.child_ids {
if let Some(child_rule) = model.get_rule_by_id(*child_id) {
find_descending_selector(
child_rule,
model,
visited_rules,
visited_selectors,
descending_selectors,
);
}
}
}

impl Rule for NoDescendingSpecificity {
type Query = Semantic<CssRoot>;
type State = DescendingSelector;
type Signals = Vec<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let model = ctx.model();
let mut visited_rules = FxHashSet::default();
let mut visited_selectors = FxHashMap::default();
let mut descending_selectors = Vec::new();
for rule in model.rules() {
find_descending_selector(
rule,
model,
&mut visited_rules,
&mut visited_selectors,
&mut descending_selectors,
);
}

descending_selectors
}

fn diagnostic(_: &RuleContext<Self>, node: &Self::State) -> Option<RuleDiagnostic> {
Some(
RuleDiagnostic::new(
rule_category!(),
node.low.0,
markup! {
"Descending specificity selector found. This selector specificity is "{node.low.1.to_string()}
},
).detail(node.high.0, markup!(
"This selector specificity is "{node.high.1.to_string()}
))
.note(markup! {
"Descending specificity selector may not applied. Consider rearranging the order of the selectors. See "<Hyperlink href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">"MDN web docs"</Hyperlink>" for more details."
}),
)
}
}
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,7 @@
b a {
color: red;
}

a {
color: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: complex_selector.invalid.css
---
# Input
```css
b a {
color: red;
}

a {
color: red;
}

```

# Diagnostics
```
complex_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Descending specificity selector found. This selector specificity is (0, 0, 1)

3 │ }
4 │
> 5 │ a {
│ ^
6 │ color: red;
7 │ }

i This selector specificity is (0, 0, 2)

> 1 │ b a {
│ ^^^
2 │ color: red;
3 │ }

i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:is(#a, a) f {
color: red;
}

:is(a, b, c, d) f {
color: red;
}

:is(#fake#fake#fake#fake#fake#fake, *) g {
color: red;
}

:where(*) g {
color: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: function_pseudo_selector.invalid.css
---
# Input
```css
:is(#a, a) f {
color: red;
}

:is(a, b, c, d) f {
color: red;
}

:is(#fake#fake#fake#fake#fake#fake, *) g {
color: red;
}

:where(*) g {
color: red;
}
```

# Diagnostics
```
function_pseudo_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━

! Descending specificity selector found. This selector specificity is (0, 0, 2)

3 │ }
4 │
> 5 │ :is(a, b, c, d) f {
│ ^^^^^^^^^^^^^^^^^
6 │ color: red;
7 │ }

i This selector specificity is (1, 0, 1)

> 1 │ :is(#a, a) f {
│ ^^^^^^^^^^^^
2 │ color: red;
3 │ }

i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.


```

```
function_pseudo_selector.invalid.css:13:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━

! Descending specificity selector found. This selector specificity is (0, 0, 1)

11 │ }
12 │
> 13 │ :where(*) g {
│ ^^^^^^^^^^^
14 │ color: red;
15 │ }

i This selector specificity is (6, 0, 1)

7 │ }
8 │
> 9 │ :is(#fake#fake#fake#fake#fake#fake, *) g {
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 │ color: red;
11 │ }

i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
a {
& > b {
color: red;
}
}

b {
color: red;
}
Loading