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(lint): add noHeadImportInDocument rule #4184

Merged
merged 6 commits into from
Oct 9, 2024
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
10 changes: 10 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

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

146 changes: 83 additions & 63 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ 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/noHeadElement": "https://biomejs.dev/linter/rules/no-head-element",
"lint/nursery/noHeadImportInDocument": "https://biomejs.dev/linter/rules/no-head-import-in-document",
"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",
Expand All @@ -154,7 +156,6 @@ define_categories! {
"lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword",
"lint/nursery/noMissingVarFunction": "https://biomejs.dev/linter/rules/no-missing-var-function",
"lint/nursery/noNestedTernary": "https://biomejs.dev/linter/rules/no-nested-ternary",
"lint/nursery/noHeadElement": "https://biomejs.dev/linter/rules/no-head-element",
"lint/nursery/noOctalEscape": "https://biomejs.dev/linter/rules/no-octal-escape",
"lint/nursery/noProcessEnv": "https://biomejs.dev/linter/rules/no-process-env",
"lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props",
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_head_import_in_document;
pub mod no_img_element;
pub mod no_irregular_whitespace;
pub mod no_nested_ternary;
Expand Down Expand Up @@ -42,6 +43,7 @@ declare_lint_group! {
self :: no_enum :: NoEnum ,
self :: no_exported_imports :: NoExportedImports ,
self :: no_head_element :: NoHeadElement ,
self :: no_head_import_in_document :: NoHeadImportInDocument ,
self :: no_img_element :: NoImgElement ,
self :: no_irregular_whitespace :: NoIrregularWhitespace ,
self :: no_nested_ternary :: NoNestedTernary ,
Expand Down
122 changes: 122 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/no_head_import_in_document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use biome_analyze::{
context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, RuleSourceKind,
};
use biome_console::markup;
use biome_js_syntax::{JsFileSource, JsImport};
use biome_rowan::AstNode;
use std::path::MAIN_SEPARATOR;

declare_lint_rule! {
/// Prevent using the `next/head` module in `pages/_document.js` on Next.js projects.
///
/// Importing `next/head` within the custom `pages/_document.js` file can cause
/// unexpected behavior in your application. The `next/head` component is designed
/// to be used at the page level, and when used in the custom document it can interfere
/// with the global document structure, which leads to issues with rendering and SEO.
///
/// To modify `<head>` elements across all pages, you should use the `<Head />`
/// component from the `next/document` module.
///
/// ## Examples
///
/// ### Valid
///
/// ```jsx
/// // pages/_document.js
/// import Document, { Html, Head, Main, NextScript } from "next/document";
///
/// class MyDocument extends Document {
/// static async getInitialProps(ctx) {
/// //...
/// }
///
/// render() {
/// return (
/// <Html>
/// <Head></Head>
/// </Html>
/// );
/// }
/// }
///
/// export default MyDocument;
/// ```
///
pub NoHeadImportInDocument {
version: "next",
name: "noHeadImportInDocument",
language: "jsx",
sources: &[RuleSource::EslintNext("no-head-import-in-document")],
source_kind: RuleSourceKind::SameLogic,
recommended: false,
}
}

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

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
if !ctx.source_type::<JsFileSource>().is_jsx() {
return None;
}

let import = ctx.query();
let import_source = import.import_clause().ok()?.source().ok()?;
let module_name = import_source.inner_string_text().ok()?;

if module_name != "next/head" {
return None;
}

let path = ctx.file_path();

if !path
.ancestors()
.filter_map(|a| a.file_name())
.any(|f| f == "pages")
{
return None;
}

let file_name = path.file_stem()?.to_str()?;

// pages/_document.(jsx|tsx)
if file_name == "_document" {
return Some(());
}

let parent_name = path.parent()?.file_stem()?.to_str()?;

// pages/_document/index.(jsx|tsx)
if parent_name == "_document" && file_name == "index" {
return Some(());
}

None
}

fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
let path = ctx.file_path().to_str()?.split("pages").nth(1)?;
let path = if cfg!(debug_assertions) {
path.replace(MAIN_SEPARATOR, "/")
} else {
path.to_string()
};

return Some(
RuleDiagnostic::new(
rule_category!(),
ctx.query().range(),
markup! {
"Don't use "<Emphasis>"next/head"</Emphasis>" in pages"{path}""
},
)
.note(markup! {
"Using the "<Emphasis>"next/head"</Emphasis>" in document pages can cause unexpected issues. Use "<Emphasis>"<Head />"</Emphasis>" from "<Emphasis>"next/document"</Emphasis>" instead."
})
);
}
}
1 change: 1 addition & 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 @@
import Head from "next/head";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: _document.jsx
---
# Input
```jsx
import Head from "next/head";
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Head from "next/head";
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: _document.jsx
---
# Input
```jsx
import Head from "next/head";
```

# Diagnostics
```
_document.jsx:1:1 lint/nursery/noHeadImportInDocument ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Don't use next/head in pages/_document.jsx

> 1 │ import Head from "next/head";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

i Using the next/head in document pages can cause unexpected issues. Use <Head /> from next/document instead.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Head from "next/head";
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: index.jsx
---
# Input
```jsx
import Head from "next/head";
```

# Diagnostics
```
index.jsx:1:1 lint/nursery/noHeadImportInDocument ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Don't use next/head in pages/_document/index.jsx

> 1 │ import Head from "next/head";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

i Using the next/head in document pages can cause unexpected issues. Use <Head /> from next/document instead.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Head from "next/head";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 86
expression: index.jsx
---
# Input
```jsx
import Head from "next/head";
```
7 changes: 6 additions & 1 deletion 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.