diff --git a/.changeset/icy-planets-know.md b/.changeset/icy-planets-know.md new file mode 100644 index 000000000..d32303d87 --- /dev/null +++ b/.changeset/icy-planets-know.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat(no-navigation-without-resolve): checking link shorthand attributes 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..24aa56085 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 @@ -49,6 +49,12 @@ export default createRule('no-navigation-without-resolve', { }, create(context) { let resolveReferences: Set = new Set(); + + const ignoreGoto = context.options[0]?.ignoreGoto ?? false; + const ignorePushState = context.options[0]?.ignorePushState ?? false; + const ignoreReplaceState = context.options[0]?.ignoreReplaceState ?? false; + const ignoreLinks = context.options[0]?.ignoreLinks ?? false; + return { Program() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!); @@ -58,12 +64,12 @@ export default createRule('no-navigation-without-resolve', { pushState: pushStateCalls, replaceState: replaceStateCalls } = extractFunctionCallReferences(referenceTracker); - if (context.options[0]?.ignoreGoto !== true) { + if (!ignoreGoto) { for (const gotoCall of gotoCalls) { checkGotoCall(context, gotoCall, resolveReferences); } } - if (context.options[0]?.ignorePushState !== true) { + if (!ignorePushState) { for (const pushStateCall of pushStateCalls) { checkShallowNavigationCall( context, @@ -73,7 +79,7 @@ export default createRule('no-navigation-without-resolve', { ); } } - if (context.options[0]?.ignoreReplaceState !== true) { + if (!ignoreReplaceState) { for (const replaceStateCall of replaceStateCalls) { checkShallowNavigationCall( context, @@ -84,9 +90,29 @@ export default createRule('no-navigation-without-resolve', { } } }, + SvelteShorthandAttribute(node) { + if ( + ignoreLinks || + node.parent.parent.type !== 'SvelteElement' || + node.parent.parent.kind !== 'html' || + node.parent.parent.name.type !== 'SvelteName' || + node.parent.parent.name.name !== 'a' || + node.key.name !== 'href' || + node.value.type !== 'Identifier' + ) { + return; + } + if ( + !expressionIsAbsolute(new FindVariableContext(context), node.value) && + !expressionIsFragment(new FindVariableContext(context), node.value) && + !isResolveCall(new FindVariableContext(context), node.value, resolveReferences) + ) { + context.report({ loc: node.loc, messageId: 'linkWithoutResolve' }); + } + }, SvelteAttribute(node) { if ( - context.options[0]?.ignoreLinks === true || + ignoreLinks || node.parent.parent.type !== 'SvelteElement' || node.parent.parent.kind !== 'html' || node.parent.parent.name.type !== 'SvelteName' || diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-errors.yaml index 98768b3c2..ad7407ad2 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-errors.yaml @@ -1,12 +1,16 @@ - message: Unexpected href link without resolve(). - line: 7 + line: 8 column: 9 suggestions: null - message: Unexpected href link without resolve(). - line: 8 + line: 9 column: 9 suggestions: null - message: Unexpected href link without resolve(). - line: 9 + line: 10 column: 9 suggestions: null +- message: Unexpected href link without resolve(). + line: 11 + column: 4 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-input.svelte index 6994a2cb1..661e8c14e 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-input.svelte @@ -2,8 +2,10 @@ import { resolve } from '$app/paths'; const value = resolve('/foo') + '/bar'; + const href = resolve('/foo') + '/bar'; Click me! Click me! Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-errors.yaml index 52e9ee73e..a714ee1d9 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-errors.yaml @@ -1,10 +1,6 @@ -- message: Unexpected href link without resolve(). - line: 5 - column: 10 - suggestions: null - message: Unexpected href link without resolve(). line: 6 - column: 9 + column: 10 suggestions: null - message: Unexpected href link without resolve(). line: 7 @@ -18,3 +14,11 @@ line: 9 column: 9 suggestions: null +- message: Unexpected href link without resolve(). + line: 10 + column: 4 + 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-with-fragment01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-input.svelte index 2a2d1d418..8e955d59a 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-input.svelte @@ -1,9 +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/invalid/link-without-resolve01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-errors.yaml index 52e9ee73e..a714ee1d9 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-errors.yaml @@ -1,10 +1,6 @@ -- message: Unexpected href link without resolve(). - line: 5 - column: 10 - suggestions: null - message: Unexpected href link without resolve(). line: 6 - column: 9 + column: 10 suggestions: null - message: Unexpected href link without resolve(). line: 7 @@ -18,3 +14,11 @@ line: 9 column: 9 suggestions: null +- message: Unexpected href link without resolve(). + line: 10 + column: 4 + 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-without-resolve01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-input.svelte index 4ae6bc7ec..2dab745ff 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-input.svelte @@ -1,9 +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-absolute-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-absolute-url01-input.svelte index 4b57ff9ba..cbc35c4a0 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-absolute-url01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-absolute-url01-input.svelte @@ -2,6 +2,7 @@ const protocol = 'https'; const value = "https://svelte.dev"; + const href = "https://svelte.dev"; Click me! @@ -16,3 +17,4 @@ Click me! Click me! Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-fragment-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-fragment-url01-input.svelte index cf56203a5..2e35b7630 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-fragment-url01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-fragment-url01-input.svelte @@ -2,6 +2,7 @@ const section = 'sectionName'; const value = '#section'; + const href = '#section'; Click me! @@ -12,3 +13,4 @@ Click me! Click me! Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-resolved01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-resolved01-input.svelte index 2561d7490..0ea7fca3a 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-resolved01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-resolved01-input.svelte @@ -2,7 +2,9 @@ import { resolve } from '$app/paths'; const value = resolve('/foo/'); + const href = resolve('/foo/'); Click me! Click me! +Click me!