diff --git a/.changeset/afraid-falcons-hang.md b/.changeset/afraid-falcons-hang.md
new file mode 100644
index 000000000..435f0faab
--- /dev/null
+++ b/.changeset/afraid-falcons-hang.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat(no-navigation-without-resolve): ignoring links with rel=external
diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts
index 9fa001223..7d21b1bf8 100644
--- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts
+++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts
@@ -98,7 +98,8 @@ export default createRule('no-navigation-without-resolve', {
node.parent.parent.name.type !== 'SvelteName' ||
node.parent.parent.name.name !== 'a' ||
node.key.name !== 'href' ||
- node.value.type !== 'Identifier'
+ node.value.type !== 'Identifier' ||
+ hasRelExternal(new FindVariableContext(context), node.parent)
) {
return;
}
@@ -117,7 +118,8 @@ export default createRule('no-navigation-without-resolve', {
node.parent.parent.kind !== 'html' ||
node.parent.parent.name.type !== 'SvelteName' ||
node.parent.parent.name.name !== 'a' ||
- node.key.name !== 'href'
+ node.key.name !== 'href' ||
+ hasRelExternal(new FindVariableContext(context), node.parent)
) {
return;
}
@@ -431,3 +433,38 @@ function templateLiteralIsFragment(
function urlValueIsFragment(url: string): boolean {
return url.startsWith('#');
}
+
+function hasRelExternal(ctx: FindVariableContext, element: AST.SvelteStartTag): boolean {
+ function identifierIsExternal(identifier: TSESTree.Identifier): boolean {
+ const variable = ctx.findVariable(identifier);
+ return (
+ variable !== null &&
+ variable.identifiers.length > 0 &&
+ variable.identifiers[0].parent.type === 'VariableDeclarator' &&
+ variable.identifiers[0].parent.init !== null &&
+ variable.identifiers[0].parent.init.type === 'Literal' &&
+ variable.identifiers[0].parent.init.value === 'external'
+ );
+ }
+
+ for (const attr of element.attributes) {
+ if (
+ (attr.type === 'SvelteAttribute' &&
+ attr.key.name === 'rel' &&
+ ((attr.value[0].type === 'SvelteLiteral' &&
+ attr.value[0].value.split(/\s+/).includes('external')) ||
+ (attr.value[0].type === 'SvelteMustacheTag' &&
+ ((attr.value[0].expression.type === 'Literal' &&
+ attr.value[0].expression.value?.toString().split(/\s+/).includes('external')) ||
+ (attr.value[0].expression.type === 'Identifier' &&
+ identifierIsExternal(attr.value[0].expression)))))) ||
+ (attr.type === 'SvelteShorthandAttribute' &&
+ attr.key.name === 'rel' &&
+ attr.value.type === 'Identifier' &&
+ identifierIsExternal(attr.value))
+ ) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-rel-external01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-rel-external01-input.svelte
new file mode 100644
index 000000000..46e5282a1
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-rel-external01-input.svelte
@@ -0,0 +1,15 @@
+
+
+Click me!
+Click me!
+Click me!
+Click me!
+Click me!
+Click me!
+Click me!
+Click me!