-
-
Notifications
You must be signed in to change notification settings - Fork 339
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): eslint-plugin-next/no-html-link-for-pages
- Loading branch information
Showing
12 changed files
with
229 additions
and
3 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
crates/oxc_linter/fixtures/next/custom-pages/[profile]/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
1 change: 1 addition & 0 deletions
1
crates/oxc_linter/fixtures/next/custom-pages/list/[foo]/[id].jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
1 change: 1 addition & 0 deletions
1
crates/oxc_linter/fixtures/next/with-custom-pages-dir/custom-pages/[profile]/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
1 change: 1 addition & 0 deletions
1
crates/oxc_linter/fixtures/next/with-custom-pages-dir/custom-pages/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
1 change: 1 addition & 0 deletions
1
crates/oxc_linter/fixtures/next/with-custom-pages-dir/custom-pages/list/[foo]/[id].jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default () => {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
crates/oxc_linter/src/rules/nextjs/no_html_link_for_pages.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
use std::path::PathBuf; | ||
|
||
use itertools::Itertools; | ||
use oxc_ast::{ | ||
ast::{JSXAttributeItem, JSXAttributeName, JSXAttributeValue, JSXElementName}, | ||
AstKind, | ||
}; | ||
use oxc_diagnostics::{ | ||
miette::{self, Diagnostic}, | ||
thiserror::{self, 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-html-link-for-pages):")] | ||
#[diagnostic(severity(warning), help(""))] | ||
struct NoHtmlLinkForPagesDiagnostic(#[label] pub Span); | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct NoHtmlLinkForPages(Box<NoHtmlLinkForPagesConfig>); | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct NoHtmlLinkForPagesConfig { | ||
pages_dirs: Option<Vec<String>>, | ||
} | ||
|
||
impl std::ops::Deref for NoHtmlLinkForPages { | ||
type Target = NoHtmlLinkForPagesConfig; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// | ||
/// | ||
/// ### Why is this bad? | ||
/// | ||
/// | ||
/// ### Example | ||
/// ```javascript | ||
/// ``` | ||
NoHtmlLinkForPages, | ||
nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` | ||
// See <https://oxc-project.github.io/docs/contribute/linter.html#rule-category> for details | ||
); | ||
|
||
impl Rule for NoHtmlLinkForPages { | ||
fn from_configuration(value: serde_json::Value) -> Self { | ||
let pages_dirs = value.as_array().map(|dirs| { | ||
dirs.iter().filter_map(|item| item.as_str().map(ToString::to_string)).collect_vec() | ||
}); | ||
Self(Box::new(NoHtmlLinkForPagesConfig { pages_dirs })) | ||
} | ||
fn run_once(&self, ctx: &LintContext) { | ||
let pages_dirs = self.pages_dirs.as_ref().map_or_else( | ||
|| { | ||
ctx.settings() | ||
.next | ||
.get_root_dirs() | ||
.iter() | ||
.flat_map(|item| { | ||
vec![ | ||
PathBuf::from(item).join("pages"), | ||
PathBuf::from(item).join("src/pages"), | ||
] | ||
}) | ||
.collect_vec() | ||
}, | ||
|dirs| dirs.iter().map(PathBuf::from).collect_vec(), | ||
); | ||
|
||
let found_pages_dirs = pages_dirs.iter().filter(|dir| dir.exists()).collect_vec(); | ||
|
||
for node in ctx.nodes().iter() { | ||
let kind = node.kind(); | ||
let AstKind::JSXOpeningElement(element) = kind else { continue }; | ||
if matches!(&element.name, JSXElementName::Identifier(ident) if ident.name != "a") { | ||
continue; | ||
} | ||
let should_ignore = element.attributes.iter().any(|attr| { | ||
let JSXAttributeItem::Attribute(attr) = attr else { | ||
return false; | ||
}; | ||
|
||
let JSXAttributeName::Identifier(ident) = &attr.name else { | ||
return false; | ||
}; | ||
|
||
match ident.name.as_str() { | ||
"target" => { | ||
attr.value.as_ref().map_or(false, |value| { | ||
matches!(&value, JSXAttributeValue::StringLiteral(value) if value.value == "_blank") | ||
}) | ||
}, | ||
"download" => true, | ||
"href" => { | ||
attr.value.as_ref().map_or(false, |value| { | ||
if let JSXAttributeValue::StringLiteral(literal) = value { | ||
// Outgoing links are ignored | ||
literal.value.starts_with("http://") || literal.value.starts_with("https://") || literal.value.starts_with("//") | ||
} else { | ||
true | ||
} | ||
}) | ||
}, | ||
_ => false | ||
} | ||
}); | ||
if should_ignore { | ||
continue; | ||
} | ||
|
||
let Some(href) = element.attributes.iter().find_map(|item| match item { | ||
JSXAttributeItem::Attribute(attribute) => { | ||
if attribute.is_identifier("href") { | ||
attribute.value.as_ref().and_then(|value| { | ||
if let JSXAttributeValue::StringLiteral(literal) = value { | ||
Some(literal) | ||
} else { | ||
None | ||
} | ||
}) | ||
} else { | ||
None | ||
} | ||
} | ||
JSXAttributeItem::SpreadAttribute(_) => None, | ||
}) else { | ||
continue; | ||
}; | ||
} | ||
} | ||
fn run<'a>(&self, _node: &AstNode<'a>, _ctx: &LintContext<'a>) {} | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
use crate::tester::Tester; | ||
use serde_json::json; | ||
use std::env; | ||
use std::path::PathBuf; | ||
|
||
let cwd = env::current_dir().unwrap().join("fixtures/next"); | ||
let with_custom_pages_directory = cwd.join("with-custom-pages-dir"); | ||
let custom_page_dir = with_custom_pages_directory.join("custom-pages"); | ||
let filename = Some(PathBuf::from("foo.jsx")); | ||
|
||
let valid_code = r" | ||
import Link from 'next/link'; | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<Link href='/'> | ||
<a>Homepage</a> | ||
</Link> | ||
<h1>Hello title</h1> | ||
</div> | ||
); | ||
} | ||
} | ||
"; | ||
|
||
let invalid_static_code = r" | ||
import Link from 'next/link'; | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<a href='/'>Homepage</a> | ||
<h1>Hello title</h1> | ||
</div> | ||
); | ||
} | ||
} | ||
"; | ||
|
||
let pass = vec![( | ||
valid_code, | ||
Some(json!([custom_page_dir.to_string_lossy().to_string()])), | ||
None, | ||
filename.clone(), | ||
)]; | ||
|
||
let fail = vec![(invalid_static_code, None, None, filename.clone())]; | ||
|
||
Tester::new(NoHtmlLinkForPages::NAME, pass, fail) | ||
.with_nextjs_plugin(true) | ||
.change_rule_path("with-custom-pages-dir") | ||
.test_and_snapshot(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters