@@ -156,8 +156,8 @@ def _route_links(q, sf, lits, tf):
156156 mine = [(s , l ) for s , _t , l in args if s ]
157157 own = [(l , i , v ) for l , i , v in own if (v , l ) in mine ][:1 ] or [(l , - 1 , s ) for s , l in mine [:1 ]]
158158 if handlers is None :
159- handlers = { n for ( n ,) in q ( "SELECT DISTINCT name FROM symbols WHERE name IS NOT NULL" )} if _has ( q , 'symbols' ) else set ( )
160- if short .lower () in HTTP_VERB and not any (_hands_over (t , handlers ) for s , t , _l in args if not s ):
159+ handlers = _handler_names ( q )
160+ if short .lower () in HTTP_VERB and not any (_hands_over (t , handlers , f , read ( f ) ) for s , t , _l in args if not s ):
161161 prev_end , prev_path = b , None
162162 continue # a request (a client, a Map, Headers): nothing is registered here
163163 if own :
@@ -257,23 +257,60 @@ def _own_args(L, a, ac, b, bc):
257257 for lo , hi in _split_args (blank , j + 1 , close ):
258258 t = text [lo :hi ].strip ()
259259 # the path argument: a string beginning with `/` written in it outside any bracket — `'/items'`, and the
260- # prefixed `path + '/:id'` a resource helper composes — never one inside a nested call's own arguments
260+ # prefixed `path + '/:id'` a resource helper composes — never one inside a nested call's own arguments. A route
261+ # written as one options object (`fastify.route({ method: 'DELETE', url: '/items/:id', handler })`) holds its
262+ # path as a property value of that object, one bracket in
261263 path , at , d = None , lo + len (text [lo :hi ]) - len (text [lo :hi ].lstrip ()), 0
264+ obj = t .startswith ('{' )
262265 for k in range (lo , hi ):
263266 c = blank [k ]
264267 if c in _OPEN : d += 1
265268 elif c in _CLOSE : d -= 1
266- elif c in '\' "`' and d == 0 :
267- m = re .match (r'([\'"`])(/[^\'"`\n]*)\1' , text [k :hi ])
268- if m : path , at = m .group (2 ), k
269- break
269+ elif c in '\' "`' and (d == 0 or (obj and d == 1 and blank [:k ].rstrip ().endswith (':' ))):
270+ # a template may put a base URL first: `${env.API_URL}/auth/register` registers `/auth/register`
271+ m = re .match (r'([\'"`])(/[^\'"`\n]*)\1|`(?:\$\{[^}`]*\})+(/[^`\n]*)`' , text [k :hi ])
272+ if m : path , at = m .group (2 ) or m .group (3 ), k
273+ if m or d == 0 : break
270274 out .append ((path , t , a + text .count ('\n ' , 0 , at )))
271275 return out
272276
273277
274- def _hands_over (t , names ):
275- """the argument text can be something a router calls: a function, a name the graph declares, a call (a wrapper or a
276- middleware factory), an array of those. A string, a number, an object, `new X()` or a name nothing declares (a
278+ _NOT_CALLABLE = {'field' , 'const' , 'var' , 'variable' , 'param' , 'parameter' , 'typeparam' , 'module' , 'type' , 'enum' ,
279+ 'enum_member' , 'property' , 'local' }
280+
281+
282+ def _handler_names (q ):
283+ """name -> {kind: {file}} of every declaration, for telling a handler handed over by name from the request's data"""
284+ names = {}
285+ if _has (q , 'symbols' ):
286+ for n , k , f in q ("SELECT name, kind, file FROM symbols WHERE name IS NOT NULL" ):
287+ names .setdefault (n , {}).setdefault (k or '' , set ()).add (f )
288+ return names
289+
290+
291+ def _names_handler (t , names , f , L ):
292+ """a written name `h` or `a.b.h` is a handler: `h` a function or method; a member of a module this file imports
293+ (`users.signup`, `exports.signup = …` declares nothing); `this.h` a property of the class; or a const (a wrapped
294+ handler, `const h = catchAsync(…)`) this file declares or imports. The same name as a parameter, a local, a field
295+ of a type or some other module's `data` (`api.post('/discussions', data)`) is the request's data."""
296+ parts = re .split (r'\s*\??\.\s*' , t )
297+ kinds = names .get (parts [- 1 ], {})
298+ if any (k not in _NOT_CALLABLE for k in kinds ):
299+ return True
300+ here = parts [0 ]
301+ if len (parts ) > 1 and here == 'this' :
302+ return any (f in fs for k , fs in kinds .items () if k in ('const' , 'field' , 'property' ))
303+ n = re .escape (here )
304+ imported = bool (re .search (r'\bimport\b[^;]*?\b%s\b[^;]*?\bfrom\b|\b%s\b[^=;\n]*=\s*require\s*\(' % (n , n ),
305+ '\n ' .join (L or ())))
306+ if len (parts ) > 1 :
307+ return imported
308+ return f in kinds .get ('const' , ()) or (imported and 'const' in kinds )
309+
310+
311+ def _hands_over (t , names , f = None , L = None ):
312+ """the argument text can be something a router calls: a function, a name declared as a handler, a call (a wrapper or
313+ a middleware factory), an array of those. A string, a number, an object, `new X()` or a name that is not one (a
277314 parameter, a local: the request's data) is not."""
278315 t = t .strip ()
279316 if not t or t [0 ] in '\' "`{' or t [0 ].isdigit () or re .match (r'(new|true|false|null|undefined)\b' , t ):
@@ -282,9 +319,9 @@ def _hands_over(t, names):
282319 return True
283320 if t .startswith ('[' ) and t .endswith (']' ):
284321 b = _blank (t )
285- return any (_hands_over (t [lo :hi ], names ) for lo , hi in _split_args (b , 1 , len (b ) - 1 ))
322+ return any (_hands_over (t [lo :hi ], names , f , L ) for lo , hi in _split_args (b , 1 , len (b ) - 1 ))
286323 if _CHAIN .fullmatch (t ):
287- return re . split ( r'\s*\??\.\s*' , t )[ - 1 ] in names
324+ return _names_handler ( t , names , f , L )
288325 return True # a call, a conditional, an `await`: not read further, and kept
289326
290327
0 commit comments