mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter): implement @next/next/no-before-interactive-script-outsi… (#2203)
…de-document implement @next/next/no-before-interactive-script-outside-document Related issue: https://github.com/oxc-project/oxc/issues/1929 original implementation - code: https://github.com/vercel/next.js/blob/canary/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts - test: https://github.com/vercel/next.js/blob/canary/test/unit/eslint-plugin-next/no-before-interactive-script-outside-document.test.ts - doc: https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
This commit is contained in:
parent
f59e87f9c4
commit
da3b3057a2
8 changed files with 483 additions and 19 deletions
|
|
@ -299,6 +299,7 @@ mod nextjs {
|
|||
pub mod next_script_for_ga;
|
||||
pub mod no_assign_module_variable;
|
||||
pub mod no_async_client_component;
|
||||
pub mod no_before_interactive_script_outside_document;
|
||||
pub mod no_css_tags;
|
||||
pub mod no_document_import_in_page;
|
||||
pub mod no_head_element;
|
||||
|
|
@ -584,4 +585,5 @@ oxc_macros::declare_all_lint_rules! {
|
|||
nextjs::no_typos,
|
||||
nextjs::no_document_import_in_page,
|
||||
nextjs::no_unwanted_polyfillio,
|
||||
nextjs::no_before_interactive_script_outside_document,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,389 @@
|
|||
use oxc_ast::{
|
||||
ast::{JSXAttributeItem, JSXAttributeName, JSXAttributeValue, JSXElementName, JSXIdentifier},
|
||||
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,
|
||||
utils::{get_next_script_import_local_name, is_document_page, is_in_app_dir},
|
||||
AstNode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`")]
|
||||
#[diagnostic(
|
||||
severity(warning),
|
||||
help("See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document")
|
||||
)]
|
||||
struct NoBeforeInteractiveScriptOutsideDocumentDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoBeforeInteractiveScriptOutsideDocument;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
/// Prevent usage of `next/script`'s `beforeInteractive` strategy outside of `pages/_document.js`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// ```
|
||||
NoBeforeInteractiveScriptOutsideDocument,
|
||||
correctness
|
||||
);
|
||||
|
||||
impl Rule for NoBeforeInteractiveScriptOutsideDocument {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::JSXOpeningElement(jsx_el) = node.kind() {
|
||||
let Some(file_path) = ctx.file_path().to_str() else { return };
|
||||
if is_in_app_dir(file_path) {
|
||||
return;
|
||||
}
|
||||
let JSXElementName::Identifier(JSXIdentifier { name: tag_name, .. }) = &jsx_el.name
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if jsx_el.attributes.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(JSXAttributeItem::Attribute(strategy)) =
|
||||
jsx_el.attributes.iter().find(|attr| {
|
||||
matches!(
|
||||
attr,
|
||||
JSXAttributeItem::Attribute(jsx_attr)
|
||||
if matches!(
|
||||
&jsx_attr.name,
|
||||
JSXAttributeName::Identifier(id) if id.name.as_str() == "strategy"
|
||||
)
|
||||
)
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(JSXAttributeValue::StringLiteral(strategy_value)) = &strategy.value {
|
||||
if strategy_value.value.as_str() == "beforeInteractive" {
|
||||
if is_document_page(file_path) {
|
||||
return;
|
||||
}
|
||||
let next_script_import_local_name = get_next_script_import_local_name(ctx);
|
||||
if !matches!(next_script_import_local_name, Some(import) if tag_name.as_str() == import.as_str())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ctx.diagnostic(NoBeforeInteractiveScriptOutsideDocumentDiagnostic(
|
||||
strategy.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pass = vec![
|
||||
(
|
||||
r#"import Document, { Html, Main, NextScript } from 'next/document'
|
||||
import Script from 'next/script'
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<Script
|
||||
id="scriptBeforeInteractive"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy="beforeInteractive"
|
||||
></Script>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("pages/_document.js")),
|
||||
),
|
||||
(
|
||||
r#"import Document, { Html, Main, NextScript } from 'next/document'
|
||||
import ScriptComponent from 'next/script'
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<ScriptComponent
|
||||
id="scriptBeforeInteractive"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy="beforeInteractive"
|
||||
></ScriptComponent>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("pages/_document.tsx")),
|
||||
),
|
||||
(
|
||||
r#"import Document, { Html, Main, NextScript } from 'next/document'
|
||||
import ScriptComponent from 'next/script'
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<ScriptComponent
|
||||
id="scriptBeforeInteractive"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
></ScriptComponent>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("pages/_document.tsx")),
|
||||
),
|
||||
(
|
||||
r#"import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("/Users/user_name/projects/project-name/app/layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#"import Script from "next/script";
|
||||
|
||||
export default function test() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("C:\\Users\\username\\projects\\project-name\\app\\layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#"import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("/Users/user_name/projects/project-name/src/app/layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#"import Script from "next/script";
|
||||
|
||||
export default function test() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from(
|
||||
"C:\\Users\\username\\projects\\project-name\\src\\app\\layout.tsx",
|
||||
)),
|
||||
),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
(
|
||||
r#"import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<Script
|
||||
id="scriptBeforeInteractive"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy="beforeInteractive"
|
||||
></Script>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("pages/index.js")),
|
||||
),
|
||||
(
|
||||
r#" import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<Script
|
||||
id="scriptBeforeInteractive"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy="beforeInteractive"
|
||||
></Script>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("components/outside-known-dirs.js")),
|
||||
),
|
||||
(
|
||||
r#" import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("/Users/user_name/projects/project-name/pages/layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#" import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("C:\\Users\\username\\projects\\project-name\\pages\\layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#" import Script from "next/script";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from("/Users/user_name/projects/project-name/src/pages/layout.tsx")),
|
||||
),
|
||||
(
|
||||
r#" import Script from "next/script";
|
||||
|
||||
export default function test() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
strategy='beforeInteractive'
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"#,
|
||||
None,
|
||||
None,
|
||||
Some(PathBuf::from(
|
||||
"C:\\Users\\username\\projects\\project-name\\src\\pages\\layout.tsx",
|
||||
)),
|
||||
),
|
||||
];
|
||||
|
||||
Tester::new(NoBeforeInteractiveScriptOutsideDocument::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ use oxc_diagnostics::{
|
|||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
use crate::{context::LintContext, rule::Rule, utils::is_document_page, 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")]
|
||||
|
|
@ -46,9 +46,8 @@ impl Rule for NoDocumentImportInPage {
|
|||
}
|
||||
|
||||
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") {
|
||||
if is_document_page(path) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +63,7 @@ fn test() {
|
|||
let pass = vec![
|
||||
(
|
||||
r#"import Document from "next/document"
|
||||
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
|
|
@ -80,7 +79,7 @@ fn test() {
|
|||
),
|
||||
(
|
||||
r#"import Document from "next/document"
|
||||
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use oxc_diagnostics::{
|
|||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
use crate::{context::LintContext, rule::Rule, utils::is_in_app_dir, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint-plugin-next(no-head-element): Do not use `<head>` element. Use `<Head />` from `next/head` instead.")]
|
||||
|
|
@ -33,8 +33,7 @@ declare_oxc_lint!(
|
|||
impl Rule for NoHeadElement {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let Some(full_file_path) = ctx.file_path().to_str() else { return };
|
||||
let is_in_app_dir = full_file_path.contains("app/") || full_file_path.contains("app\\");
|
||||
if is_in_app_dir {
|
||||
if is_in_app_dir(full_file_path) {
|
||||
return;
|
||||
}
|
||||
if let AstKind::JSXOpeningElement(elem) = node.kind() {
|
||||
|
|
@ -54,7 +53,7 @@ fn test() {
|
|||
let pass = vec![
|
||||
(
|
||||
r"import Head from 'next/head';
|
||||
|
||||
|
||||
export class MyComponent {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use oxc_semantic::AstNode;
|
|||
use oxc_span::Span;
|
||||
use phf::{phf_set, Set};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule};
|
||||
use crate::{context::LintContext, rule::Rule, utils::get_next_script_import_local_name};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint-plugin-next(no-unwanted-polyfillio): No duplicate polyfills from Polyfill.io are allowed. {0} already shipped with Next.js.")]
|
||||
|
|
@ -117,14 +117,7 @@ impl Rule for NoUnwantedPolyfillio {
|
|||
};
|
||||
|
||||
if tag_name.as_str() != "script" {
|
||||
let next_script_import_local_name =
|
||||
ctx.semantic().module_record().import_entries.iter().find_map(|entry| {
|
||||
if entry.module_request.name().as_str() == "next/script" {
|
||||
Some(entry.local_name.name())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let next_script_import_local_name = get_next_script_import_local_name(ctx);
|
||||
if !matches!(next_script_import_local_name, Some(import) if tag_name.as_str() == import.as_str())
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: no_before_interactive_script_outside_document
|
||||
---
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy="beforeInteractive"
|
||||
· ────────────────────────────
|
||||
10 │ ></Script>
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy="beforeInteractive"
|
||||
· ────────────────────────────
|
||||
10 │ ></Script>
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy='beforeInteractive'
|
||||
· ────────────────────────────
|
||||
10 │ />
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy='beforeInteractive'
|
||||
· ────────────────────────────
|
||||
10 │ />
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy='beforeInteractive'
|
||||
· ────────────────────────────
|
||||
10 │ />
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
⚠ eslint-plugin-next(no-before-interactive-script-outside-document): next/script's `beforeInteractive` strategy should not be used outside of `pages/_document.js`
|
||||
╭─[no_before_interactive_script_outside_document.tsx:8:1]
|
||||
8 │ src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js?a=scriptBeforeInteractive"
|
||||
9 │ strategy='beforeInteractive'
|
||||
· ────────────────────────────
|
||||
10 │ />
|
||||
╰────
|
||||
help: See https://nextjs.org/docs/messages/no-before-interactive-script-outside-document
|
||||
|
||||
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
mod jest;
|
||||
mod nextjs;
|
||||
mod node;
|
||||
mod react;
|
||||
mod react_perf;
|
||||
mod unicorn;
|
||||
|
||||
pub use self::{jest::*, node::*, react::*, react_perf::*, unicorn::*};
|
||||
pub use self::{jest::*, nextjs::*, node::*, react::*, react_perf::*, unicorn::*};
|
||||
|
|
|
|||
22
crates/oxc_linter/src/utils/nextjs.rs
Normal file
22
crates/oxc_linter/src/utils/nextjs.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use oxc_span::Atom;
|
||||
|
||||
use crate::LintContext;
|
||||
|
||||
pub fn is_in_app_dir(file_path: &str) -> bool {
|
||||
file_path.contains("app/") || file_path.contains("app\\")
|
||||
}
|
||||
|
||||
pub fn is_document_page(file_path: &str) -> bool {
|
||||
let Some(page) = file_path.split("pages").last() else { return false };
|
||||
page.starts_with("/_document") || page.starts_with("\\_document")
|
||||
}
|
||||
|
||||
pub fn get_next_script_import_local_name<'a>(ctx: &'a LintContext) -> Option<&'a Atom> {
|
||||
ctx.semantic().module_record().import_entries.iter().find_map(|entry| {
|
||||
if entry.module_request.name().as_str() == "next/script" {
|
||||
Some(entry.local_name.name())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue