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 + +