diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 70fc5c036ee6c..0edcbc819d2c6 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -295,6 +295,14 @@ impl<'a> Expression<'a> { _ => false, } } + + pub fn as_identifier(&self) -> Option<&Box<'a, IdentifierReference>> { + if let Self::Identifier(v) = self { + Some(v) + } else { + None + } + } } /// Identifier Name diff --git a/crates/oxc_ast/src/ast/jsx.rs b/crates/oxc_ast/src/ast/jsx.rs index 907570c2d6d01..28e9152b752bc 100644 --- a/crates/oxc_ast/src/ast/jsx.rs +++ b/crates/oxc_ast/src/ast/jsx.rs @@ -86,6 +86,16 @@ pub enum JSXElementName<'a> { MemberExpression(Box<'a, JSXMemberExpression<'a>>), } +impl<'a> JSXElementName<'a> { + pub fn as_identifier(&self) -> Option<&JSXIdentifier> { + if let Self::Identifier(v) = self { + Some(v) + } else { + None + } + } +} + /// JSX Namespaced Name #[derive(Debug, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] @@ -237,6 +247,16 @@ pub enum JSXChild<'a> { Spread(JSXSpreadChild<'a>), } +impl<'a> JSXChild<'a> { + pub fn as_element(&self) -> Option<&Box<'a, JSXElement<'a>>> { + if let Self::Element(v) = self { + Some(v) + } else { + None + } + } +} + #[derive(Debug, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] #[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))] diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 3874e20251b2c..894163de02148 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -307,6 +307,7 @@ mod nextjs { pub mod no_before_interactive_script_outside_document; pub mod no_css_tags; pub mod no_document_import_in_page; + pub mod no_duplicate_head; pub mod no_head_element; pub mod no_head_import_in_document; pub mod no_img_element; @@ -596,4 +597,5 @@ oxc_macros::declare_all_lint_rules! { nextjs::no_document_import_in_page, nextjs::no_unwanted_polyfillio, nextjs::no_before_interactive_script_outside_document, + nextjs::no_duplicate_head, } diff --git a/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs new file mode 100644 index 0000000000000..a070932a04dc6 --- /dev/null +++ b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs @@ -0,0 +1,220 @@ +use oxc_ast::{ + ast::{Expression, ImportDeclarationSpecifier::ImportDefaultSpecifier}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-next(no-duplicate-head): Do not include multiple instances of `
+ /// + ///`. See: https://nextjs.org/docs/messages/no-duplicate-head")] +#[diagnostic( + severity(warning), + help( + "Only use a single `
` component in your custom document in `pages/_document.js`." + ) +)] +struct NoDuplicateHeadDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoDuplicateHead; + +declare_oxc_lint!( + /// ### What it does + /// Prevent duplicate usage of
in pages/_document.js. + /// + /// ### Why is this bad? + /// This can cause unexpected behavior in your application. + /// + /// ### Example + /// ```javascript + /// import Document, { Html, Head, Main, NextScript } from 'next/document' + /// class MyDocument extends Document { + /// static async getInitialProps(ctx) { + /// } + /// render() { + /// return ( + /// + ///
+ ///
+
+
+ + ) + } + } + + export default MyDocument + ", + r" + import Document, { Html, Main, NextScript } from 'next/document' + import Head from 'next/head' + + class MyDocument extends Document { + render() { + return ( + +
+ + + +
`. See: https://nextjs.org/docs/messages/no-duplicate-head + ╭─[no_duplicate_head.tsx:9:27] + 8 │ + 9 │
+ · ──────── + 10 │
+ ╰──── + help: Only use a single `
` component in your custom document in `pages/_document.js`. + + ⚠ eslint-plugin-next(no-duplicate-head): Do not include multiple instances of `
`. See: https://nextjs.org/docs/messages/no-duplicate-head + ╭─[no_duplicate_head.tsx:10:27] + 9 │
+ 10 │
+ · ──────── + 11 │
+ ╰──── + help: Only use a single `
` component in your custom document in `pages/_document.js`. + + ⚠ eslint-plugin-next(no-duplicate-head): Do not include multiple instances of `
`. See: https://nextjs.org/docs/messages/no-duplicate-head + ╭─[no_duplicate_head.tsx:11:27] + 10 │
+ 11 │
+ · ──────── + 12 │ + ╰──── + help: Only use a single `
` component in your custom document in `pages/_document.js`. + + ⚠ eslint-plugin-next(no-duplicate-head): Do not include multiple instances of `
`. See: https://nextjs.org/docs/messages/no-duplicate-head + ╭─[no_duplicate_head.tsx:9:27] + 8 │ + 9 │ ╭─▶
+ 10 │ │ + 11 │ │ + 15 │ ╰─▶ + 16 │
` component in your custom document in `pages/_document.js`. + + ⚠ eslint-plugin-next(no-duplicate-head): Do not include multiple instances of `
`. See: https://nextjs.org/docs/messages/no-duplicate-head + ╭─[no_duplicate_head.tsx:20:27] + 19 │ + 20 │ ╭─▶
+ 21 │ │ + 26 │ ╰─▶ + 27 │