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(linter): implement @next/next/no-document-import-in-page #1997

Merged
merged 3 commits into from
Jan 13, 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
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ mod nextjs {
pub mod no_assign_module_variable;
pub mod no_async_client_component;
pub mod no_css_tags;
pub mod no_document_import_in_page;
pub mod no_head_element;
pub mod no_head_import_in_document;
pub mod no_img_element;
Expand Down Expand Up @@ -551,4 +552,5 @@ oxc_macros::declare_all_lint_rules! {
nextjs::no_sync_scripts,
nextjs::no_title_in_document_head,
nextjs::no_typos,
nextjs::no_document_import_in_page,
}
219 changes: 219 additions & 0 deletions crates/oxc_linter/src/rules/nextjs/no_document_import_in_page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use oxc_ast::{ast::ModuleDeclaration, AstKind};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-next(no-document-import-in-page): `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https://nextjs.org/docs/messages/no-document-import-in-page")]
#[diagnostic(
severity(warning),
help("Prevent importing `next/document` outside of `pages/_document.js`.")
)]
struct NoDocumentImportInPageDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct NoDocumentImportInPage;

declare_oxc_lint!(
/// ### What it does
/// Prevent importing `next/document` outside of `pages/_document.js`.
///
/// ### Why is this bad?
///
///
/// ### Example
/// ```javascript
/// ```
NoDocumentImportInPage,
correctness
);

impl Rule for NoDocumentImportInPage {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::ModuleDeclaration(ModuleDeclaration::ImportDeclaration(import_decl)) =
node.kind()
else {
return;
};

if import_decl.source.value.as_str() != "next/document" {
return;
}

let Some(path) = ctx.file_path().to_str() else { return };
let Some(page) = path.split("pages").last() else { return };

if page.starts_with("/_document") || page.starts_with("\\_document") {
return;
}

ctx.diagnostic(NoDocumentImportInPageDiagnostic(import_decl.span));
}
}

#[test]
fn test() {
use crate::tester::Tester;
use std::path::PathBuf;

let pass = vec![
(
r#"import Document from "next/document"

export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document.js")),
),
(
r#"import Document from "next/document"

export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document.js")),
),
(
r#"import NextDocument from "next/document"

export default class MyDocument extends NextDocument {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document.tsx")),
),
(
r#"import Document from "next/document"

export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document.page.tsx")),
),
(
r#"import NDocument from "next/document"

export default class Document extends NDocument {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document/index.js")),
),
(
r#"import NDocument from "next/document"

export default class Document extends NDocument {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pages/_document/index.tsx")),
),
(
r#"import Document from "next/document"

export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
"#,
None,
None,
Some(PathBuf::from("pagesapp/src/pages/_document.js")),
),
];

let fail = vec![
(
r#"import Document from "next/document"

export const Test = () => <p>Test</p>
"#,
None,
None,
Some(PathBuf::from("components/test.js")),
),
(
r#"import Document from "next/document"

export const Test = () => <p>Test</p>
"#,
None,
None,
Some(PathBuf::from("pages/test.js")),
),
(
r#"import Document from "next/document"

export const Test = () => <p>Test</p>
"#,
None,
None,
Some(PathBuf::from("src/pages/user/test.tsx")),
),
(
r#"import Document from "next/document"

export const Test = () => <p>Test</p>
"#,
None,
None,
Some(PathBuf::from("src/pages/user/_document.tsx")),
),
];

Tester::new(NoDocumentImportInPage::NAME, pass, fail).test_and_snapshot();
}
41 changes: 41 additions & 0 deletions crates/oxc_linter/src/snapshots/no_document_import_in_page.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_document_import_in_page
---
⚠ eslint-plugin-next(no-document-import-in-page): `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https://nextjs.org/docs/messages/no-document-
│ import-in-page
╭─[no_document_import_in_page.tsx:1:1]
1 │ import Document from "next/document"
· ────────────────────────────────────
2 │
╰────
help: Prevent importing `next/document` outside of `pages/_document.js`.

⚠ eslint-plugin-next(no-document-import-in-page): `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https://nextjs.org/docs/messages/no-document-
│ import-in-page
╭─[no_document_import_in_page.tsx:1:1]
1 │ import Document from "next/document"
· ────────────────────────────────────
2 │
╰────
help: Prevent importing `next/document` outside of `pages/_document.js`.

⚠ eslint-plugin-next(no-document-import-in-page): `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https://nextjs.org/docs/messages/no-document-
│ import-in-page
╭─[no_document_import_in_page.tsx:1:1]
1 │ import Document from "next/document"
· ────────────────────────────────────
2 │
╰────
help: Prevent importing `next/document` outside of `pages/_document.js`.

⚠ eslint-plugin-next(no-document-import-in-page): `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https://nextjs.org/docs/messages/no-document-
│ import-in-page
╭─[no_document_import_in_page.tsx:1:1]
1 │ import Document from "next/document"
· ────────────────────────────────────
2 │
╰────
help: Prevent importing `next/document` outside of `pages/_document.js`.