From 7577d1dcfc3554079524021076b07926adc954c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sun, 5 Oct 2025 15:28:53 +0200 Subject: [PATCH] fix(no-navigation-without-resolve): allowing undefined and null in link hrefs --- .changeset/public-groups-prove.md | 5 +++ .../rules/no-navigation-without-resolve.ts | 41 +++++++++++++++++++ .../link-nullish-like-literal01-errors.yaml | 24 +++++++++++ .../link-nullish-like-literal01-input.svelte | 11 +++++ .../valid/link-nullish01-input.svelte | 13 ++++++ 5 files changed, 94 insertions(+) create mode 100644 .changeset/public-groups-prove.md create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-nullish01-input.svelte diff --git a/.changeset/public-groups-prove.md b/.changeset/public-groups-prove.md new file mode 100644 index 000000000..0a744470e --- /dev/null +++ b/.changeset/public-groups-prove.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': patch +--- + +fix(no-navigation-without-resolve): allowing undefined and null in link hrefs 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 06ad91df8..fdf808982 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 @@ -97,9 +97,11 @@ export default createRule('no-navigation-without-resolve', { } if ( (node.value[0].type === 'SvelteLiteral' && + !expressionIsNullish(new FindVariableContext(context), node.value[0]) && !expressionIsAbsolute(new FindVariableContext(context), node.value[0]) && !expressionIsFragment(new FindVariableContext(context), node.value[0])) || (node.value[0].type === 'SvelteMustacheTag' && + !expressionIsNullish(new FindVariableContext(context), node.value[0].expression) && !expressionIsAbsolute(new FindVariableContext(context), node.value[0].expression) && !expressionIsFragment(new FindVariableContext(context), node.value[0].expression) && !isResolveCall( @@ -263,6 +265,45 @@ function expressionIsEmpty(url: TSESTree.CallExpressionArgument): boolean { ); } +function expressionIsNullish( + ctx: FindVariableContext, + url: AST.SvelteLiteral | TSESTree.Expression +): boolean { + switch (url.type) { + case 'Identifier': + return identifierIsNullish(ctx, url); + case 'Literal': + return url.value === null; // Undefined is an Identifier in ESTree, null is a Literal + case 'TemplateLiteral': + return templateLiteralIsNullish(ctx, url); + default: + return false; + } +} + +function identifierIsNullish(ctx: FindVariableContext, url: TSESTree.Identifier): boolean { + if (url.name === 'undefined') { + return true; + } + const variable = ctx.findVariable(url); + if ( + variable === null || + variable.identifiers.length === 0 || + variable.identifiers[0].parent.type !== 'VariableDeclarator' || + variable.identifiers[0].parent.init === null + ) { + return false; + } + return expressionIsNullish(ctx, variable.identifiers[0].parent.init); +} + +function templateLiteralIsNullish( + ctx: FindVariableContext, + url: TSESTree.TemplateLiteral +): boolean { + return url.expressions.length === 1 && expressionIsNullish(ctx, url.expressions[0]); +} + function expressionIsAbsolute( ctx: FindVariableContext, url: AST.SvelteLiteral | TSESTree.Expression diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-errors.yaml new file mode 100644 index 000000000..716ba9583 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-errors.yaml @@ -0,0 +1,24 @@ +- message: Unexpected href link without resolve(). + line: 6 + column: 10 + suggestions: null +- message: Unexpected href link without resolve(). + line: 7 + column: 10 + suggestions: null +- message: Unexpected href link without resolve(). + line: 8 + column: 9 + suggestions: null +- message: Unexpected href link without resolve(). + line: 9 + column: 9 + suggestions: null +- message: Unexpected href link without resolve(). + line: 10 + column: 9 + suggestions: null +- message: Unexpected href link without resolve(). + line: 11 + column: 9 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-input.svelte new file mode 100644 index 000000000..1986f1f38 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-nullish-like-literal01-input.svelte @@ -0,0 +1,11 @@ + + +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-nullish01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-nullish01-input.svelte new file mode 100644 index 000000000..87756feb0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-nullish01-input.svelte @@ -0,0 +1,13 @@ + + +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! +Click me!