@@ -8,14 +8,19 @@ import type { RuleContext } from '../types';
88export default createRule ( 'no-navigation-without-base' , {
99 meta : {
1010 docs : {
11- description : 'disallow using goto() without the base path' ,
11+ description :
12+ 'disallow using navigation (links, goto, pushState, replaceState) without the base path' ,
1213 category : 'SvelteKit' ,
1314 recommended : false
1415 } ,
1516 schema : [ ] ,
1617 messages : {
17- isNotPrefixedWithBasePath :
18- "Found a goto() call with a url that isn't prefixed with the base path."
18+ gotoNotPrefixed : "Found a goto() call with a url that isn't prefixed with the base path." ,
19+ linkNotPrefixed : "Found a link with a url that isn't prefixed with the base path." ,
20+ pushStateNotPrefixed :
21+ "Found a pushState() call with a url that isn't prefixed with the base path." ,
22+ replaceStateNotPrefixed :
23+ "Found a replaceState() call with a url that isn't prefixed with the base path."
1924 } ,
2025 type : 'suggestion'
2126 } ,
@@ -27,58 +32,167 @@ export default createRule('no-navigation-without-base', {
2732 ) ;
2833 const basePathNames = extractBasePathReferences ( referenceTracker , context ) ;
2934 for ( const gotoCall of extractGotoReferences ( referenceTracker ) ) {
30- if ( gotoCall . arguments . length < 1 ) {
31- continue ;
32- }
33- const path = gotoCall . arguments [ 0 ] ;
34- switch ( path . type ) {
35- case 'BinaryExpression' :
36- checkBinaryExpression ( context , path , basePathNames ) ;
37- break ;
38- case 'Literal' :
39- checkLiteral ( context , path ) ;
40- break ;
41- case 'TemplateLiteral' :
42- checkTemplateLiteral ( context , path , basePathNames ) ;
43- break ;
44- default :
45- context . report ( { loc : path . loc , messageId : 'isNotPrefixedWithBasePath' } ) ;
46- }
35+ checkGotoCall ( context , gotoCall , basePathNames ) ;
36+ }
37+ for ( const pushStateCall of extractPushStateReferences ( referenceTracker ) ) {
38+ checkShallowNavigationCall ( context , pushStateCall , basePathNames , 'pushStateNotPrefixed' ) ;
39+ }
40+ for ( const replaceStateCall of extractReplaceStateReferences ( referenceTracker ) ) {
41+ checkShallowNavigationCall (
42+ context ,
43+ replaceStateCall ,
44+ basePathNames ,
45+ 'replaceStateNotPrefixed'
46+ ) ;
4747 }
4848 }
4949 } ;
5050 }
5151} ) ;
5252
53- function checkBinaryExpression (
53+ // Extract all imports of the base path
54+
55+ function extractBasePathReferences (
56+ referenceTracker : ReferenceTracker ,
57+ context : RuleContext
58+ ) : Set < TSESTree . Identifier > {
59+ const set = new Set < TSESTree . Identifier > ( ) ;
60+ for ( const { node } of referenceTracker . iterateEsmReferences ( {
61+ '$app/paths' : {
62+ [ ReferenceTracker . ESM ] : true ,
63+ base : {
64+ [ ReferenceTracker . READ ] : true
65+ }
66+ }
67+ } ) ) {
68+ const variable = findVariable ( context , ( node as TSESTree . ImportSpecifier ) . local ) ;
69+ if ( ! variable ) continue ;
70+ for ( const reference of variable . references ) {
71+ if ( reference . identifier . type === 'Identifier' ) set . add ( reference . identifier ) ;
72+ }
73+ }
74+ return set ;
75+ }
76+
77+ // Actual function checking
78+
79+ function extractGotoReferences ( referenceTracker : ReferenceTracker ) : TSESTree . CallExpression [ ] {
80+ return Array . from (
81+ referenceTracker . iterateEsmReferences ( {
82+ '$app/navigation' : {
83+ [ ReferenceTracker . ESM ] : true ,
84+ goto : {
85+ [ ReferenceTracker . CALL ] : true
86+ }
87+ }
88+ } ) ,
89+ ( { node } ) => node
90+ ) ;
91+ }
92+
93+ function checkGotoCall (
5494 context : RuleContext ,
55- path : TSESTree . BinaryExpression ,
95+ call : TSESTree . CallExpression ,
5696 basePathNames : Set < TSESTree . Identifier >
5797) : void {
58- if ( path . left . type !== 'Identifier' || ! basePathNames . has ( path . left ) ) {
59- context . report ( { loc : path . loc , messageId : 'isNotPrefixedWithBasePath' } ) ;
98+ if ( call . arguments . length < 1 ) {
99+ return ;
60100 }
101+ const url = call . arguments [ 0 ] ;
102+ checkUrlStartsWithBase ( context , url , basePathNames , 'gotoNotPrefixed' ) ;
103+ }
104+
105+ function extractPushStateReferences ( referenceTracker : ReferenceTracker ) : TSESTree . CallExpression [ ] {
106+ return Array . from (
107+ referenceTracker . iterateEsmReferences ( {
108+ '$app/navigation' : {
109+ [ ReferenceTracker . ESM ] : true ,
110+ pushState : {
111+ [ ReferenceTracker . CALL ] : true
112+ }
113+ }
114+ } ) ,
115+ ( { node } ) => node
116+ ) ;
61117}
62118
63- function checkTemplateLiteral (
119+ function extractReplaceStateReferences (
120+ referenceTracker : ReferenceTracker
121+ ) : TSESTree . CallExpression [ ] {
122+ return Array . from (
123+ referenceTracker . iterateEsmReferences ( {
124+ '$app/navigation' : {
125+ [ ReferenceTracker . ESM ] : true ,
126+ replaceState : {
127+ [ ReferenceTracker . CALL ] : true
128+ }
129+ }
130+ } ) ,
131+ ( { node } ) => node
132+ ) ;
133+ }
134+
135+ function checkShallowNavigationCall (
64136 context : RuleContext ,
65- path : TSESTree . TemplateLiteral ,
66- basePathNames : Set < TSESTree . Identifier >
137+ call : TSESTree . CallExpression ,
138+ basePathNames : Set < TSESTree . Identifier > ,
139+ messageId : string
67140) : void {
68- const startingIdentifier = extractStartingIdentifier ( path ) ;
69- if ( startingIdentifier === undefined || ! basePathNames . has ( startingIdentifier ) ) {
70- context . report ( { loc : path . loc , messageId : 'isNotPrefixedWithBasePath' } ) ;
141+ if ( call . arguments . length < 1 ) {
142+ return ;
71143 }
144+ const url = call . arguments [ 0 ] ;
145+ if ( urlIsEmpty ( url ) ) {
146+ return ;
147+ }
148+ console . log ( url ) ;
149+ checkUrlStartsWithBase ( context , url , basePathNames , messageId ) ;
72150}
73151
74- function checkLiteral ( context : RuleContext , path : TSESTree . Literal ) : void {
75- const absolutePathRegex = / ^ (?: [ + a - z ] + : ) ? \/ \/ / i;
76- if ( ! absolutePathRegex . test ( path . value ?. toString ( ) ?? '' ) ) {
77- context . report ( { loc : path . loc , messageId : 'isNotPrefixedWithBasePath' } ) ;
152+ // Helper functions
153+
154+ function checkUrlStartsWithBase (
155+ context : RuleContext ,
156+ url : TSESTree . CallExpressionArgument ,
157+ basePathNames : Set < TSESTree . Identifier > ,
158+ messageId : string
159+ ) : void {
160+ switch ( url . type ) {
161+ case 'BinaryExpression' :
162+ checkBinaryExpressionStartsWithBase ( context , url , basePathNames , messageId ) ;
163+ break ;
164+ case 'TemplateLiteral' :
165+ checkTemplateLiteralStartsWithBase ( context , url , basePathNames , messageId ) ;
166+ break ;
167+ default :
168+ context . report ( { loc : url . loc , messageId } ) ;
78169 }
79170}
80171
81- function extractStartingIdentifier (
172+ function checkBinaryExpressionStartsWithBase (
173+ context : RuleContext ,
174+ url : TSESTree . BinaryExpression ,
175+ basePathNames : Set < TSESTree . Identifier > ,
176+ messageId : string
177+ ) : void {
178+ if ( url . left . type !== 'Identifier' || ! basePathNames . has ( url . left ) ) {
179+ context . report ( { loc : url . loc , messageId } ) ;
180+ }
181+ }
182+
183+ function checkTemplateLiteralStartsWithBase (
184+ context : RuleContext ,
185+ url : TSESTree . TemplateLiteral ,
186+ basePathNames : Set < TSESTree . Identifier > ,
187+ messageId : string
188+ ) : void {
189+ const startingIdentifier = extractLiteralStartingIdentifier ( url ) ;
190+ if ( startingIdentifier === undefined || ! basePathNames . has ( startingIdentifier ) ) {
191+ context . report ( { loc : url . loc , messageId } ) ;
192+ }
193+ }
194+
195+ function extractLiteralStartingIdentifier (
82196 templateLiteral : TSESTree . TemplateLiteral
83197) : TSESTree . Identifier | undefined {
84198 const literalParts = [ ...templateLiteral . expressions , ...templateLiteral . quasis ] . sort ( ( a , b ) =>
@@ -97,38 +211,21 @@ function extractStartingIdentifier(
97211 return undefined ;
98212}
99213
100- function extractGotoReferences ( referenceTracker : ReferenceTracker ) : TSESTree . CallExpression [ ] {
101- return Array . from (
102- referenceTracker . iterateEsmReferences ( {
103- '$app/navigation' : {
104- [ ReferenceTracker . ESM ] : true ,
105- goto : {
106- [ ReferenceTracker . CALL ] : true
107- }
108- }
109- } ) ,
110- ( { node } ) => node
214+ function urlIsEmpty ( url : TSESTree . CallExpressionArgument ) : boolean {
215+ return (
216+ ( url . type === 'Literal' && url . value === '' ) ||
217+ ( url . type === 'TemplateLiteral' &&
218+ url . expressions . length === 0 &&
219+ url . quasis . length === 1 &&
220+ url . quasis [ 0 ] . value . raw === '' )
111221 ) ;
112222}
113223
114- function extractBasePathReferences (
115- referenceTracker : ReferenceTracker ,
116- context : RuleContext
117- ) : Set < TSESTree . Identifier > {
118- const set = new Set < TSESTree . Identifier > ( ) ;
119- for ( const { node } of referenceTracker . iterateEsmReferences ( {
120- '$app/paths' : {
121- [ ReferenceTracker . ESM ] : true ,
122- base : {
123- [ ReferenceTracker . READ ] : true
124- }
125- }
126- } ) ) {
127- const variable = findVariable ( context , ( node as TSESTree . ImportSpecifier ) . local ) ;
128- if ( ! variable ) continue ;
129- for ( const reference of variable . references ) {
130- if ( reference . identifier . type === 'Identifier' ) set . add ( reference . identifier ) ;
131- }
224+ /*
225+ function checkLiteral(context: RuleContext, url: TSESTree.Literal): void {
226+ const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i;
227+ if (!absolutePathRegex.test(url.value?.toString() ?? '')) {
228+ context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' });
132229 }
133- return set ;
134230}
231+ */
0 commit comments