@@ -4,6 +4,7 @@ import { ReferenceTracker } from '@eslint-community/eslint-utils';
4
4
import { getSourceCode } from '../utils/compat.js' ;
5
5
import { findVariable } from '../utils/ast-utils.js' ;
6
6
import type { RuleContext } from '../types.js' ;
7
+ import type { SvelteLiteral } from 'svelte-eslint-parser/lib/ast' ;
7
8
8
9
export default createRule ( 'no-navigation-without-base' , {
9
10
meta : {
@@ -25,12 +26,13 @@ export default createRule('no-navigation-without-base', {
25
26
type : 'suggestion'
26
27
} ,
27
28
create ( context ) {
29
+ let basePathNames : Set < TSESTree . Identifier > = new Set < TSESTree . Identifier > ( ) ;
28
30
return {
29
31
Program ( ) {
30
32
const referenceTracker = new ReferenceTracker (
31
33
getSourceCode ( context ) . scopeManager . globalScope !
32
34
) ;
33
- const basePathNames = extractBasePathReferences ( referenceTracker , context ) ;
35
+ basePathNames = extractBasePathReferences ( referenceTracker , context ) ;
34
36
const {
35
37
goto : gotoCalls ,
36
38
pushState : pushStateCalls ,
@@ -50,6 +52,30 @@ export default createRule('no-navigation-without-base', {
50
52
'replaceStateNotPrefixed'
51
53
) ;
52
54
}
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
+ }
53
79
}
54
80
} ;
55
81
}
@@ -206,11 +232,34 @@ function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean {
206
232
) ;
207
233
}
208
234
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 ;
214
247
}
215
248
}
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