Skip to content

Commit 074af20

Browse files
authored
feat(no-navigation-without-resolve): checking link shorthand attributes (#1323)
1 parent 09a4eae commit 074af20

11 files changed

+72
-17
lines changed

.changeset/icy-planets-know.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat(no-navigation-without-resolve): checking link shorthand attributes

packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export default createRule('no-navigation-without-resolve', {
4949
},
5050
create(context) {
5151
let resolveReferences: Set<TSESTree.Identifier> = new Set<TSESTree.Identifier>();
52+
53+
const ignoreGoto = context.options[0]?.ignoreGoto ?? false;
54+
const ignorePushState = context.options[0]?.ignorePushState ?? false;
55+
const ignoreReplaceState = context.options[0]?.ignoreReplaceState ?? false;
56+
const ignoreLinks = context.options[0]?.ignoreLinks ?? false;
57+
5258
return {
5359
Program() {
5460
const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!);
@@ -58,12 +64,12 @@ export default createRule('no-navigation-without-resolve', {
5864
pushState: pushStateCalls,
5965
replaceState: replaceStateCalls
6066
} = extractFunctionCallReferences(referenceTracker);
61-
if (context.options[0]?.ignoreGoto !== true) {
67+
if (!ignoreGoto) {
6268
for (const gotoCall of gotoCalls) {
6369
checkGotoCall(context, gotoCall, resolveReferences);
6470
}
6571
}
66-
if (context.options[0]?.ignorePushState !== true) {
72+
if (!ignorePushState) {
6773
for (const pushStateCall of pushStateCalls) {
6874
checkShallowNavigationCall(
6975
context,
@@ -73,7 +79,7 @@ export default createRule('no-navigation-without-resolve', {
7379
);
7480
}
7581
}
76-
if (context.options[0]?.ignoreReplaceState !== true) {
82+
if (!ignoreReplaceState) {
7783
for (const replaceStateCall of replaceStateCalls) {
7884
checkShallowNavigationCall(
7985
context,
@@ -84,9 +90,29 @@ export default createRule('no-navigation-without-resolve', {
8490
}
8591
}
8692
},
93+
SvelteShorthandAttribute(node) {
94+
if (
95+
ignoreLinks ||
96+
node.parent.parent.type !== 'SvelteElement' ||
97+
node.parent.parent.kind !== 'html' ||
98+
node.parent.parent.name.type !== 'SvelteName' ||
99+
node.parent.parent.name.name !== 'a' ||
100+
node.key.name !== 'href' ||
101+
node.value.type !== 'Identifier'
102+
) {
103+
return;
104+
}
105+
if (
106+
!expressionIsAbsolute(new FindVariableContext(context), node.value) &&
107+
!expressionIsFragment(new FindVariableContext(context), node.value) &&
108+
!isResolveCall(new FindVariableContext(context), node.value, resolveReferences)
109+
) {
110+
context.report({ loc: node.loc, messageId: 'linkWithoutResolve' });
111+
}
112+
},
87113
SvelteAttribute(node) {
88114
if (
89-
context.options[0]?.ignoreLinks === true ||
115+
ignoreLinks ||
90116
node.parent.parent.type !== 'SvelteElement' ||
91117
node.parent.parent.kind !== 'html' ||
92118
node.parent.parent.name.type !== 'SvelteName' ||
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
- message: Unexpected href link without resolve().
2-
line: 7
2+
line: 8
33
column: 9
44
suggestions: null
55
- message: Unexpected href link without resolve().
6-
line: 8
6+
line: 9
77
column: 9
88
suggestions: null
99
- message: Unexpected href link without resolve().
10-
line: 9
10+
line: 10
1111
column: 9
1212
suggestions: null
13+
- message: Unexpected href link without resolve().
14+
line: 11
15+
column: 4
16+
suggestions: null

packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-partial-resolve01-input.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
import { resolve } from '$app/paths';
33
44
const value = resolve('/foo') + '/bar';
5+
const href = resolve('/foo') + '/bar';
56
</script>
67

78
<a href={resolve('/foo') + '/bar'}>Click me!</a>
89
<a href={'/foo' + resolve('/bar')}>Click me!</a>
910
<a href={value}>Click me!</a>
11+
<a {href}>Click me!</a>

packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-with-fragment01-errors.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
- message: Unexpected href link without resolve().
2-
line: 5
3-
column: 10
4-
suggestions: null
51
- message: Unexpected href link without resolve().
62
line: 6
7-
column: 9
3+
column: 10
84
suggestions: null
95
- message: Unexpected href link without resolve().
106
line: 7
@@ -18,3 +14,11 @@
1814
line: 9
1915
column: 9
2016
suggestions: null
17+
- message: Unexpected href link without resolve().
18+
line: 10
19+
column: 4
20+
suggestions: null
21+
- message: Unexpected href link without resolve().
22+
line: 11
23+
column: 9
24+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script>
22
const value = "/foo#section";
3+
const href = "/foo#section";
34
</script>
45

56
<a href="/foo#section">Click me!</a>
67
<a href={'/foo#section'}>Click me!</a>
78
<a href={'/' + 'foo#section'}>Click me!</a>
89
<a href={value}>Click me!</a>
10+
<a {href}>Click me!</a>
911
<a href={'/foo#section:42'}>Click me!</a>

packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/link-without-resolve01-errors.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
- message: Unexpected href link without resolve().
2-
line: 5
3-
column: 10
4-
suggestions: null
51
- message: Unexpected href link without resolve().
62
line: 6
7-
column: 9
3+
column: 10
84
suggestions: null
95
- message: Unexpected href link without resolve().
106
line: 7
@@ -18,3 +14,11 @@
1814
line: 9
1915
column: 9
2016
suggestions: null
17+
- message: Unexpected href link without resolve().
18+
line: 10
19+
column: 4
20+
suggestions: null
21+
- message: Unexpected href link without resolve().
22+
line: 11
23+
column: 9
24+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script>
22
const value = "/foo";
3+
const href = "/foo";
34
</script>
45

56
<a href="/foo">Click me!</a>
67
<a href={'/foo'}>Click me!</a>
78
<a href={'/' + 'foo'}>Click me!</a>
89
<a href={value}>Click me!</a>
10+
<a {href}>Click me!</a>
911
<a href={'/user:42'}>Click me!</a>

packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-absolute-url01-input.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const protocol = 'https';
33
44
const value = "https://svelte.dev";
5+
const href = "https://svelte.dev";
56
</script>
67

78
<a href="http://svelte.dev">Click me!</a>
@@ -16,3 +17,4 @@
1617
<a href="mailto:[email protected]">Click me!</a>
1718
<a href="tel:+123456789">Click me!</a>
1819
<a href={value}>Click me!</a>
20+
<a {href}>Click me!</a>

packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/valid/link-fragment-url01-input.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const section = 'sectionName';
33
44
const value = '#section';
5+
const href = '#section';
56
</script>
67

78
<a href="#">Click me!</a>
@@ -12,3 +13,4 @@
1213
<a href={`#${section}`}>Click me!</a>
1314
<a href={'#user:42'}>Click me!</a>
1415
<a href={value}>Click me!</a>
16+
<a {href}>Click me!</a>

0 commit comments

Comments
 (0)