feat(linter): eslint-plugin-next/no-styled-jsx-in-document (#3184)

This commit is contained in:
Dunqing 2024-05-07 10:35:28 +08:00 committed by GitHub
parent 762677e17b
commit 5514936f51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 190 additions and 0 deletions

View file

@ -355,6 +355,7 @@ mod nextjs {
pub mod no_head_import_in_document;
pub mod no_img_element;
pub mod no_script_component_in_head;
pub mod no_styled_jsx_in_document;
pub mod no_sync_scripts;
pub mod no_title_in_document_head;
pub mod no_typos;
@ -703,6 +704,7 @@ 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_styled_jsx_in_document,
jsdoc::check_access,
jsdoc::check_property_names,
jsdoc::check_tag_names,

View file

@ -0,0 +1,176 @@
use oxc_ast::{
ast::{JSXAttributeItem, JSXElementName},
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-styled-jsx-in-document): `styled-jsx` should not be used in `pages/_document.js`")]
#[diagnostic(severity(warning), help("Possible to fix it please see: https://nextjs.org/docs/messages/no-styled-jsx-in-document#possible-ways-to-fix-it"))]
struct NoStyledJsxInDocumentDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoStyledJsxInDocument;
declare_oxc_lint!(
/// ### What it does
///
/// Prevent usage of styled-jsx in pages/_document.js.
///
/// ### Why is this bad?
///
/// Custom CSS like styled-jsx is not allowed in a [Custom Document](https://nextjs.org/docs/pages/building-your-application/routing/custom-document).
///
/// ### Example
/// ```javascript
/// ```
NoStyledJsxInDocument,
correctness,
);
impl Rule for NoStyledJsxInDocument {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::JSXOpeningElement(element) = node.kind() else {
return;
};
if !matches!(&element.name, JSXElementName::Identifier(ident) if ident.name == "style") {
return;
}
// Check only pages/_document.* file
let full_file_path = ctx.file_path();
let Some(file_name) = full_file_path.file_name() else {
return;
};
let Some(file_name) = file_name.to_str() else { return };
if !file_name.starts_with("_document.") {
return;
}
let has_jsx_attribute = element.attributes.iter().any(|attribute| {
matches!(attribute, JSXAttributeItem::Attribute(attribute) if attribute.is_identifier("jsx"))
});
if has_jsx_attribute {
ctx.diagnostic(NoStyledJsxInDocumentDiagnostic(element.span));
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
use std::path::PathBuf;
let pass = vec![
(
"import Document, { Html, Head, Main, NextScript } from 'next/document'
export class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}",
None,
None,
Some(PathBuf::from("pages/_document.tsx")),
),
(
r#"import Document, { Html, Head, Main, NextScript } from 'next/document'
export class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<style>{" body{ color:red; } "}</style>
<style {...{nonce: '123' }}></style>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}"#,
None,
None,
Some(PathBuf::from("pages/_document.tsx")),
),
(
"
export default function Page() {
return (
<>
<p>Hello world</p>
<style jsx>{`
p {
color: orange;
}
`}</style>
</>
)
}
",
None,
None,
Some(PathBuf::from("pages/index.jsx")),
),
];
let fail = vec![(
r#"
import Document, { Html, Head, Main, NextScript } from 'next/document'
export class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<style jsx>{" body{ color:red; } "}</style>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}"#,
None,
None,
Some(PathBuf::from("pages/_document.jsx")),
)];
Tester::new(NoStyledJsxInDocument::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,12 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_styled_jsx_in_document
---
⚠ eslint-plugin-next(no-styled-jsx-in-document): `styled-jsx` should not be used in `pages/_document.js`
╭─[no_styled_jsx_in_document.tsx:14:24]
13 │ <Head />
14 │ <style jsx>{" body{ color:red; } "}</style>
· ───────────
15 │ <body>
╰────
help: Possible to fix it please see: https://nextjs.org/docs/messages/no-styled-jsx-in-document#possible-ways-to-fix-it