Skip to content

Commit 67e9884

Browse files
committed
feat(no-navigation-without-base): added support for links
1 parent df96f99 commit 67e9884

File tree

1 file changed

+56
-7
lines changed

1 file changed

+56
-7
lines changed

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

+56-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ReferenceTracker } from '@eslint-community/eslint-utils';
44
import { getSourceCode } from '../utils/compat.js';
55
import { findVariable } from '../utils/ast-utils.js';
66
import type { RuleContext } from '../types.js';
7+
import type { SvelteLiteral } from 'svelte-eslint-parser/lib/ast';
78

89
export default createRule('no-navigation-without-base', {
910
meta: {
@@ -25,12 +26,13 @@ export default createRule('no-navigation-without-base', {
2526
type: 'suggestion'
2627
},
2728
create(context) {
29+
let basePathNames: Set<TSESTree.Identifier> = new Set<TSESTree.Identifier>();
2830
return {
2931
Program() {
3032
const referenceTracker = new ReferenceTracker(
3133
getSourceCode(context).scopeManager.globalScope!
3234
);
33-
const basePathNames = extractBasePathReferences(referenceTracker, context);
35+
basePathNames = extractBasePathReferences(referenceTracker, context);
3436
const {
3537
goto: gotoCalls,
3638
pushState: pushStateCalls,
@@ -50,6 +52,30 @@ export default createRule('no-navigation-without-base', {
5052
'replaceStateNotPrefixed'
5153
);
5254
}
55+
},
56+
SvelteAttribute(node) {
57+
if (
58+
node.parent.parent.type !== 'SvelteElement' ||
59+
node.parent.parent.kind !== 'html' ||
60+
node.parent.parent.name.type !== 'SvelteName' ||
61+
node.parent.parent.name.name !== 'a' ||
62+
node.key.name !== 'href'
63+
) {
64+
return;
65+
}
66+
const hrefValue = node.value[0];
67+
if (hrefValue.type === 'SvelteLiteral') {
68+
if (!urlIsAbsolute(hrefValue)) {
69+
context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' });
70+
}
71+
return;
72+
}
73+
if (
74+
!urlStartsWithBase(hrefValue.expression, basePathNames) &&
75+
!urlIsAbsolute(hrefValue.expression)
76+
) {
77+
context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' });
78+
}
5379
}
5480
};
5581
}
@@ -206,11 +232,34 @@ function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean {
206232
);
207233
}
208234

209-
/*
210-
function checkLiteral(context: RuleContext, url: TSESTree.Literal): void {
211-
const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i;
212-
if (!absolutePathRegex.test(url.value?.toString() ?? '')) {
213-
context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' });
235+
function urlIsAbsolute(url: SvelteLiteral | TSESTree.Expression): boolean {
236+
switch (url.type) {
237+
case 'BinaryExpression':
238+
return binaryExpressionIsAbsolute(url);
239+
case 'Literal':
240+
return typeof url.value === 'string' && urlValueIsAbsolute(url.value);
241+
case 'SvelteLiteral':
242+
return urlValueIsAbsolute(url.value);
243+
case 'TemplateLiteral':
244+
return templateLiteralIsAbsolute(url);
245+
default:
246+
return false;
214247
}
215248
}
216-
*/
249+
250+
function binaryExpressionIsAbsolute(url: TSESTree.BinaryExpression): boolean {
251+
return (
252+
(url.left.type !== 'PrivateIdentifier' && urlIsAbsolute(url.left)) || urlIsAbsolute(url.right)
253+
);
254+
}
255+
256+
function templateLiteralIsAbsolute(url: TSESTree.TemplateLiteral): boolean {
257+
return (
258+
url.expressions.some(urlIsAbsolute) ||
259+
url.quasis.some((quasi) => urlValueIsAbsolute(quasi.value.raw))
260+
);
261+
}
262+
263+
function urlValueIsAbsolute(url: string): boolean {
264+
return url.includes('://');
265+
}

0 commit comments

Comments
 (0)