Skip to content

Commit

Permalink
feat(lint): add noImgElement rule (#4155)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaioduarte authored Oct 7, 2024
1 parent 0398828 commit 0d8cc8f
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 59 deletions.

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

137 changes: 78 additions & 59 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ define_categories! {
"lint/nursery/noDynamicNamespaceImportAccess": "https://biomejs.dev/linter/rules/no-dynamic-namespace-import-access",
"lint/nursery/noEnum": "https://biomejs.dev/linter/rules/no-enum",
"lint/nursery/noExportedImports": "https://biomejs.dev/linter/rules/no-exported-imports",
"lint/nursery/noImgElement": "https://biomejs.dev/linter/rules/no-img-element",
"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/noInvalidGridAreas": "https://biomejs.dev/linter/rules/use-consistent-grid-areas",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod no_dynamic_namespace_import_access;
pub mod no_enum;
pub mod no_exported_imports;
pub mod no_head_element;
pub mod no_img_element;
pub mod no_irregular_whitespace;
pub mod no_nested_ternary;
pub mod no_octal_escape;
Expand Down Expand Up @@ -41,6 +42,7 @@ declare_lint_group! {
self :: no_enum :: NoEnum ,
self :: no_exported_imports :: NoExportedImports ,
self :: no_head_element :: NoHeadElement ,
self :: no_img_element :: NoImgElement ,
self :: no_irregular_whitespace :: NoIrregularWhitespace ,
self :: no_nested_ternary :: NoNestedTernary ,
self :: no_octal_escape :: NoOctalEscape ,
Expand Down
113 changes: 113 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/no_img_element.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use biome_analyze::RuleSourceKind;
use biome_analyze::{
context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource,
};
use biome_console::markup;
use biome_js_syntax::jsx_ext::AnyJsxElement;
use biome_js_syntax::{JsxChildList, JsxElement};
use biome_rowan::{AstNode, AstNodeList};

declare_lint_rule! {
/// Prevent usage of `<img>` element in a Next.js project.
///
/// Using the `<img>` element can result in slower Largest Contentful Paint (LCP)
/// and higher bandwidth usage, as it lacks the optimizations provided by the `<Image />`
/// component from `next/image`. Next.js's `<Image />` automatically optimizes images
/// by serving responsive sizes and using modern formats, improving performance and reducing bandwidth.
///
/// If your project is self-hosted, ensure that you have sufficient storage and have
/// installed the `sharp` package to support optimized images. When deploying to managed
/// hosting providers, be aware of potential additional costs or usage.
///
/// ## Examples
///
/// ### Invalid
///
/// ```jsx,expect_diagnostic
/// <img alt="Foo" />
/// ```
///
/// ```jsx,expect_diagnostic
/// <div>
/// <img alt="Foo" />
/// </div>
/// ```
///
/// ### Valid
///
/// ```jsx
/// <img />
/// ```
///
/// ```jsx
/// <Image src="https://example.com/hero.jpg" />
/// ```
///
/// ```jsx
/// <picture>
/// <source srcSet="https://example.com/hero.avif" type="image/avif" />
/// <source srcSet="https://example.com/hero.webp" type="image/webp" />
/// <img src="https://example.com/hero.jpg" />
/// </picture>
/// ```
///
pub NoImgElement {
version: "next",
name: "noImgElement",
language: "jsx",
sources: &[RuleSource::EslintNext("no-img-element")],
source_kind: RuleSourceKind::SameLogic,
recommended: false,
}
}

impl Rule for NoImgElement {
type Query = Ast<AnyJsxElement>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();

if node.name().ok()?.name_value_token()?.text_trimmed() != "img"
|| node.attributes().is_empty()
{
return None;
}

if let AnyJsxElement::JsxSelfClosingElement(jsx) = &node {
let Some(parent) = jsx.parent::<JsxChildList>() else {
return Some(());
};
let Some(parent) = parent.parent::<JsxElement>() else {
return Some(());
};
let Some(opening_element) = parent.opening_element().ok() else {
return Some(());
};
let name = opening_element.name().ok()?.name_value_token()?;

if name.text_trimmed() == "picture" {
return None;
}
}

Some(())
}

fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
return Some(
RuleDiagnostic::new(
rule_category!(),
ctx.query().range(),
markup! {
"Don't use "<Emphasis>"<img>"</Emphasis>" element."
},
)
.note(markup! {
"Using the "<Emphasis>"<img>"</Emphasis>" can lead to slower LCP and higher bandwidth. Consider using "<Emphasis>"<Image />"</Emphasis>" from "<Emphasis>"next/image"</Emphasis>" to automatically optimize images."
})
);
}
}
2 changes: 2 additions & 0 deletions crates/biome_js_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 @@
<>
<img alt="Foo" />

<div>
<img alt="Foo" />
</div>
</>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: invalid.jsx
---
# Input
```jsx
<>
<img alt="Foo" />

<div>
<img alt="Foo" />
</div>
</>

```

# Diagnostics
```
invalid.jsx:2:2 lint/nursery/noImgElement ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Don't use <img> element.
1 │ <>
> 2 │ <img alt="Foo" />
│ ^^^^^^^^^^^^^^^^^
3 │
4 │ <div>
i Using the <img> can lead to slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images.
```

```
invalid.jsx:5:3 lint/nursery/noImgElement ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Don't use <img> element.
4 │ <div>
> 5 │ <img alt="Foo" />
│ ^^^^^^^^^^^^^^^^^
6 │ </div>
7 │ </>
i Using the <img> can lead to slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<>
<img />

<picture>
<img alt="Foo" />
</picture>
</>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: valid.jsx
---
# Input
```jsx
<>
<img />

<picture>
<img alt="Foo" />
</picture>
</>

```
5 changes: 5 additions & 0 deletions packages/@biomejs/backend-jsonrpc/src/workspace.ts

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

7 changes: 7 additions & 0 deletions packages/@biomejs/biome/configuration_schema.json

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

0 comments on commit 0d8cc8f

Please sign in to comment.