From 05e761ef19c490257cb9d09dde0bec61567ed27f Mon Sep 17 00:00:00 2001
From: Cameron Clark
Date: Mon, 8 Jan 2024 22:07:48 +0000
Subject: [PATCH] feat(linter) eslint-plugin-next no-head-import-in-document
---
crates/oxc_linter/src/rules.rs | 2 +
.../nextjs/no_head_import_in_document.rs | 252 ++++++++++++++++++
.../snapshots/no_head_import_in_document.snap | 50 ++++
3 files changed, 304 insertions(+)
create mode 100644 crates/oxc_linter/src/rules/nextjs/no_head_import_in_document.rs
create mode 100644 crates/oxc_linter/src/snapshots/no_head_import_in_document.snap
diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index fac44806477c..e2d09d2b0d9d 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -276,6 +276,7 @@ mod nextjs {
pub mod no_assign_module_variable;
pub mod no_async_client_component;
pub mod no_css_tags;
+ pub mod no_head_import_in_document;
pub mod no_img_element;
pub mod no_script_component_in_head;
pub mod no_sync_scripts;
@@ -525,6 +526,7 @@ oxc_macros::declare_all_lint_rules! {
nextjs::no_assign_module_variable,
nextjs::no_async_client_component,
nextjs::no_css_tags,
+ nextjs::no_head_import_in_document,
nextjs::no_img_element,
nextjs::no_script_component_in_head,
nextjs::no_sync_scripts,
diff --git a/crates/oxc_linter/src/rules/nextjs/no_head_import_in_document.rs b/crates/oxc_linter/src/rules/nextjs/no_head_import_in_document.rs
new file mode 100644
index 000000000000..31a60e5c7aaa
--- /dev/null
+++ b/crates/oxc_linter/src/rules/nextjs/no_head_import_in_document.rs
@@ -0,0 +1,252 @@
+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-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.")]
+#[diagnostic(
+ severity(warning),
+ help("See https://nextjs.org/docs/messages/no-head-import-in-document")
+)]
+struct NoHeadImportInDocumentDiagnostic(#[label] pub Span);
+
+#[derive(Debug, Default, Clone)]
+pub struct NoHeadImportInDocument;
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ ///
+ /// ### Why is this bad?
+ ///
+ ///
+ /// ### Example
+ /// ```javascript
+ /// ```
+ NoHeadImportInDocument,
+ correctness
+);
+
+impl Rule for NoHeadImportInDocument {
+ 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/head" {
+ return;
+ }
+
+ let full_file_path = ctx.file_path();
+
+ if let Some(file_name) = full_file_path.file_name() {
+ if let Some(file_name) = file_name.to_str() {
+ // check `_document.*` case
+
+ if file_name.starts_with("_document.") {
+ ctx.diagnostic(NoHeadImportInDocumentDiagnostic(import_decl.span));
+ // check `_document/index.*` case
+ } else if file_name.starts_with("index") {
+ if let Some(p) = full_file_path.parent() {
+ if let Some(file_name) = p.file_name() {
+ if let Some(file_name) = file_name.to_str() {
+ if file_name.starts_with("_document") {
+ ctx.diagnostic(NoHeadImportInDocumentDiagnostic(
+ import_decl.span,
+ ));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn test() {
+ use crate::tester::Tester;
+ use std::path::PathBuf;
+
+ let pass = vec![
+ (
+ r"import Document, { Html, Head, Main, NextScript } from 'next/document'
+
+ class MyDocument extends Document {
+ static async getInitialProps(ctx) {
+ //...
+ }
+
+ render() {
+ return (
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document.tsx")),
+ ),
+ (
+ r#"import Head from "next/head";
+
+ export default function IndexPage() {
+ return (
+
+ My page title
+
+
+ );
+ }
+ "#,
+ None,
+ None,
+ Some(PathBuf::from("pages/index.tsx")),
+ ),
+ ];
+
+ let fail = vec![
+ (
+ r"
+ import Document, { Html, Main, NextScript } from 'next/document'
+ import Head from 'next/head'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document.js")),
+ ),
+ (
+ r"
+ import Document, { Html, Main, NextScript } from 'next/document'
+ import Head from 'next/head'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document.tsx")),
+ ),
+ (
+ r"
+ import Document, { Html, Main, NextScript } from 'next/document'
+ import Head from 'next/head'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document.page.tsx")),
+ ),
+ (
+ r"
+ import Document, { Html, Main, NextScript } from 'next/document'
+ import Head from 'next/head'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document/index.tsx")),
+ ),
+ (
+ r"
+ import Document, { Html, Main, NextScript } from 'next/document'
+ import Head from 'next/head'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ ",
+ None,
+ None,
+ Some(PathBuf::from("pages/_document/index.tsx")),
+ ),
+ ];
+
+ Tester::new_with_settings(NoHeadImportInDocument::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_head_import_in_document.snap b/crates/oxc_linter/src/snapshots/no_head_import_in_document.snap
new file mode 100644
index 000000000000..b5025ea07306
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_head_import_in_document.snap
@@ -0,0 +1,50 @@
+---
+source: crates/oxc_linter/src/tester.rs
+expression: no_head_import_in_document
+---
+ ⚠ eslint-plugin-next(no-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.
+ ╭─[no_head_import_in_document.tsx:2:1]
+ 2 │ import Document, { Html, Main, NextScript } from 'next/document'
+ 3 │ import Head from 'next/head'
+ · ────────────────────────────
+ 4 │
+ ╰────
+ help: See https://nextjs.org/docs/messages/no-head-import-in-document
+
+ ⚠ eslint-plugin-next(no-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.
+ ╭─[no_head_import_in_document.tsx:2:1]
+ 2 │ import Document, { Html, Main, NextScript } from 'next/document'
+ 3 │ import Head from 'next/head'
+ · ────────────────────────────
+ 4 │
+ ╰────
+ help: See https://nextjs.org/docs/messages/no-head-import-in-document
+
+ ⚠ eslint-plugin-next(no-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.
+ ╭─[no_head_import_in_document.tsx:2:1]
+ 2 │ import Document, { Html, Main, NextScript } from 'next/document'
+ 3 │ import Head from 'next/head'
+ · ────────────────────────────
+ 4 │
+ ╰────
+ help: See https://nextjs.org/docs/messages/no-head-import-in-document
+
+ ⚠ eslint-plugin-next(no-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.
+ ╭─[no_head_import_in_document.tsx:2:1]
+ 2 │ import Document, { Html, Main, NextScript } from 'next/document'
+ 3 │ import Head from 'next/head'
+ · ────────────────────────────
+ 4 │
+ ╰────
+ help: See https://nextjs.org/docs/messages/no-head-import-in-document
+
+ ⚠ eslint-plugin-next(no-head-import-in-document): Prevent usage of `next/head` in `pages/_document.js`.
+ ╭─[no_head_import_in_document.tsx:2:1]
+ 2 │ import Document, { Html, Main, NextScript } from 'next/document'
+ 3 │ import Head from 'next/head'
+ · ────────────────────────────
+ 4 │
+ ╰────
+ help: See https://nextjs.org/docs/messages/no-head-import-in-document
+
+