Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/brown-dryers-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Added `ignore` option to [`noUnknownProperty`](https://biomejs.dev/linter/rules/no-unknown-property). If an unknown property name matches any of the items provided in `ignore`, a diagnostic won't be emitted.
5 changes: 5 additions & 0 deletions .changeset/mean-buses-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Added `ignore` option to [`noUnknownFunction`](https://biomejs.dev/linter/rules/no-unknown-function). If an unknown function name matches any of the items provided in `ignore`, a diagnostic won't be emitted.
5 changes: 5 additions & 0 deletions .changeset/nine-ends-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Added `ignore` option to [`noUnknownPseudoClass`](https://biomejs.dev/linter/rules/no-unknown-pseudo-class). If an unknown pseudo-class name matches any of the items provided in `ignore`, a diagnostic won't be emitted.
5 changes: 5 additions & 0 deletions .changeset/rich-flowers-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Added `ignore` option to [`noUnknownPseudoElement`](https://biomejs.dev/linter/rules/no-unknown-selector-pseudo-element). If an unknown pseudo-element name matches any of the items provided in `ignore`, a diagnostic won't be emitted.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for references to this rule and its URL patterns
echo "=== Searching for noUnknownPseudoElement references ==="
rg -i "noUnknownPseudoElement|no-unknown.*pseudo" --type md --type json --type ts --type js | head -20

echo -e "\n=== Checking changeset files for URL patterns ==="
find .changeset -name "*.md" -type f -exec grep -H "biomejs.dev" {} \; | head -10

echo -e "\n=== Checking if rule slug exists in codebase ==="
rg "no-unknown-pseudo-element|no-unknown-selector-pseudo" --type md --type json | head -10

Repository: biomejs/biome

Length of output: 5955


Fix documentation URL in changeset.

Change no-unknown-selector-pseudo-element to no-unknown-pseudo-element — all current references in the codebase (configuration schema, backend, CHANGELOG) use the correct kebab-case slug without the redundant "selector" prefix.

🤖 Prompt for AI Agents
In @.changeset/rich-flowers-hunt.md at line 5, The changeset contains a
documentation URL referencing the incorrect rule slug
`no-unknown-selector-pseudo-element`; update that URL and any mention in this
changeset to use the correct kebab-case slug `no-unknown-pseudo-element` (e.g.,
change the link to https://biomejs.dev/linter/rules/no-unknown-pseudo-element)
so it matches the configuration schema, backend, and CHANGELOG references.

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ declare_lint_rule! {
/// a { transform: scale(1); }
/// ```
///
/// ## Options
///
/// ### `ignore`
///
/// A list of unknown function names to ignore (case-insensitive).
///
/// ```json,options
/// {
/// "options": {
/// "ignore": [
/// "custom-function"
/// ]
/// }
/// }
/// ```
///
/// #### Valid
///
/// ```css,use_options
/// a { transform: custom-function(1); }
/// ```
///
pub NoUnknownFunction {
version: "1.8.0",
name: "noUnknownFunction",
Expand Down Expand Up @@ -69,6 +91,10 @@ impl Rule for NoUnknownFunction {
return None;
}

if should_ignore(function_name, ctx.options()) {
return None;
}

Some(NoUnknownFunctionState {
function_name: function_name.into(),
span: node.name().ok()?.range(),
Expand All @@ -93,3 +119,12 @@ impl Rule for NoUnknownFunction {
)
}
}

fn should_ignore(name: &str, options: &NoUnknownFunctionOptions) -> bool {
for ignore_pattern in &options.ignore {
if name.eq_ignore_ascii_case(ignore_pattern) {
return true;
}
}
false
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ declare_lint_rule! {
/// }
/// ```
///
/// ## Options
///
/// ### `ignore`
///
/// A list of unknown property names to ignore (case-insensitive).
///
/// ```json,options
/// {
/// "options": {
/// "ignore": [
/// "custom-property"
/// ]
/// }
/// }
/// ```
///
/// #### Valid
///
/// ```css,use_options
/// a {
/// custom-property: black;
/// }
/// ```
///
pub NoUnknownProperty {
version: "1.8.0",
name: "noUnknownProperty",
Expand Down Expand Up @@ -92,6 +116,7 @@ impl Rule for NoUnknownProperty {
&& property_name_lower != "composes"
&& !is_known_properties(&property_name_lower)
&& !vendor_prefixed(&property_name_lower)
&& !should_ignore(&property_name_lower, ctx.options())
{
return Some(node.name().ok()?.range());
}
Expand All @@ -116,3 +141,12 @@ impl Rule for NoUnknownProperty {
)
}
}

fn should_ignore(name: &str, options: &NoUnknownPropertyOptions) -> bool {
for ignore_pattern in &options.ignore {
if name.eq_ignore_ascii_case(ignore_pattern) {
return true;
}
}
false
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@ declare_lint_rule! {
/// input:-moz-placeholder {}
/// ```
///
/// ## Options
///
/// ### `ignore`
///
/// A list of unknown pseudo-class names to ignore (case-insensitive).
///
/// ```json,options
/// {
/// "options": {
/// "ignore": [
/// "custom-pseudo-class"
/// ]
/// }
/// }
/// ```
///
/// #### Valid
///
/// ```css,use_options
/// a:custom-pseudo-class {}
/// ```
///
pub NoUnknownPseudoClass {
version: "1.8.0",
name: "noUnknownPseudoClass",
Expand Down Expand Up @@ -177,7 +199,9 @@ impl Rule for NoUnknownPseudoClass {
}
};

if is_valid_class || file_source.is_css_modules() && is_css_module_pseudo_class(lower_name)
if is_valid_class
|| should_ignore(lower_name, ctx.options())
|| file_source.is_css_modules() && is_css_module_pseudo_class(lower_name)
{
None
} else {
Expand Down Expand Up @@ -222,3 +246,12 @@ impl Rule for NoUnknownPseudoClass {
Some(diag)
}
}

fn should_ignore(name: &str, options: &NoUnknownPseudoClassOptions) -> bool {
for ignore_pattern in &options.ignore {
if name.eq_ignore_ascii_case(ignore_pattern) {
return true;
}
}
false
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ declare_lint_rule! {
/// input::-moz-placeholder {}
/// ```
///
/// ## Options
///
/// ### `ignore`
///
/// A list of unknown pseudo-element names to ignore (case-insensitive).
///
/// ```json,options
/// {
/// "options": {
/// "ignore": [
/// "custom-pseudo-element"
/// ]
/// }
/// }
/// ```
///
/// #### Valid
///
/// ```css,use_options
/// ::custom-pseudo-element {}
/// ```
///
pub NoUnknownPseudoElement {
version: "1.8.0",
name: "noUnknownPseudoElement",
Expand All @@ -74,20 +96,30 @@ impl Rule for NoUnknownPseudoElement {

let should_not_trigger = match &pseudo_element {
AnyCssPseudoElement::CssBogusPseudoElement(element) => {
should_not_trigger(element.to_trimmed_text().text(), file_source)
should_not_trigger(element.to_trimmed_text().text(), file_source, ctx.options())
}
AnyCssPseudoElement::CssPseudoElementFunctionCustomIdentifier(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementFunctionSelector(selector) => {
should_not_trigger(selector.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementIdentifier(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementFunction(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
should_not_trigger(
ident.name().ok()?.to_trimmed_text().text(),
file_source,
ctx.options(),
)
}
AnyCssPseudoElement::CssPseudoElementFunctionSelector(selector) => should_not_trigger(
selector.name().ok()?.to_trimmed_text().text(),
file_source,
ctx.options(),
),
AnyCssPseudoElement::CssPseudoElementIdentifier(ident) => should_not_trigger(
ident.name().ok()?.to_trimmed_text().text(),
file_source,
ctx.options(),
),
AnyCssPseudoElement::CssPseudoElementFunction(ident) => should_not_trigger(
ident.name().ok()?.to_trimmed_text().text(),
file_source,
ctx.options(),
),
};

if should_not_trigger {
Expand Down Expand Up @@ -121,12 +153,28 @@ impl Rule for NoUnknownPseudoElement {
}

/// It doesn't trigger the rule if the pseudo-element name isn't a vendor prefix or is a pseudo-element
fn should_not_trigger(pseudo_element_name: &str, file_source: &CssFileSource) -> bool {
fn should_not_trigger(
pseudo_element_name: &str,
file_source: &CssFileSource,
options: &NoUnknownPseudoElementOptions,
) -> bool {
let lowercase = pseudo_element_name.to_ascii_lowercase_cow();
let lowercase = &lowercase.as_ref();

if file_source.is_css_modules() {
return ["global", "local"]
.contains(&pseudo_element_name.to_ascii_lowercase_cow().as_ref());
return ["global", "local"].contains(lowercase);
}

!vender_prefix(pseudo_element_name).is_empty()
|| is_pseudo_elements(pseudo_element_name.to_ascii_lowercase_cow().as_ref())
|| is_pseudo_elements(lowercase)
|| should_ignore(pseudo_element_name, options)
}

fn should_ignore(name: &str, options: &NoUnknownPseudoElementOptions) -> bool {
for ignore_pattern in &options.ignore {
if name.eq_ignore_ascii_case(ignore_pattern) {
return true;
}
}
false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* should not generate diagnostics */

/* These should be valid because they're in the ignore list */
a { transform: custom-function(1px); }
a { transform: MyCustomFunction(1px); }
a { transform: mycustomfunction(1px); }
a { transform: another-custom-function(1px); }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: valid_with_ignore.css
---
# Input
```css
/* should not generate diagnostics */

/* These should be valid because they're in the ignore list */
a { transform: custom-function(1px); }
a { transform: MyCustomFunction(1px); }
a { transform: mycustomfunction(1px); }
a { transform: another-custom-function(1px); }

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"correctness": {
"noUnknownFunction": {
"level": "error",
"options": {
"ignore": [
"custom-function",
"MyCustomFunction",
"another-custom-function"
]
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* should not generate diagnostics */

/* These should be valid because they're in the ignore list */
a {
custom-property: green;
MyCustomProperty: green;
mycustomproperty: green;
another-custom-property: green;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: valid_with_ignore.css
---
# Input
```css
/* should not generate diagnostics */

/* These should be valid because they're in the ignore list */
a {
custom-property: green;
MyCustomProperty: green;
mycustomproperty: green;
another-custom-property: green;
}

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"correctness": {
"noUnknownProperty": {
"level": "error",
"options": {
"ignore": [
"custom-property",
"MyCustomProperty",
"another-custom-property"
]
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* should not generate diagnostics */

/* These should be valid because they're in the ignore list */
a:custom-pseudo-class { }
a:MyCustomPseudoClass { }
a:mycustompseudoclass { }
a:another-custom-pseudo-class { }
Loading