From 6f07541b1ef7c5259d3a68a19050b955604bd45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20=E3=82=A4=E3=83=BC=E3=83=99=E3=82=B9=20Eves?= Date: Fri, 26 Sep 2025 23:06:42 +0100 Subject: [PATCH] fix(no-navigation-without-resolve): do not report relative upper directory urls in `` refactor(no-navigation-without-resolve): combine `expressionIsAbsolute()` and `expressionIsFragment()` into `expressionIsIgnored()` traversing once to reduce duplication --- .../rules/no-navigation-without-resolve.ts | 93 +++++-------------- .../valid/link-relative-url01-input.svelte | 9 ++ 2 files changed, 34 insertions(+), 68 deletions(-) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-relative-url01-input.svelte 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..0995030cd 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,11 +97,9 @@ export default createRule('no-navigation-without-resolve', { } if ( (node.value[0].type === 'SvelteLiteral' && - !expressionIsAbsolute(new FindVariableContext(context), node.value[0]) && - !expressionIsFragment(new FindVariableContext(context), node.value[0])) || + !expressionIsIgnored(new FindVariableContext(context), node.value[0])) || (node.value[0].type === 'SvelteMustacheTag' && - !expressionIsAbsolute(new FindVariableContext(context), node.value[0].expression) && - !expressionIsFragment(new FindVariableContext(context), node.value[0].expression) && + !expressionIsIgnored(new FindVariableContext(context), node.value[0].expression) && !isResolveCall( new FindVariableContext(context), node.value[0].expression, @@ -263,37 +261,37 @@ function expressionIsEmpty(url: TSESTree.CallExpressionArgument): boolean { ); } -function expressionIsAbsolute( +function expressionIsIgnored( ctx: FindVariableContext, url: AST.SvelteLiteral | TSESTree.Expression ): boolean { switch (url.type) { case 'BinaryExpression': - return binaryExpressionIsAbsolute(ctx, url); + return binaryExpressionIsIgnored(ctx, url); case 'Identifier': - return identifierIsAbsolute(ctx, url); + return identifierIsIgnored(ctx, url); case 'Literal': - return typeof url.value === 'string' && urlValueIsAbsolute(url.value); + return typeof url.value === 'string' && urlValueIsIgnored(url.value); case 'SvelteLiteral': - return urlValueIsAbsolute(url.value); + return urlValueIsIgnored(url.value); case 'TemplateLiteral': - return templateLiteralIsAbsolute(ctx, url); + return templateLiteralIsIgnored(ctx, url); default: return false; } } -function binaryExpressionIsAbsolute( +function binaryExpressionIsIgnored( ctx: FindVariableContext, url: TSESTree.BinaryExpression ): boolean { return ( - (url.left.type !== 'PrivateIdentifier' && expressionIsAbsolute(ctx, url.left)) || - expressionIsAbsolute(ctx, url.right) + (url.left.type !== 'PrivateIdentifier' && expressionIsIgnored(ctx, url.left)) || + expressionIsIgnored(ctx, url.right) ); } -function identifierIsAbsolute(ctx: FindVariableContext, url: TSESTree.Identifier): boolean { +function identifierIsIgnored(ctx: FindVariableContext, url: TSESTree.Identifier): boolean { const variable = ctx.findVariable(url); if ( variable === null || @@ -303,73 +301,32 @@ function identifierIsAbsolute(ctx: FindVariableContext, url: TSESTree.Identifier ) { return false; } - return expressionIsAbsolute(ctx, variable.identifiers[0].parent.init); + + return expressionIsIgnored(ctx, variable.identifiers[0].parent.init); } -function templateLiteralIsAbsolute( +function templateLiteralIsIgnored( ctx: FindVariableContext, url: TSESTree.TemplateLiteral ): boolean { return ( - url.expressions.some((expression) => expressionIsAbsolute(ctx, expression)) || - url.quasis.some((quasi) => urlValueIsAbsolute(quasi.value.raw)) + url.expressions.some((expression) => expressionIsIgnored(ctx, expression)) || + url.quasis.some((quasi) => urlValueIsIgnored(quasi.value.raw)) ); } -function urlValueIsAbsolute(url: string): boolean { - return /^[+a-z]*:/i.test(url); -} - -function expressionIsFragment( - ctx: FindVariableContext, - url: AST.SvelteLiteral | TSESTree.Expression -): boolean { - switch (url.type) { - case 'BinaryExpression': - return binaryExpressionIsFragment(ctx, url); - case 'Identifier': - return identifierIsFragment(ctx, url); - case 'Literal': - return typeof url.value === 'string' && urlValueIsFragment(url.value); - case 'SvelteLiteral': - return urlValueIsFragment(url.value); - case 'TemplateLiteral': - return templateLiteralIsFragment(ctx, url); - default: - return false; - } -} - -function binaryExpressionIsFragment( - ctx: FindVariableContext, - url: TSESTree.BinaryExpression -): boolean { - return url.left.type !== 'PrivateIdentifier' && expressionIsFragment(ctx, url.left); -} - -function identifierIsFragment(ctx: FindVariableContext, url: TSESTree.Identifier): boolean { - 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 expressionIsFragment(ctx, variable.identifiers[0].parent.init); +function urlValueIsIgnored(url: string): boolean { + return urlValueIsAbsolute(url) || urlValueIsFragment(url) || urlValueIsUpperDirectory(url); } -function templateLiteralIsFragment( - ctx: FindVariableContext, - url: TSESTree.TemplateLiteral -): boolean { - return ( - (url.expressions.length >= 1 && expressionIsFragment(ctx, url.expressions[0])) || - (url.quasis.length >= 1 && urlValueIsFragment(url.quasis[0].value.raw)) - ); +function urlValueIsAbsolute(url: string): boolean { + return /^[+a-z]*:/i.test(url); } function urlValueIsFragment(url: string): boolean { return url.startsWith('#'); } + +function urlValueIsUpperDirectory(url: string): boolean { + return url.startsWith('..'); +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-relative-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-relative-url01-input.svelte new file mode 100644 index 000000000..532cdf16f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-relative-url01-input.svelte @@ -0,0 +1,9 @@ + + +Click me! +Click me! +Click me! +Click me! +Click me!