@@ -8,14 +8,19 @@ import type { RuleContext } from '../types';
8
8
export default createRule ( 'no-navigation-without-base' , {
9
9
meta : {
10
10
docs : {
11
- description : 'disallow using goto() without the base path' ,
11
+ description :
12
+ 'disallow using navigation (links, goto, pushState, replaceState) without the base path' ,
12
13
category : 'SvelteKit' ,
13
14
recommended : false
14
15
} ,
15
16
schema : [ ] ,
16
17
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."
19
24
} ,
20
25
type : 'suggestion'
21
26
} ,
@@ -27,58 +32,167 @@ export default createRule('no-navigation-without-base', {
27
32
) ;
28
33
const basePathNames = extractBasePathReferences ( referenceTracker , context ) ;
29
34
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
+ ) ;
47
47
}
48
48
}
49
49
} ;
50
50
}
51
51
} ) ;
52
52
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 (
54
94
context : RuleContext ,
55
- path : TSESTree . BinaryExpression ,
95
+ call : TSESTree . CallExpression ,
56
96
basePathNames : Set < TSESTree . Identifier >
57
97
) : 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 ;
60
100
}
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
+ ) ;
61
117
}
62
118
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 (
64
136
context : RuleContext ,
65
- path : TSESTree . TemplateLiteral ,
66
- basePathNames : Set < TSESTree . Identifier >
137
+ call : TSESTree . CallExpression ,
138
+ basePathNames : Set < TSESTree . Identifier > ,
139
+ messageId : string
67
140
) : 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 ;
71
143
}
144
+ const url = call . arguments [ 0 ] ;
145
+ if ( urlIsEmpty ( url ) ) {
146
+ return ;
147
+ }
148
+ console . log ( url ) ;
149
+ checkUrlStartsWithBase ( context , url , basePathNames , messageId ) ;
72
150
}
73
151
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 } ) ;
78
169
}
79
170
}
80
171
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 (
82
196
templateLiteral : TSESTree . TemplateLiteral
83
197
) : TSESTree . Identifier | undefined {
84
198
const literalParts = [ ...templateLiteral . expressions , ...templateLiteral . quasis ] . sort ( ( a , b ) =>
@@ -97,38 +211,21 @@ function extractStartingIdentifier(
97
211
return undefined ;
98
212
}
99
213
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 === '' )
111
221
) ;
112
222
}
113
223
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' });
132
229
}
133
- return set ;
134
230
}
231
+ */
0 commit comments