@@ -4,6 +4,7 @@ import { ReferenceTracker } from '@eslint-community/eslint-utils';
44import { getSourceCode } from '../utils/compat' ;
55import { findVariable } from '../utils/ast-utils' ;
66import type { RuleContext } from '../types' ;
7+ import type { SvelteLiteral } from 'svelte-eslint-parser/lib/ast' ;
78
89export 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