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!