mnehmos.trace.mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1662 -0
- package/dist/adapters/bootstrap.d.ts +29 -0
- package/dist/adapters/bootstrap.d.ts.map +1 -0
- package/dist/adapters/bootstrap.js +46 -0
- package/dist/adapters/bootstrap.js.map +1 -0
- package/dist/adapters/errors.d.ts +94 -0
- package/dist/adapters/errors.d.ts.map +1 -0
- package/dist/adapters/errors.js +107 -0
- package/dist/adapters/errors.js.map +1 -0
- package/dist/adapters/graphql/index.d.ts +9 -0
- package/dist/adapters/graphql/index.d.ts.map +1 -0
- package/dist/adapters/graphql/index.js +9 -0
- package/dist/adapters/graphql/index.js.map +1 -0
- package/dist/adapters/graphql/sdl-parser.d.ts +74 -0
- package/dist/adapters/graphql/sdl-parser.d.ts.map +1 -0
- package/dist/adapters/graphql/sdl-parser.js +559 -0
- package/dist/adapters/graphql/sdl-parser.js.map +1 -0
- package/dist/adapters/grpc/adapter.d.ts +76 -0
- package/dist/adapters/grpc/adapter.d.ts.map +1 -0
- package/dist/adapters/grpc/adapter.js +362 -0
- package/dist/adapters/grpc/adapter.js.map +1 -0
- package/dist/adapters/grpc/index.d.ts +10 -0
- package/dist/adapters/grpc/index.d.ts.map +1 -0
- package/dist/adapters/grpc/index.js +12 -0
- package/dist/adapters/grpc/index.js.map +1 -0
- package/dist/adapters/grpc/proto-parser.d.ts +76 -0
- package/dist/adapters/grpc/proto-parser.d.ts.map +1 -0
- package/dist/adapters/grpc/proto-parser.js +523 -0
- package/dist/adapters/grpc/proto-parser.js.map +1 -0
- package/dist/adapters/grpc/type-converter.d.ts +43 -0
- package/dist/adapters/grpc/type-converter.d.ts.map +1 -0
- package/dist/adapters/grpc/type-converter.js +270 -0
- package/dist/adapters/grpc/type-converter.js.map +1 -0
- package/dist/adapters/grpc/types.d.ts +85 -0
- package/dist/adapters/grpc/types.d.ts.map +1 -0
- package/dist/adapters/grpc/types.js +7 -0
- package/dist/adapters/grpc/types.js.map +1 -0
- package/dist/adapters/index.d.ts +39 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +50 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mcp.d.ts +23 -0
- package/dist/adapters/mcp.d.ts.map +1 -0
- package/dist/adapters/mcp.js +293 -0
- package/dist/adapters/mcp.js.map +1 -0
- package/dist/adapters/openapi/adapter.d.ts +213 -0
- package/dist/adapters/openapi/adapter.d.ts.map +1 -0
- package/dist/adapters/openapi/adapter.js +557 -0
- package/dist/adapters/openapi/adapter.js.map +1 -0
- package/dist/adapters/openapi/convert.d.ts +120 -0
- package/dist/adapters/openapi/convert.d.ts.map +1 -0
- package/dist/adapters/openapi/convert.js +363 -0
- package/dist/adapters/openapi/convert.js.map +1 -0
- package/dist/adapters/openapi/index.d.ts +39 -0
- package/dist/adapters/openapi/index.d.ts.map +1 -0
- package/dist/adapters/openapi/index.js +48 -0
- package/dist/adapters/openapi/index.js.map +1 -0
- package/dist/adapters/openapi/parser.d.ts +95 -0
- package/dist/adapters/openapi/parser.d.ts.map +1 -0
- package/dist/adapters/openapi/parser.js +171 -0
- package/dist/adapters/openapi/parser.js.map +1 -0
- package/dist/adapters/registry.d.ts +116 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +246 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/trpc/adapter.d.ts +159 -0
- package/dist/adapters/trpc/adapter.d.ts.map +1 -0
- package/dist/adapters/trpc/adapter.js +223 -0
- package/dist/adapters/trpc/adapter.js.map +1 -0
- package/dist/adapters/trpc/extractor.d.ts +218 -0
- package/dist/adapters/trpc/extractor.d.ts.map +1 -0
- package/dist/adapters/trpc/extractor.js +708 -0
- package/dist/adapters/trpc/extractor.js.map +1 -0
- package/dist/adapters/trpc/index.d.ts +31 -0
- package/dist/adapters/trpc/index.d.ts.map +1 -0
- package/dist/adapters/trpc/index.js +40 -0
- package/dist/adapters/trpc/index.js.map +1 -0
- package/dist/adapters/trpc/parser.d.ts +119 -0
- package/dist/adapters/trpc/parser.d.ts.map +1 -0
- package/dist/adapters/trpc/parser.js +128 -0
- package/dist/adapters/trpc/parser.js.map +1 -0
- package/dist/compare/index.d.ts +33 -0
- package/dist/compare/index.d.ts.map +1 -0
- package/dist/compare/index.js +261 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/core/types.d.ts +188 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/extract/index.d.ts +26 -0
- package/dist/extract/index.d.ts.map +1 -0
- package/dist/extract/index.js +44 -0
- package/dist/extract/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +674 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/base.d.ts +57 -0
- package/dist/languages/base.d.ts.map +1 -0
- package/dist/languages/base.js +6 -0
- package/dist/languages/base.js.map +1 -0
- package/dist/languages/bootstrap.d.ts +10 -0
- package/dist/languages/bootstrap.d.ts.map +1 -0
- package/dist/languages/bootstrap.js +25 -0
- package/dist/languages/bootstrap.js.map +1 -0
- package/dist/languages/go/handlers/chi.d.ts +24 -0
- package/dist/languages/go/handlers/chi.d.ts.map +1 -0
- package/dist/languages/go/handlers/chi.js +205 -0
- package/dist/languages/go/handlers/chi.js.map +1 -0
- package/dist/languages/go/handlers/gin.d.ts +24 -0
- package/dist/languages/go/handlers/gin.d.ts.map +1 -0
- package/dist/languages/go/handlers/gin.js +156 -0
- package/dist/languages/go/handlers/gin.js.map +1 -0
- package/dist/languages/go/handlers/stdlib.d.ts +19 -0
- package/dist/languages/go/handlers/stdlib.d.ts.map +1 -0
- package/dist/languages/go/handlers/stdlib.js +112 -0
- package/dist/languages/go/handlers/stdlib.js.map +1 -0
- package/dist/languages/go/index.d.ts +18 -0
- package/dist/languages/go/index.d.ts.map +1 -0
- package/dist/languages/go/index.js +20 -0
- package/dist/languages/go/index.js.map +1 -0
- package/dist/languages/go/parser.d.ts +33 -0
- package/dist/languages/go/parser.d.ts.map +1 -0
- package/dist/languages/go/parser.js +95 -0
- package/dist/languages/go/parser.js.map +1 -0
- package/dist/languages/go/struct-extractor.d.ts +59 -0
- package/dist/languages/go/struct-extractor.d.ts.map +1 -0
- package/dist/languages/go/struct-extractor.js +483 -0
- package/dist/languages/go/struct-extractor.js.map +1 -0
- package/dist/languages/go/tag-parser.d.ts +62 -0
- package/dist/languages/go/tag-parser.d.ts.map +1 -0
- package/dist/languages/go/tag-parser.js +108 -0
- package/dist/languages/go/tag-parser.js.map +1 -0
- package/dist/languages/go/type-converter.d.ts +32 -0
- package/dist/languages/go/type-converter.d.ts.map +1 -0
- package/dist/languages/go/type-converter.js +226 -0
- package/dist/languages/go/type-converter.js.map +1 -0
- package/dist/languages/go/types.d.ts +153 -0
- package/dist/languages/go/types.d.ts.map +1 -0
- package/dist/languages/go/types.js +6 -0
- package/dist/languages/go/types.js.map +1 -0
- package/dist/languages/import-resolver.d.ts +645 -0
- package/dist/languages/import-resolver.d.ts.map +1 -0
- package/dist/languages/import-resolver.js +1278 -0
- package/dist/languages/import-resolver.js.map +1 -0
- package/dist/languages/index.d.ts +34 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +93 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/json-schema.d.ts +40 -0
- package/dist/languages/json-schema.d.ts.map +1 -0
- package/dist/languages/json-schema.js +188 -0
- package/dist/languages/json-schema.js.map +1 -0
- package/dist/languages/python-ast/index.d.ts +8 -0
- package/dist/languages/python-ast/index.d.ts.map +1 -0
- package/dist/languages/python-ast/index.js +7 -0
- package/dist/languages/python-ast/index.js.map +1 -0
- package/dist/languages/python-ast/parser.d.ts +174 -0
- package/dist/languages/python-ast/parser.d.ts.map +1 -0
- package/dist/languages/python-ast/parser.js +1205 -0
- package/dist/languages/python-ast/parser.js.map +1 -0
- package/dist/languages/python-ast/type-resolver.d.ts +75 -0
- package/dist/languages/python-ast/type-resolver.d.ts.map +1 -0
- package/dist/languages/python-ast/type-resolver.js +421 -0
- package/dist/languages/python-ast/type-resolver.js.map +1 -0
- package/dist/languages/python-ast/types.d.ts +216 -0
- package/dist/languages/python-ast/types.d.ts.map +1 -0
- package/dist/languages/python-ast/types.js +6 -0
- package/dist/languages/python-ast/types.js.map +1 -0
- package/dist/languages/python.d.ts +55 -0
- package/dist/languages/python.d.ts.map +1 -0
- package/dist/languages/python.js +311 -0
- package/dist/languages/python.js.map +1 -0
- package/dist/languages/typescript.d.ts +272 -0
- package/dist/languages/typescript.d.ts.map +1 -0
- package/dist/languages/typescript.js +1381 -0
- package/dist/languages/typescript.js.map +1 -0
- package/dist/patterns/base.d.ts +146 -0
- package/dist/patterns/base.d.ts.map +1 -0
- package/dist/patterns/base.js +89 -0
- package/dist/patterns/base.js.map +1 -0
- package/dist/patterns/errors.d.ts +172 -0
- package/dist/patterns/errors.d.ts.map +1 -0
- package/dist/patterns/errors.js +185 -0
- package/dist/patterns/errors.js.map +1 -0
- package/dist/patterns/extractors.d.ts +170 -0
- package/dist/patterns/extractors.d.ts.map +1 -0
- package/dist/patterns/extractors.js +305 -0
- package/dist/patterns/extractors.js.map +1 -0
- package/dist/patterns/graphql/apollo-client.d.ts +80 -0
- package/dist/patterns/graphql/apollo-client.d.ts.map +1 -0
- package/dist/patterns/graphql/apollo-client.js +800 -0
- package/dist/patterns/graphql/apollo-client.js.map +1 -0
- package/dist/patterns/graphql/apollo-server.d.ts +55 -0
- package/dist/patterns/graphql/apollo-server.d.ts.map +1 -0
- package/dist/patterns/graphql/apollo-server.js +523 -0
- package/dist/patterns/graphql/apollo-server.js.map +1 -0
- package/dist/patterns/graphql/index.d.ts +11 -0
- package/dist/patterns/graphql/index.d.ts.map +1 -0
- package/dist/patterns/graphql/index.js +12 -0
- package/dist/patterns/graphql/index.js.map +1 -0
- package/dist/patterns/graphql/types.d.ts +213 -0
- package/dist/patterns/graphql/types.d.ts.map +1 -0
- package/dist/patterns/graphql/types.js +16 -0
- package/dist/patterns/graphql/types.js.map +1 -0
- package/dist/patterns/http-clients/axios.d.ts +148 -0
- package/dist/patterns/http-clients/axios.d.ts.map +1 -0
- package/dist/patterns/http-clients/axios.js +652 -0
- package/dist/patterns/http-clients/axios.js.map +1 -0
- package/dist/patterns/http-clients/fetch.d.ts +88 -0
- package/dist/patterns/http-clients/fetch.d.ts.map +1 -0
- package/dist/patterns/http-clients/fetch.js +364 -0
- package/dist/patterns/http-clients/fetch.js.map +1 -0
- package/dist/patterns/http-clients/index.d.ts +36 -0
- package/dist/patterns/http-clients/index.d.ts.map +1 -0
- package/dist/patterns/http-clients/index.js +50 -0
- package/dist/patterns/http-clients/index.js.map +1 -0
- package/dist/patterns/http-clients/property-access.d.ts +46 -0
- package/dist/patterns/http-clients/property-access.d.ts.map +1 -0
- package/dist/patterns/http-clients/property-access.js +818 -0
- package/dist/patterns/http-clients/property-access.js.map +1 -0
- package/dist/patterns/http-clients/type-inference.d.ts +48 -0
- package/dist/patterns/http-clients/type-inference.d.ts.map +1 -0
- package/dist/patterns/http-clients/type-inference.js +293 -0
- package/dist/patterns/http-clients/type-inference.js.map +1 -0
- package/dist/patterns/http-clients/types.d.ts +168 -0
- package/dist/patterns/http-clients/types.d.ts.map +1 -0
- package/dist/patterns/http-clients/types.js +10 -0
- package/dist/patterns/http-clients/types.js.map +1 -0
- package/dist/patterns/http-clients/url-extractor.d.ts +53 -0
- package/dist/patterns/http-clients/url-extractor.d.ts.map +1 -0
- package/dist/patterns/http-clients/url-extractor.js +338 -0
- package/dist/patterns/http-clients/url-extractor.js.map +1 -0
- package/dist/patterns/index.d.ts +44 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +49 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/python/aiohttp.d.ts +21 -0
- package/dist/patterns/python/aiohttp.d.ts.map +1 -0
- package/dist/patterns/python/aiohttp.js +188 -0
- package/dist/patterns/python/aiohttp.js.map +1 -0
- package/dist/patterns/python/httpx.d.ts +20 -0
- package/dist/patterns/python/httpx.d.ts.map +1 -0
- package/dist/patterns/python/httpx.js +183 -0
- package/dist/patterns/python/httpx.js.map +1 -0
- package/dist/patterns/python/index.d.ts +32 -0
- package/dist/patterns/python/index.d.ts.map +1 -0
- package/dist/patterns/python/index.js +63 -0
- package/dist/patterns/python/index.js.map +1 -0
- package/dist/patterns/python/property-access.d.ts +27 -0
- package/dist/patterns/python/property-access.d.ts.map +1 -0
- package/dist/patterns/python/property-access.js +132 -0
- package/dist/patterns/python/property-access.js.map +1 -0
- package/dist/patterns/python/requests.d.ts +19 -0
- package/dist/patterns/python/requests.d.ts.map +1 -0
- package/dist/patterns/python/requests.js +239 -0
- package/dist/patterns/python/requests.js.map +1 -0
- package/dist/patterns/python/types.d.ts +95 -0
- package/dist/patterns/python/types.d.ts.map +1 -0
- package/dist/patterns/python/types.js +43 -0
- package/dist/patterns/python/types.js.map +1 -0
- package/dist/patterns/registry.d.ts +181 -0
- package/dist/patterns/registry.d.ts.map +1 -0
- package/dist/patterns/registry.js +304 -0
- package/dist/patterns/registry.js.map +1 -0
- package/dist/patterns/rest/express.d.ts +78 -0
- package/dist/patterns/rest/express.d.ts.map +1 -0
- package/dist/patterns/rest/express.js +289 -0
- package/dist/patterns/rest/express.js.map +1 -0
- package/dist/patterns/rest/fastify.d.ts +93 -0
- package/dist/patterns/rest/fastify.d.ts.map +1 -0
- package/dist/patterns/rest/fastify.js +420 -0
- package/dist/patterns/rest/fastify.js.map +1 -0
- package/dist/patterns/rest/index.d.ts +31 -0
- package/dist/patterns/rest/index.d.ts.map +1 -0
- package/dist/patterns/rest/index.js +45 -0
- package/dist/patterns/rest/index.js.map +1 -0
- package/dist/patterns/rest/middleware.d.ts +25 -0
- package/dist/patterns/rest/middleware.d.ts.map +1 -0
- package/dist/patterns/rest/middleware.js +219 -0
- package/dist/patterns/rest/middleware.js.map +1 -0
- package/dist/patterns/rest/path-parser.d.ts +50 -0
- package/dist/patterns/rest/path-parser.d.ts.map +1 -0
- package/dist/patterns/rest/path-parser.js +137 -0
- package/dist/patterns/rest/path-parser.js.map +1 -0
- package/dist/patterns/rest/response-inference.d.ts +44 -0
- package/dist/patterns/rest/response-inference.d.ts.map +1 -0
- package/dist/patterns/rest/response-inference.js +218 -0
- package/dist/patterns/rest/response-inference.js.map +1 -0
- package/dist/patterns/rest/types.d.ts +102 -0
- package/dist/patterns/rest/types.d.ts.map +1 -0
- package/dist/patterns/rest/types.js +10 -0
- package/dist/patterns/rest/types.js.map +1 -0
- package/dist/patterns/types.d.ts +105 -0
- package/dist/patterns/types.d.ts.map +1 -0
- package/dist/patterns/types.js +11 -0
- package/dist/patterns/types.js.map +1 -0
- package/dist/report/index.d.ts +11 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +55 -0
- package/dist/report/index.js.map +1 -0
- package/dist/tools/contract-comments.d.ts +48 -0
- package/dist/tools/contract-comments.d.ts.map +1 -0
- package/dist/tools/contract-comments.js +130 -0
- package/dist/tools/contract-comments.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/scaffold.d.ts +38 -0
- package/dist/tools/scaffold.d.ts.map +1 -0
- package/dist/tools/scaffold.js +373 -0
- package/dist/tools/scaffold.js.map +1 -0
- package/dist/trace/index.d.ts +28 -0
- package/dist/trace/index.d.ts.map +1 -0
- package/dist/trace/index.js +45 -0
- package/dist/trace/index.js.map +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/watch/cache.d.ts +41 -0
- package/dist/watch/cache.d.ts.map +1 -0
- package/dist/watch/cache.js +230 -0
- package/dist/watch/cache.js.map +1 -0
- package/dist/watch/index.d.ts +9 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +7 -0
- package/dist/watch/index.js.map +1 -0
- package/dist/watch/project.d.ts +128 -0
- package/dist/watch/project.d.ts.map +1 -0
- package/dist/watch/project.js +152 -0
- package/dist/watch/project.js.map +1 -0
- package/dist/watch/watcher.d.ts +76 -0
- package/dist/watch/watcher.d.ts.map +1 -0
- package/dist/watch/watcher.js +235 -0
- package/dist/watch/watcher.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Registry
|
|
3
|
+
*
|
|
4
|
+
* Registry for pattern matchers, following the same design as AdapterRegistry.
|
|
5
|
+
* Provides registration, lookup, and scanning capabilities.
|
|
6
|
+
*
|
|
7
|
+
* The registry uses a module-scoped singleton pattern with exported wrapper
|
|
8
|
+
* functions for a clean, functional API.
|
|
9
|
+
*
|
|
10
|
+
* @module patterns/registry
|
|
11
|
+
* @see .context/ADR-P2-1-PATTERN-MATCHER.md
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { registerPattern, scanForPatterns } from './patterns';
|
|
16
|
+
*
|
|
17
|
+
* // Register a custom pattern matcher
|
|
18
|
+
* registerPattern(myExpressMatcher);
|
|
19
|
+
*
|
|
20
|
+
* // Scan source file for patterns
|
|
21
|
+
* const matches = scanForPatterns(sourceFile, {
|
|
22
|
+
* frameworks: ['express'],
|
|
23
|
+
* types: ['call']
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { PatternNotFoundError, PatternValidationError, } from './errors.js';
|
|
28
|
+
/**
|
|
29
|
+
* Registry of pattern matchers
|
|
30
|
+
*
|
|
31
|
+
* Follows the same design as AdapterRegistry from Phase 1,
|
|
32
|
+
* with module-scoped singleton and exported wrapper functions.
|
|
33
|
+
*/
|
|
34
|
+
class PatternRegistry {
|
|
35
|
+
matchers = new Map();
|
|
36
|
+
matchersByFramework = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* Register a pattern matcher
|
|
39
|
+
* @throws {PatternValidationError} if matcher is invalid
|
|
40
|
+
*/
|
|
41
|
+
register(matcher) {
|
|
42
|
+
this.validate(matcher);
|
|
43
|
+
// Warn on overwrite in debug mode
|
|
44
|
+
if (this.matchers.has(matcher.name) && process.env.DEBUG_TRACE_MCP) {
|
|
45
|
+
console.error(`[PatternRegistry] Overwriting matcher: ${matcher.name}`);
|
|
46
|
+
}
|
|
47
|
+
this.matchers.set(matcher.name, matcher);
|
|
48
|
+
// Index by framework
|
|
49
|
+
const existing = this.matchersByFramework.get(matcher.framework) || [];
|
|
50
|
+
this.matchersByFramework.set(matcher.framework, [...existing.filter(m => m.name !== matcher.name), matcher]);
|
|
51
|
+
if (process.env.DEBUG_TRACE_MCP) {
|
|
52
|
+
console.error(`[PatternRegistry] Registered: ${matcher.name} (${matcher.framework})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get matcher by name
|
|
57
|
+
* @throws {PatternNotFoundError} if not found
|
|
58
|
+
*/
|
|
59
|
+
get(name) {
|
|
60
|
+
const matcher = this.matchers.get(name);
|
|
61
|
+
if (!matcher) {
|
|
62
|
+
throw new PatternNotFoundError(name, this.names());
|
|
63
|
+
}
|
|
64
|
+
return matcher;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get all matchers for a framework
|
|
68
|
+
*/
|
|
69
|
+
getByFramework(framework) {
|
|
70
|
+
return this.matchersByFramework.get(framework) || [];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get all matchers supporting a pattern type
|
|
74
|
+
*/
|
|
75
|
+
getByType(type) {
|
|
76
|
+
return Array.from(this.matchers.values())
|
|
77
|
+
.filter(m => m.supportedTypes.includes(type));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if a matcher exists
|
|
81
|
+
*/
|
|
82
|
+
has(name) {
|
|
83
|
+
return this.matchers.has(name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* List all matcher names
|
|
87
|
+
*/
|
|
88
|
+
names() {
|
|
89
|
+
return Array.from(this.matchers.keys());
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List all frameworks
|
|
93
|
+
*/
|
|
94
|
+
frameworks() {
|
|
95
|
+
return Array.from(this.matchersByFramework.keys());
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Scan a source file with all registered matchers
|
|
99
|
+
*
|
|
100
|
+
* @param sourceFile - The file to scan
|
|
101
|
+
* @param options - Optional filtering options
|
|
102
|
+
* @returns All matches found across all matchers
|
|
103
|
+
*/
|
|
104
|
+
scan(sourceFile, options) {
|
|
105
|
+
let matchers = Array.from(this.matchers.values());
|
|
106
|
+
// Filter by framework
|
|
107
|
+
if (options?.frameworks?.length) {
|
|
108
|
+
matchers = matchers.filter(m => options.frameworks.includes(m.framework));
|
|
109
|
+
}
|
|
110
|
+
// Filter by pattern type
|
|
111
|
+
if (options?.types?.length) {
|
|
112
|
+
matchers = matchers.filter(m => m.supportedTypes.some(t => options.types.includes(t)));
|
|
113
|
+
}
|
|
114
|
+
const allMatches = [];
|
|
115
|
+
for (const matcher of matchers) {
|
|
116
|
+
const matches = matcher.scan
|
|
117
|
+
? matcher.scan(sourceFile)
|
|
118
|
+
: this.defaultScan(matcher, sourceFile);
|
|
119
|
+
allMatches.push(...matches);
|
|
120
|
+
}
|
|
121
|
+
return allMatches;
|
|
122
|
+
}
|
|
123
|
+
defaultScan(matcher, sourceFile) {
|
|
124
|
+
const matches = [];
|
|
125
|
+
sourceFile.forEachDescendant((node) => {
|
|
126
|
+
const result = matcher.match(node);
|
|
127
|
+
if (result) {
|
|
128
|
+
matches.push(result);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return matches;
|
|
132
|
+
}
|
|
133
|
+
validate(matcher) {
|
|
134
|
+
const m = matcher;
|
|
135
|
+
if (!m.name || typeof m.name !== 'string') {
|
|
136
|
+
throw new PatternValidationError(matcher, "Matcher must have 'name' property");
|
|
137
|
+
}
|
|
138
|
+
if (!m.framework || typeof m.framework !== 'string') {
|
|
139
|
+
throw new PatternValidationError(matcher, "Matcher must have 'framework' property");
|
|
140
|
+
}
|
|
141
|
+
if (!Array.isArray(m.patterns) || m.patterns.length === 0) {
|
|
142
|
+
throw new PatternValidationError(matcher, "Matcher must have at least one pattern");
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(m.supportedTypes) || m.supportedTypes.length === 0) {
|
|
145
|
+
throw new PatternValidationError(matcher, "Matcher must declare supportedTypes");
|
|
146
|
+
}
|
|
147
|
+
if (typeof m.match !== 'function') {
|
|
148
|
+
throw new PatternValidationError(matcher, "Matcher must implement match()");
|
|
149
|
+
}
|
|
150
|
+
if (typeof m.extract !== 'function') {
|
|
151
|
+
throw new PatternValidationError(matcher, "Matcher must implement extract()");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Module-scoped singleton
|
|
156
|
+
const registry = new PatternRegistry();
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
// Public API - Wrapper Functions
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Register a pattern matcher with the global registry.
|
|
162
|
+
*
|
|
163
|
+
* The matcher is validated before registration. If validation fails,
|
|
164
|
+
* a {@link PatternValidationError} is thrown with details.
|
|
165
|
+
*
|
|
166
|
+
* @param matcher - The pattern matcher to register
|
|
167
|
+
* @throws {PatternValidationError} If the matcher fails validation
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { registerPattern, BasePatternMatcher } from './patterns';
|
|
172
|
+
*
|
|
173
|
+
* class MyMatcher extends BasePatternMatcher {
|
|
174
|
+
* readonly name = 'my-matcher';
|
|
175
|
+
* readonly framework = 'express';
|
|
176
|
+
* // ... implementation
|
|
177
|
+
* }
|
|
178
|
+
*
|
|
179
|
+
* registerPattern(new MyMatcher());
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function registerPattern(matcher) {
|
|
183
|
+
registry.register(matcher);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get a pattern matcher by its unique name.
|
|
187
|
+
*
|
|
188
|
+
* @param name - The unique name of the matcher to retrieve
|
|
189
|
+
* @returns The registered pattern matcher
|
|
190
|
+
* @throws {PatternNotFoundError} If no matcher with that name exists
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* const matcher = getPattern('express-router');
|
|
195
|
+
* const matches = matcher.scan(sourceFile);
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export function getPattern(name) {
|
|
199
|
+
return registry.get(name);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get all pattern matchers for a specific framework.
|
|
203
|
+
*
|
|
204
|
+
* @param framework - The framework name to filter by
|
|
205
|
+
* @returns Array of matchers for that framework (empty if none)
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* const expressMatchers = getPatternsByFramework('express');
|
|
210
|
+
* console.log(`Found ${expressMatchers.length} Express matchers`);
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function getPatternsByFramework(framework) {
|
|
214
|
+
return registry.getByFramework(framework);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get all pattern matchers supporting a specific pattern type.
|
|
218
|
+
*
|
|
219
|
+
* @param type - The pattern type to filter by
|
|
220
|
+
* @returns Array of matchers supporting that type
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* const decoratorMatchers = getPatternsByType('decorator');
|
|
225
|
+
* // Returns matchers like NestJS that use decorators
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
export function getPatternsByType(type) {
|
|
229
|
+
return registry.getByType(type);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check if a pattern matcher is registered by name.
|
|
233
|
+
*
|
|
234
|
+
* @param name - The name to check
|
|
235
|
+
* @returns `true` if a matcher with that name exists
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* if (!hasPattern('express-router')) {
|
|
240
|
+
* registerPattern(new ExpressRouterMatcher());
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export function hasPattern(name) {
|
|
245
|
+
return registry.has(name);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* List all registered pattern matcher names.
|
|
249
|
+
*
|
|
250
|
+
* @returns Array of all registered matcher names
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* console.log('Available matchers:', listPatterns().join(', '));
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export function listPatterns() {
|
|
258
|
+
return registry.names();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* List all unique framework names from registered matchers.
|
|
262
|
+
*
|
|
263
|
+
* @returns Array of unique framework names
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const frameworks = listFrameworks();
|
|
268
|
+
* // ['express', 'nestjs', 'trpc', ...]
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export function listFrameworks() {
|
|
272
|
+
return registry.frameworks();
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Scan a source file for patterns using all registered matchers.
|
|
276
|
+
*
|
|
277
|
+
* This is the primary entry point for pattern detection. It applies
|
|
278
|
+
* all registered matchers (or a filtered subset) to find API patterns.
|
|
279
|
+
*
|
|
280
|
+
* @param sourceFile - The ts-morph SourceFile to scan
|
|
281
|
+
* @param options - Optional filtering options
|
|
282
|
+
* @returns Array of all matches found across all matchers
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* import { Project } from 'ts-morph';
|
|
287
|
+
*
|
|
288
|
+
* const project = new Project();
|
|
289
|
+
* const sourceFile = project.addSourceFileAtPath('./src/routes.ts');
|
|
290
|
+
*
|
|
291
|
+
* // Scan with all matchers
|
|
292
|
+
* const allMatches = scanForPatterns(sourceFile);
|
|
293
|
+
*
|
|
294
|
+
* // Scan with filters
|
|
295
|
+
* const expressMatches = scanForPatterns(sourceFile, {
|
|
296
|
+
* frameworks: ['express'],
|
|
297
|
+
* types: ['call']
|
|
298
|
+
* });
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export function scanForPatterns(sourceFile, options) {
|
|
302
|
+
return registry.scan(sourceFile, options);
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/patterns/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Pattern Matcher
|
|
3
|
+
*
|
|
4
|
+
* Detects Express.js route patterns including:
|
|
5
|
+
* - app.get(), app.post(), etc.
|
|
6
|
+
* - router.get(), router.post(), etc.
|
|
7
|
+
* - app.route().get().post()
|
|
8
|
+
* - app.all(), app.use()
|
|
9
|
+
*
|
|
10
|
+
* @module patterns/rest/express
|
|
11
|
+
* @see .context/ADR-P2-2-REST-DETECTION.md
|
|
12
|
+
*/
|
|
13
|
+
import { Node } from 'ts-morph';
|
|
14
|
+
import type { NormalizedSchema } from '../../core/types.js';
|
|
15
|
+
import { BasePatternMatcher } from '../base.js';
|
|
16
|
+
import type { PatternDef, MatchResult, PatternType } from '../types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Pattern matcher for Express.js routes.
|
|
19
|
+
*
|
|
20
|
+
* Detects patterns:
|
|
21
|
+
* - `app.get()`, `app.post()`, etc.
|
|
22
|
+
* - `router.get()`, `router.post()`, etc.
|
|
23
|
+
* - `app.route('/path').get().post()`
|
|
24
|
+
* - `app.all()`, `app.use()`
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const matcher = new ExpressPatternMatcher();
|
|
29
|
+
* const result = matcher.match(callExpressionNode);
|
|
30
|
+
* if (result) {
|
|
31
|
+
* console.log(result.captures.httpMethod); // 'GET'
|
|
32
|
+
* console.log(result.captures.routePath); // '/users/:id'
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare class ExpressPatternMatcher extends BasePatternMatcher {
|
|
37
|
+
readonly name = "express";
|
|
38
|
+
readonly framework = "express";
|
|
39
|
+
readonly supportedTypes: PatternType[];
|
|
40
|
+
readonly patterns: PatternDef[];
|
|
41
|
+
/**
|
|
42
|
+
* Match an AST node against Express patterns
|
|
43
|
+
*/
|
|
44
|
+
match(node: Node): MatchResult | null;
|
|
45
|
+
/**
|
|
46
|
+
* Match direct HTTP method calls: app.get(), router.post(), etc.
|
|
47
|
+
*/
|
|
48
|
+
private matchDirectMethod;
|
|
49
|
+
/**
|
|
50
|
+
* Match route chain: app.route('/path').get().post()
|
|
51
|
+
*/
|
|
52
|
+
private matchRouteChain;
|
|
53
|
+
/**
|
|
54
|
+
* Find app.route('/path') in a chain
|
|
55
|
+
*/
|
|
56
|
+
private findRouteCall;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a method name is an HTTP method
|
|
59
|
+
*/
|
|
60
|
+
private isHttpMethod;
|
|
61
|
+
/**
|
|
62
|
+
* Get the router/app name from an expression
|
|
63
|
+
*/
|
|
64
|
+
private getRouterName;
|
|
65
|
+
/**
|
|
66
|
+
* Extract string value from a path argument
|
|
67
|
+
*/
|
|
68
|
+
private extractPathString;
|
|
69
|
+
/**
|
|
70
|
+
* Extract pattern - implement abstract method
|
|
71
|
+
*/
|
|
72
|
+
protected matchPattern(pattern: PatternDef, node: Node): MatchResult | null;
|
|
73
|
+
/**
|
|
74
|
+
* Extract schema from match
|
|
75
|
+
*/
|
|
76
|
+
extract(match: MatchResult): Promise<NormalizedSchema>;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/patterns/rest/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAwD,MAAM,UAAU,CAAC;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmBxE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IAC3D,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,SAAS,aAAa;IAC/B,QAAQ,CAAC,cAAc,EAAE,WAAW,EAAE,CAAqB;IAE3D,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,CAa7B;IAEF;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,IAAI;IAkBrC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4DzB;;OAEG;IACH,OAAO,CAAC,eAAe;IA2CvB;;OAEG;IACH,OAAO,CAAC,aAAa;IA4BrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,IAAI;IAK3E;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA8C7D"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Pattern Matcher
|
|
3
|
+
*
|
|
4
|
+
* Detects Express.js route patterns including:
|
|
5
|
+
* - app.get(), app.post(), etc.
|
|
6
|
+
* - router.get(), router.post(), etc.
|
|
7
|
+
* - app.route().get().post()
|
|
8
|
+
* - app.all(), app.use()
|
|
9
|
+
*
|
|
10
|
+
* @module patterns/rest/express
|
|
11
|
+
* @see .context/ADR-P2-2-REST-DETECTION.md
|
|
12
|
+
*/
|
|
13
|
+
import { Node } from 'ts-morph';
|
|
14
|
+
import { BasePatternMatcher } from '../base.js';
|
|
15
|
+
import { parsePath } from './path-parser.js';
|
|
16
|
+
import { detectExpressMiddleware } from './middleware.js';
|
|
17
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
* 📋 Constants
|
|
19
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
20
|
+
/**
|
|
21
|
+
* HTTP methods supported by Express
|
|
22
|
+
*/
|
|
23
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'];
|
|
24
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
* 🔍 ExpressPatternMatcher Class
|
|
26
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
27
|
+
/**
|
|
28
|
+
* Pattern matcher for Express.js routes.
|
|
29
|
+
*
|
|
30
|
+
* Detects patterns:
|
|
31
|
+
* - `app.get()`, `app.post()`, etc.
|
|
32
|
+
* - `router.get()`, `router.post()`, etc.
|
|
33
|
+
* - `app.route('/path').get().post()`
|
|
34
|
+
* - `app.all()`, `app.use()`
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const matcher = new ExpressPatternMatcher();
|
|
39
|
+
* const result = matcher.match(callExpressionNode);
|
|
40
|
+
* if (result) {
|
|
41
|
+
* console.log(result.captures.httpMethod); // 'GET'
|
|
42
|
+
* console.log(result.captures.routePath); // '/users/:id'
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class ExpressPatternMatcher extends BasePatternMatcher {
|
|
47
|
+
name = 'express';
|
|
48
|
+
framework = 'express';
|
|
49
|
+
supportedTypes = ['call', 'chain'];
|
|
50
|
+
patterns = [
|
|
51
|
+
// app.get(), router.post(), etc.
|
|
52
|
+
{
|
|
53
|
+
type: 'call',
|
|
54
|
+
signature: /^(get|post|put|delete|patch|options|head|all)$/i,
|
|
55
|
+
inputSchemaLocation: { type: 'body' },
|
|
56
|
+
},
|
|
57
|
+
// app.route().get().post()
|
|
58
|
+
{
|
|
59
|
+
type: 'chain',
|
|
60
|
+
signature: /\.route\s*\([^)]+\)\s*\./,
|
|
61
|
+
inputSchemaLocation: { type: 'chain-method', method: 'route' },
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Match an AST node against Express patterns
|
|
66
|
+
*/
|
|
67
|
+
match(node) {
|
|
68
|
+
if (!node)
|
|
69
|
+
return null;
|
|
70
|
+
// Must be a call expression
|
|
71
|
+
if (!Node.isCallExpression(node)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Check for route chain (app.route('/path').get())
|
|
75
|
+
const routeChainResult = this.matchRouteChain(node);
|
|
76
|
+
if (routeChainResult) {
|
|
77
|
+
return routeChainResult;
|
|
78
|
+
}
|
|
79
|
+
// Check for direct method call (app.get('/path', handler))
|
|
80
|
+
return this.matchDirectMethod(node);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Match direct HTTP method calls: app.get(), router.post(), etc.
|
|
84
|
+
*/
|
|
85
|
+
matchDirectMethod(call) {
|
|
86
|
+
const expression = call.getExpression();
|
|
87
|
+
if (!Node.isPropertyAccessExpression(expression)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const methodName = expression.getName().toLowerCase();
|
|
91
|
+
if (!this.isHttpMethod(methodName)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
// Get the receiver (app, router, userRouter, etc.)
|
|
95
|
+
const receiver = expression.getExpression();
|
|
96
|
+
const routerName = this.getRouterName(receiver);
|
|
97
|
+
// First argument should be the path
|
|
98
|
+
const args = call.getArguments();
|
|
99
|
+
if (args.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const pathArg = args[0];
|
|
103
|
+
const routePath = this.extractPathString(pathArg);
|
|
104
|
+
if (!routePath) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// Parse path parameters
|
|
108
|
+
const pathParams = parsePath(routePath);
|
|
109
|
+
// Detect validation middleware
|
|
110
|
+
const middlewareArgs = args.slice(1, -1); // All but first (path) and last (handler)
|
|
111
|
+
const validationMiddleware = detectExpressMiddleware(middlewareArgs);
|
|
112
|
+
// Get handler (last argument)
|
|
113
|
+
const handler = args[args.length - 1];
|
|
114
|
+
const handlerNode = args.length > 1 ? handler : undefined;
|
|
115
|
+
const httpMethod = methodName.toUpperCase();
|
|
116
|
+
const sourceFile = call.getSourceFile();
|
|
117
|
+
const captures = {
|
|
118
|
+
httpMethod,
|
|
119
|
+
routePath,
|
|
120
|
+
pathParameters: pathParams,
|
|
121
|
+
routerName,
|
|
122
|
+
validationMiddleware: validationMiddleware.length > 0 ? validationMiddleware : undefined,
|
|
123
|
+
handlerNode,
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
pattern: this.patterns[0],
|
|
127
|
+
node: call,
|
|
128
|
+
framework: this.framework,
|
|
129
|
+
identifier: `${httpMethod} ${routePath}`,
|
|
130
|
+
location: this.getLocation(call, sourceFile.getFilePath()),
|
|
131
|
+
captures,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Match route chain: app.route('/path').get().post()
|
|
136
|
+
*/
|
|
137
|
+
matchRouteChain(call) {
|
|
138
|
+
const expression = call.getExpression();
|
|
139
|
+
if (!Node.isPropertyAccessExpression(expression)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const methodName = expression.getName().toLowerCase();
|
|
143
|
+
if (!this.isHttpMethod(methodName)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// Check if this is part of a route() chain
|
|
147
|
+
const routeInfo = this.findRouteCall(expression.getExpression());
|
|
148
|
+
if (!routeInfo) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
// Get handler (first argument for chained methods)
|
|
152
|
+
const args = call.getArguments();
|
|
153
|
+
const handlerNode = args.length > 0 ? args[0] : undefined;
|
|
154
|
+
const httpMethod = methodName.toUpperCase();
|
|
155
|
+
const pathParams = parsePath(routeInfo.path);
|
|
156
|
+
const sourceFile = call.getSourceFile();
|
|
157
|
+
const captures = {
|
|
158
|
+
httpMethod,
|
|
159
|
+
routePath: routeInfo.path,
|
|
160
|
+
pathParameters: pathParams,
|
|
161
|
+
routerName: routeInfo.routerName,
|
|
162
|
+
handlerNode,
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
pattern: this.patterns[1],
|
|
166
|
+
node: call,
|
|
167
|
+
framework: this.framework,
|
|
168
|
+
identifier: `${httpMethod} ${routeInfo.path}`,
|
|
169
|
+
location: this.getLocation(call, sourceFile.getFilePath()),
|
|
170
|
+
captures,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find app.route('/path') in a chain
|
|
175
|
+
*/
|
|
176
|
+
findRouteCall(node) {
|
|
177
|
+
// Could be: app.route('/path') directly
|
|
178
|
+
if (Node.isCallExpression(node)) {
|
|
179
|
+
const expr = node.getExpression();
|
|
180
|
+
if (Node.isPropertyAccessExpression(expr) && expr.getName() === 'route') {
|
|
181
|
+
const args = node.getArguments();
|
|
182
|
+
if (args.length > 0) {
|
|
183
|
+
const path = this.extractPathString(args[0]);
|
|
184
|
+
if (path) {
|
|
185
|
+
const routerName = this.getRouterName(expr.getExpression());
|
|
186
|
+
return { path, routerName };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Could be chained: app.route('/path').get().post()
|
|
191
|
+
// Walk up the chain
|
|
192
|
+
return this.findRouteCall(expr);
|
|
193
|
+
}
|
|
194
|
+
// Could be property access in a chain
|
|
195
|
+
if (Node.isPropertyAccessExpression(node)) {
|
|
196
|
+
return this.findRouteCall(node.getExpression());
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if a method name is an HTTP method
|
|
202
|
+
*/
|
|
203
|
+
isHttpMethod(name) {
|
|
204
|
+
return HTTP_METHODS.includes(name.toLowerCase());
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get the router/app name from an expression
|
|
208
|
+
*/
|
|
209
|
+
getRouterName(expr) {
|
|
210
|
+
if (Node.isIdentifier(expr)) {
|
|
211
|
+
return expr.getText();
|
|
212
|
+
}
|
|
213
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
214
|
+
// Could be something like this.router
|
|
215
|
+
return expr.getText();
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Extract string value from a path argument
|
|
221
|
+
*/
|
|
222
|
+
extractPathString(arg) {
|
|
223
|
+
if (Node.isStringLiteral(arg)) {
|
|
224
|
+
return arg.getLiteralValue();
|
|
225
|
+
}
|
|
226
|
+
if (Node.isNoSubstitutionTemplateLiteral(arg)) {
|
|
227
|
+
return arg.getLiteralValue();
|
|
228
|
+
}
|
|
229
|
+
if (Node.isTemplateExpression(arg)) {
|
|
230
|
+
// For now, just return the head text
|
|
231
|
+
// Full template analysis would need variable resolution
|
|
232
|
+
return arg.getHead().getText().slice(1, -2); // Remove ` and ${
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Extract pattern - implement abstract method
|
|
238
|
+
*/
|
|
239
|
+
matchPattern(pattern, node) {
|
|
240
|
+
// Delegate to the main match method
|
|
241
|
+
return this.match(node);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Extract schema from match
|
|
245
|
+
*/
|
|
246
|
+
async extract(match) {
|
|
247
|
+
const captures = match.captures;
|
|
248
|
+
// Build properties from path parameters
|
|
249
|
+
const properties = {};
|
|
250
|
+
// Add path parameters
|
|
251
|
+
if (captures.pathParameters) {
|
|
252
|
+
for (const param of captures.pathParameters) {
|
|
253
|
+
properties[`params.${param.name}`] = {
|
|
254
|
+
type: { kind: 'primitive', value: param.inferredType === 'number' ? 'number' : 'string' },
|
|
255
|
+
optional: param.optional,
|
|
256
|
+
nullable: false,
|
|
257
|
+
readonly: false,
|
|
258
|
+
deprecated: false,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Add validation middleware schemas
|
|
263
|
+
if (captures.validationMiddleware) {
|
|
264
|
+
for (const middleware of captures.validationMiddleware) {
|
|
265
|
+
// The actual schema extraction would need to parse the Zod/Joi schema
|
|
266
|
+
// For now, just note that validation exists
|
|
267
|
+
properties[`${middleware.target}._validated`] = {
|
|
268
|
+
type: { kind: 'unknown' },
|
|
269
|
+
optional: false,
|
|
270
|
+
nullable: false,
|
|
271
|
+
readonly: false,
|
|
272
|
+
deprecated: false,
|
|
273
|
+
description: `Validated by ${middleware.library}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
name: match.identifier,
|
|
279
|
+
properties,
|
|
280
|
+
required: Object.keys(properties).filter(k => !properties[k].optional),
|
|
281
|
+
source: {
|
|
282
|
+
source: 'typescript',
|
|
283
|
+
id: `express:${match.identifier}`,
|
|
284
|
+
},
|
|
285
|
+
location: match.location,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=express.js.map
|