@@ -19,6 +19,75 @@ const ECMA_VERSION = 2021,
1919 TEST_FILES = [ '**/*.test.js' , '**/*.test.jsx' , '**/*.test.ts' , '**/*.test.tsx' , '**/test/**' , '**/__tests__/**' ] ,
2020 TYPESCRIPT_FILES = [ '**/*.cts' , '**/*.mts' , '**/*.ts' , '**/*.tsx' ] ;
2121
22+ const noNavigateUseClerk = {
23+ meta : {
24+ type : 'problem' ,
25+ docs : {
26+ description : 'Disallow any usage of `navigate` from `useClerk()`' ,
27+ recommended : false ,
28+ } ,
29+ messages : {
30+ noNavigate :
31+ 'Usage of `navigate` from `useClerk()` is not allowed.\nUse `useRouter().navigate` to navigate in-between flows or `setActive({ redirectUrl })`.' ,
32+ } ,
33+ schema : [ ] ,
34+ } ,
35+ create ( context ) {
36+ const sourceCode = context . getSourceCode ( ) ;
37+
38+ return {
39+ // Case 1: Destructuring `navigate` from `useClerk()`
40+ VariableDeclarator ( node ) {
41+ if (
42+ node . id . type === 'ObjectPattern' && // Checks if it's an object destructuring
43+ node . init ?. type === 'CallExpression' &&
44+ node . init . callee . name === 'useClerk'
45+ ) {
46+ for ( const property of node . id . properties ) {
47+ if ( property . type === 'Property' && property . key . name === 'navigate' ) {
48+ context . report ( {
49+ node : property ,
50+ messageId : 'noNavigate' ,
51+ } ) ;
52+ }
53+ }
54+ }
55+ } ,
56+
57+ // Case 2 & 3: Accessing `navigate` on a variable or directly calling `useClerk().navigate`
58+ MemberExpression ( node ) {
59+ if (
60+ node . property . name === 'navigate' &&
61+ node . object . type === 'CallExpression' &&
62+ node . object . callee . name === 'useClerk'
63+ ) {
64+ // Case 3: Direct `useClerk().navigate`
65+ context . report ( {
66+ node,
67+ messageId : 'noNavigate' ,
68+ } ) ;
69+ } else if ( node . property . name === 'navigate' && node . object . type === 'Identifier' ) {
70+ // Case 2: `clerk.navigate` where `clerk` is assigned `useClerk()`
71+ const scope = sourceCode . scopeManager . acquire ( node ) ;
72+ if ( ! scope ) return ;
73+
74+ const variable = scope . variables . find ( v => v . name === node . object . name ) ;
75+
76+ if (
77+ variable ?. defs ?. [ 0 ] ?. node ?. init ?. type === 'CallExpression' &&
78+ variable . defs [ 0 ] . node . init . callee . name === 'useClerk'
79+ ) {
80+ context . report ( {
81+ node,
82+ messageId : 'noNavigate' ,
83+ } ) ;
84+ }
85+ }
86+ } ,
87+ } ;
88+ } ,
89+ } ;
90+
2291export default tseslint . config ( [
2392 {
2493 name : 'repo/ignores' ,
@@ -285,6 +354,20 @@ export default tseslint.config([
285354 'react-hooks/rules-of-hooks' : 'warn' ,
286355 } ,
287356 } ,
357+ {
358+ name : 'packages/clerk-js' ,
359+ files : [ 'packages/clerk-js/src/ui/**/*' ] ,
360+ plugins : {
361+ 'custom-rules' : {
362+ rules : {
363+ 'no-navigate-useClerk' : noNavigateUseClerk ,
364+ } ,
365+ } ,
366+ } ,
367+ rules : {
368+ 'custom-rules/no-navigate-useClerk' : 'error' ,
369+ } ,
370+ } ,
288371 {
289372 name : 'packages/expo-passkeys' ,
290373 files : [ 'packages/expo-passkeys/src/**/*' ] ,
0 commit comments