gitnexus 1.6.0 → 1.6.2-rc.1

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.
Files changed (145) hide show
  1. package/README.md +73 -0
  2. package/dist/cli/analyze.js +50 -3
  3. package/dist/core/group/extractors/fs-utils.d.ts +10 -0
  4. package/dist/core/group/extractors/fs-utils.js +24 -0
  5. package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
  6. package/dist/core/group/extractors/grpc-extractor.js +328 -191
  7. package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
  8. package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
  9. package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
  10. package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
  11. package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
  12. package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
  13. package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
  14. package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
  15. package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
  16. package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
  17. package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
  18. package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
  19. package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
  20. package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
  21. package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
  22. package/dist/core/group/extractors/http-patterns/go.js +215 -0
  23. package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
  24. package/dist/core/group/extractors/http-patterns/index.js +44 -0
  25. package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
  26. package/dist/core/group/extractors/http-patterns/java.js +253 -0
  27. package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
  28. package/dist/core/group/extractors/http-patterns/node.js +354 -0
  29. package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
  30. package/dist/core/group/extractors/http-patterns/php.js +70 -0
  31. package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
  32. package/dist/core/group/extractors/http-patterns/python.js +133 -0
  33. package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
  34. package/dist/core/group/extractors/http-patterns/types.js +1 -0
  35. package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
  36. package/dist/core/group/extractors/http-route-extractor.js +231 -238
  37. package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
  38. package/dist/core/group/extractors/manifest-extractor.js +277 -0
  39. package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
  40. package/dist/core/group/extractors/topic-extractor.js +55 -192
  41. package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
  42. package/dist/core/group/extractors/topic-patterns/go.js +120 -0
  43. package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
  44. package/dist/core/group/extractors/topic-patterns/index.js +38 -0
  45. package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
  46. package/dist/core/group/extractors/topic-patterns/java.js +80 -0
  47. package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
  48. package/dist/core/group/extractors/topic-patterns/node.js +155 -0
  49. package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
  50. package/dist/core/group/extractors/topic-patterns/python.js +116 -0
  51. package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
  52. package/dist/core/group/extractors/topic-patterns/types.js +10 -0
  53. package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
  54. package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
  55. package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
  56. package/dist/core/ingestion/binding-accumulator.js +29 -25
  57. package/dist/core/ingestion/cobol-processor.d.ts +1 -1
  58. package/dist/core/ingestion/import-processor.js +1 -1
  59. package/dist/core/ingestion/language-config.js +1 -1
  60. package/dist/core/ingestion/language-provider.d.ts +32 -5
  61. package/dist/core/ingestion/languages/c-cpp.js +2 -2
  62. package/dist/core/ingestion/languages/dart.d.ts +1 -1
  63. package/dist/core/ingestion/languages/dart.js +2 -2
  64. package/dist/core/ingestion/languages/go.d.ts +1 -1
  65. package/dist/core/ingestion/languages/go.js +2 -2
  66. package/dist/core/ingestion/languages/ruby.js +16 -1
  67. package/dist/core/ingestion/languages/swift.d.ts +1 -1
  68. package/dist/core/ingestion/languages/swift.js +2 -2
  69. package/dist/core/ingestion/markdown-processor.d.ts +1 -1
  70. package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
  71. package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
  72. package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
  73. package/dist/core/ingestion/method-extractors/generic.js +48 -4
  74. package/dist/core/ingestion/method-types.d.ts +4 -0
  75. package/dist/core/ingestion/model/resolve.js +103 -48
  76. package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
  77. package/dist/core/ingestion/model/semantic-model.js +1 -1
  78. package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
  79. package/dist/core/ingestion/model/symbol-table.js +7 -7
  80. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  81. package/dist/core/ingestion/mro-processor.js +1 -1
  82. package/dist/core/ingestion/parsing-processor.js +54 -42
  83. package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
  84. package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
  85. package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
  86. package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
  87. package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
  88. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
  89. package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
  90. package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
  91. package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
  92. package/dist/core/ingestion/pipeline-phases/index.js +22 -0
  93. package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
  94. package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
  95. package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
  96. package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
  97. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
  98. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
  99. package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
  100. package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
  101. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
  102. package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
  103. package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
  104. package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
  105. package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
  106. package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
  107. package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
  108. package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
  109. package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
  110. package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
  111. package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
  112. package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
  113. package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
  114. package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
  115. package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
  116. package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
  117. package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
  118. package/dist/core/ingestion/pipeline-phases/types.js +37 -0
  119. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
  120. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
  121. package/dist/core/ingestion/pipeline.d.ts +16 -10
  122. package/dist/core/ingestion/pipeline.js +66 -1534
  123. package/dist/core/ingestion/process-processor.js +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  125. package/dist/core/ingestion/tree-sitter-queries.js +69 -0
  126. package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
  127. package/dist/core/ingestion/utils/ast-helpers.js +48 -21
  128. package/dist/core/ingestion/utils/env.d.ts +10 -0
  129. package/dist/core/ingestion/utils/env.js +10 -0
  130. package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
  131. package/dist/core/ingestion/utils/graph-sort.js +100 -0
  132. package/dist/core/ingestion/workers/parse-worker.js +12 -8
  133. package/dist/core/lbug/lbug-adapter.d.ts +28 -0
  134. package/dist/core/lbug/lbug-adapter.js +162 -57
  135. package/package.json +3 -3
  136. package/vendor/tree-sitter-proto/binding.gyp +30 -0
  137. package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
  138. package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
  139. package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
  140. package/vendor/tree-sitter-proto/package.json +18 -0
  141. package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
  142. package/vendor/tree-sitter-proto/src/parser.c +10149 -0
  143. package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
  144. package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
  145. package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
@@ -0,0 +1,19 @@
1
+ import type { GrpcLanguagePlugin } from './types.js';
2
+ export type { GrpcDetection, GrpcLanguagePlugin, GrpcRole } from './types.js';
3
+ export { PROTO_GRPC_PLUGIN, extractPackageFromTree } from './proto.js';
4
+ /**
5
+ * Glob for source files worth scanning for gRPC server/client patterns.
6
+ * Includes `.proto` when the grammar is available.
7
+ */
8
+ export declare const GRPC_SCAN_GLOB: string;
9
+ /**
10
+ * Whether the tree-sitter proto plugin is available. The orchestrator
11
+ * uses this to decide between the tree-sitter path and the fallback
12
+ * manual parser for `.proto` files.
13
+ */
14
+ export declare const hasProtoPlugin: boolean;
15
+ /**
16
+ * Return the gRPC plugin registered for the given file's extension,
17
+ * or `undefined` if the extension is not registered.
18
+ */
19
+ export declare function getPluginForFile(rel: string): GrpcLanguagePlugin | undefined;
@@ -0,0 +1,46 @@
1
+ import * as path from 'node:path';
2
+ import { GO_GRPC_PLUGIN } from './go.js';
3
+ import { JAVA_GRPC_PLUGIN } from './java.js';
4
+ import { PYTHON_GRPC_PLUGIN } from './python.js';
5
+ import { JAVASCRIPT_GRPC_PLUGIN, TYPESCRIPT_GRPC_PLUGIN, TSX_GRPC_PLUGIN } from './node.js';
6
+ import { PROTO_GRPC_PLUGIN } from './proto.js';
7
+ export { PROTO_GRPC_PLUGIN, extractPackageFromTree } from './proto.js';
8
+ /**
9
+ * File-extension → gRPC language plugin registry. Mirrors the shape
10
+ * of `http-patterns/index.ts` and `topic-patterns/index.ts`.
11
+ *
12
+ * `.proto` files are registered only when `tree-sitter-proto` is
13
+ * available (it's an optionalDependency). When absent, the orchestrator
14
+ * falls back to the built-in manual proto parser.
15
+ */
16
+ const REGISTRY = {
17
+ '.go': GO_GRPC_PLUGIN,
18
+ '.java': JAVA_GRPC_PLUGIN,
19
+ '.py': PYTHON_GRPC_PLUGIN,
20
+ '.js': JAVASCRIPT_GRPC_PLUGIN,
21
+ '.jsx': JAVASCRIPT_GRPC_PLUGIN,
22
+ '.ts': TYPESCRIPT_GRPC_PLUGIN,
23
+ '.tsx': TSX_GRPC_PLUGIN,
24
+ ...(PROTO_GRPC_PLUGIN ? { '.proto': PROTO_GRPC_PLUGIN } : {}),
25
+ };
26
+ /**
27
+ * Glob for source files worth scanning for gRPC server/client patterns.
28
+ * Includes `.proto` when the grammar is available.
29
+ */
30
+ export const GRPC_SCAN_GLOB = PROTO_GRPC_PLUGIN
31
+ ? '**/*.{go,java,py,ts,tsx,js,jsx,proto}'
32
+ : '**/*.{go,java,py,ts,tsx,js,jsx}';
33
+ /**
34
+ * Whether the tree-sitter proto plugin is available. The orchestrator
35
+ * uses this to decide between the tree-sitter path and the fallback
36
+ * manual parser for `.proto` files.
37
+ */
38
+ export const hasProtoPlugin = PROTO_GRPC_PLUGIN !== null;
39
+ /**
40
+ * Return the gRPC plugin registered for the given file's extension,
41
+ * or `undefined` if the extension is not registered.
42
+ */
43
+ export function getPluginForFile(rel) {
44
+ const ext = path.extname(rel).toLowerCase();
45
+ return REGISTRY[ext];
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { GrpcLanguagePlugin } from './types.js';
2
+ export declare const JAVA_GRPC_PLUGIN: GrpcLanguagePlugin;
@@ -0,0 +1,173 @@
1
+ import Java from 'tree-sitter-java';
2
+ import { compilePatterns, runCompiledPatterns, } from '../tree-sitter-scanner.js';
3
+ /**
4
+ * Java gRPC plugin. Detects:
5
+ * - Provider: classes extending `XxxServiceGrpc.XxxServiceImplBase`
6
+ * (with or without a `@GrpcService` annotation; the annotation
7
+ * only affects confidence labelling in the original regex version
8
+ * — here we emit a single detection per class and pick the source
9
+ * label based on whether the annotation is present).
10
+ * - Consumer: `XxxServiceGrpc.newBlockingStub(ch)` /
11
+ * `XxxServiceGrpc.newStub(ch)` calls.
12
+ */
13
+ const IMPL_BASE_RE = /^(\w+)ImplBase$/;
14
+ const GRPC_SUFFIX_RE = /^(\w+)Grpc$/;
15
+ // Classes extending `ScopedType.ScopedType` where the inner name ends
16
+ // in ImplBase. Covers `XxxServiceGrpc.XxxServiceImplBase`.
17
+ // Note: tree-sitter-java's `scoped_type_identifier` exposes its two
18
+ // segments as positional `type_identifier` children, NOT as named
19
+ // `scope:`/`name:` fields. We match positionally here and rely on the
20
+ // grammar's left-to-right ordering: first child = outer, second = inner.
21
+ const SCOPED_IMPL_BASE_PATTERNS = compilePatterns({
22
+ name: 'java-grpc-scoped-impl-base',
23
+ language: Java,
24
+ patterns: [
25
+ {
26
+ meta: {},
27
+ query: `
28
+ (class_declaration
29
+ name: (identifier) @class_name
30
+ superclass: (superclass
31
+ (scoped_type_identifier
32
+ (type_identifier) @outer
33
+ (type_identifier) @inner (#match? @inner "ImplBase$")))) @class
34
+ `,
35
+ },
36
+ ],
37
+ });
38
+ // Classes extending a simple `XxxImplBase` identifier (no scope).
39
+ const PLAIN_IMPL_BASE_PATTERNS = compilePatterns({
40
+ name: 'java-grpc-plain-impl-base',
41
+ language: Java,
42
+ patterns: [
43
+ {
44
+ meta: {},
45
+ query: `
46
+ (class_declaration
47
+ name: (identifier) @class_name
48
+ superclass: (superclass
49
+ (type_identifier) @plain_type (#match? @plain_type "ImplBase$"))) @class
50
+ `,
51
+ },
52
+ ],
53
+ });
54
+ // gRPC stub factories: `XxxGrpc.newStub(ch)` / `XxxGrpc.newBlockingStub(ch)`.
55
+ const STUB_PATTERNS = compilePatterns({
56
+ name: 'java-grpc-stub',
57
+ language: Java,
58
+ patterns: [
59
+ {
60
+ meta: {},
61
+ query: `
62
+ (method_invocation
63
+ object: (identifier) @grpc_cls
64
+ name: (identifier) @method (#match? @method "^new(Blocking)?Stub$"))
65
+ `,
66
+ },
67
+ ],
68
+ });
69
+ /**
70
+ * Check whether a `class_declaration` node has a `@GrpcService`
71
+ * annotation in its modifiers list. In tree-sitter-java, class-level
72
+ * annotations live under `(class_declaration (modifiers (marker_annotation|annotation)))`.
73
+ */
74
+ function hasGrpcServiceAnnotation(classNode) {
75
+ for (let i = 0; i < classNode.namedChildCount; i++) {
76
+ const child = classNode.namedChild(i);
77
+ if (!child || child.type !== 'modifiers')
78
+ continue;
79
+ for (let j = 0; j < child.namedChildCount; j++) {
80
+ const mod = child.namedChild(j);
81
+ if (!mod)
82
+ continue;
83
+ if (mod.type !== 'marker_annotation' && mod.type !== 'annotation')
84
+ continue;
85
+ const nameNode = mod.childForFieldName('name');
86
+ if (nameNode?.text === 'GrpcService')
87
+ return true;
88
+ }
89
+ }
90
+ return false;
91
+ }
92
+ /**
93
+ * Given the inner type_identifier text like `AuthServiceImplBase`,
94
+ * return the service name (`AuthService`), or null if the text
95
+ * doesn't end in `ImplBase`.
96
+ */
97
+ function extractServiceFromImplBase(text) {
98
+ const m = IMPL_BASE_RE.exec(text);
99
+ if (!m)
100
+ return null;
101
+ // Strip a trailing `Grpc` on the service name too — the original
102
+ // regex replaces `Grpc$` on the extracted prefix.
103
+ return m[1].replace(/Grpc$/, '');
104
+ }
105
+ export const JAVA_GRPC_PLUGIN = {
106
+ name: 'java-grpc',
107
+ language: Java,
108
+ scan(tree) {
109
+ const out = [];
110
+ const emittedClassIds = new Set();
111
+ // ─── Providers: scoped form (`...Grpc.XxxImplBase`) ─────────────
112
+ for (const match of runCompiledPatterns(SCOPED_IMPL_BASE_PATTERNS, tree)) {
113
+ const classNode = match.captures.class;
114
+ const innerNode = match.captures.inner;
115
+ if (!classNode || !innerNode)
116
+ continue;
117
+ const serviceName = extractServiceFromImplBase(innerNode.text);
118
+ if (!serviceName)
119
+ continue;
120
+ emittedClassIds.add(classNode.id);
121
+ const annotated = hasGrpcServiceAnnotation(classNode);
122
+ out.push({
123
+ role: 'provider',
124
+ serviceName,
125
+ symbolName: serviceName,
126
+ source: annotated ? 'java_grpc_service' : 'java_impl_base',
127
+ confidenceWithProto: 0.8,
128
+ confidenceWithoutProto: 0.65,
129
+ });
130
+ }
131
+ // ─── Providers: plain form (`XxxImplBase`) ──────────────────────
132
+ for (const match of runCompiledPatterns(PLAIN_IMPL_BASE_PATTERNS, tree)) {
133
+ const classNode = match.captures.class;
134
+ const plainNode = match.captures.plain_type;
135
+ if (!classNode || !plainNode)
136
+ continue;
137
+ if (emittedClassIds.has(classNode.id))
138
+ continue;
139
+ const serviceName = extractServiceFromImplBase(plainNode.text);
140
+ if (!serviceName)
141
+ continue;
142
+ emittedClassIds.add(classNode.id);
143
+ const annotated = hasGrpcServiceAnnotation(classNode);
144
+ out.push({
145
+ role: 'provider',
146
+ serviceName,
147
+ symbolName: serviceName,
148
+ source: annotated ? 'java_grpc_service' : 'java_impl_base',
149
+ confidenceWithProto: 0.8,
150
+ confidenceWithoutProto: 0.65,
151
+ });
152
+ }
153
+ // ─── Consumers: `XxxGrpc.newBlockingStub(...)` / `newStub(...)` ─
154
+ for (const match of runCompiledPatterns(STUB_PATTERNS, tree)) {
155
+ const grpcClsNode = match.captures.grpc_cls;
156
+ if (!grpcClsNode)
157
+ continue;
158
+ const grpcMatch = GRPC_SUFFIX_RE.exec(grpcClsNode.text);
159
+ if (!grpcMatch)
160
+ continue;
161
+ const serviceName = grpcMatch[1];
162
+ out.push({
163
+ role: 'consumer',
164
+ serviceName,
165
+ symbolName: `${serviceName}Stub`,
166
+ source: 'java_stub',
167
+ confidenceWithProto: 0.75,
168
+ confidenceWithoutProto: 0.55,
169
+ });
170
+ }
171
+ return out;
172
+ },
173
+ };
@@ -0,0 +1,4 @@
1
+ import type { GrpcLanguagePlugin } from './types.js';
2
+ export declare const JAVASCRIPT_GRPC_PLUGIN: GrpcLanguagePlugin;
3
+ export declare const TYPESCRIPT_GRPC_PLUGIN: GrpcLanguagePlugin;
4
+ export declare const TSX_GRPC_PLUGIN: GrpcLanguagePlugin;
@@ -0,0 +1,290 @@
1
+ import JavaScript from 'tree-sitter-javascript';
2
+ import TypeScript from 'tree-sitter-typescript';
3
+ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
4
+ /**
5
+ * Node.js / TypeScript gRPC plugin family. Detects:
6
+ * - Provider: NestJS `@GrpcMethod('Service', 'Method')` decorators
7
+ * - Consumer: NestJS `@GrpcClient(...) readonly x!: XxxServiceClient`
8
+ * - Consumer: `client.getService<X>('AuthService')`
9
+ * - Consumer: `new XxxServiceClient(...)` (generated client constructor)
10
+ * - Consumer: `new foo.bar.Xxx(...)` when the file uses
11
+ * `loadPackageDefinition` (gRPC dynamic proto loader)
12
+ *
13
+ * As with the HTTP `node.ts`, pattern sources are defined once and
14
+ * compiled against three grammar variants (JS / TS / TSX) because
15
+ * `Parser.Query` is not portable across grammar objects.
16
+ */
17
+ const SERVICE_CLIENT_RE = /^(\w+Service)Client$/;
18
+ const CAPITALIZED_SERVICE_RE = /^[A-Z]\w+$/;
19
+ // @GrpcMethod('Service', 'Method')
20
+ const GRPC_METHOD_SPEC = {
21
+ meta: {},
22
+ query: `
23
+ (decorator
24
+ (call_expression
25
+ function: (identifier) @dec (#eq? @dec "GrpcMethod")
26
+ arguments: (arguments
27
+ . [(string) (template_string)] @service
28
+ . [(string) (template_string)] @method)))
29
+ `,
30
+ };
31
+ // @GrpcClient(...) standalone decorator — the plugin walks to the next
32
+ // sibling (a field definition) to read its type annotation.
33
+ const GRPC_CLIENT_SPEC = {
34
+ meta: {},
35
+ query: `
36
+ (decorator
37
+ (call_expression
38
+ function: (identifier) @dec (#eq? @dec "GrpcClient"))) @grpc_client_decorator
39
+ `,
40
+ };
41
+ // `.getService<X>('AuthService')` / `.getService('AuthService')`
42
+ const GET_SERVICE_SPEC = {
43
+ meta: {},
44
+ query: `
45
+ (call_expression
46
+ function: (member_expression
47
+ property: (property_identifier) @method (#eq? @method "getService"))
48
+ arguments: (arguments . [(string) (template_string)] @service))
49
+ `,
50
+ };
51
+ // `new XxxServiceClient(...)` — bare identifier constructor.
52
+ const NEW_SIMPLE_CTOR_SPEC = {
53
+ meta: {},
54
+ query: `
55
+ (new_expression
56
+ constructor: (identifier) @ctor)
57
+ `,
58
+ };
59
+ // `new foo.bar.XxxService(...)` — qualified constructor.
60
+ const NEW_QUALIFIED_CTOR_SPEC = {
61
+ meta: {},
62
+ query: `
63
+ (new_expression
64
+ constructor: (member_expression
65
+ property: (property_identifier) @ctor))
66
+ `,
67
+ };
68
+ // Detect whether the file uses `loadPackageDefinition` (gRPC dynamic
69
+ // proto loader). Matches either a bare call or an `obj.loadPackageDefinition(...)`
70
+ // call. Plugin gates the qualified-constructor consumer on this —
71
+ // structural check avoids materializing `tree.rootNode.text` for every file.
72
+ const LOAD_PACKAGE_DEFINITION_SPEC = {
73
+ meta: {},
74
+ query: `
75
+ (call_expression
76
+ function: [
77
+ (identifier) @fn (#eq? @fn "loadPackageDefinition")
78
+ (member_expression property: (property_identifier) @fn (#eq? @fn "loadPackageDefinition"))
79
+ ])
80
+ `,
81
+ };
82
+ function compileBundle(language, name) {
83
+ const mk = (spec, suffix) => compilePatterns({
84
+ name: `${name}-${suffix}`,
85
+ language,
86
+ patterns: [spec],
87
+ });
88
+ return {
89
+ grpcMethod: mk(GRPC_METHOD_SPEC, 'grpc-method'),
90
+ grpcClient: mk(GRPC_CLIENT_SPEC, 'grpc-client'),
91
+ getService: mk(GET_SERVICE_SPEC, 'get-service'),
92
+ newSimpleCtor: mk(NEW_SIMPLE_CTOR_SPEC, 'new-simple-ctor'),
93
+ newQualifiedCtor: mk(NEW_QUALIFIED_CTOR_SPEC, 'new-qualified-ctor'),
94
+ loadPackageDefinition: mk(LOAD_PACKAGE_DEFINITION_SPEC, 'load-package-definition'),
95
+ };
96
+ }
97
+ const JAVASCRIPT_BUNDLE = compileBundle(JavaScript, 'javascript-grpc');
98
+ const TYPESCRIPT_BUNDLE = compileBundle(TypeScript.typescript, 'typescript-grpc');
99
+ const TSX_BUNDLE = compileBundle(TypeScript.tsx, 'tsx-grpc');
100
+ /**
101
+ * Given a `@GrpcClient(...)` decorator node, find the type annotation
102
+ * text of the field it decorates (e.g. `AuthServiceClient`).
103
+ *
104
+ * In tree-sitter-typescript, decorators on class fields can appear in
105
+ * two configurations:
106
+ * - As a CHILD of `public_field_definition` alongside the field's
107
+ * type annotation (the common case for NestJS `@GrpcClient`).
108
+ * - As a SIBLING of the field in `class_body` (for method
109
+ * decorators, but kept for resilience against grammar variants).
110
+ * We walk the parent container and search for a type annotation.
111
+ */
112
+ function resolveGrpcClientFieldType(decoratorNode) {
113
+ const parent = decoratorNode.parent;
114
+ if (!parent)
115
+ return null;
116
+ // Case 1: decorator is a child of the field definition — search
117
+ // the parent itself (which is the field definition) for a
118
+ // type_annotation child.
119
+ if (parent.type === 'public_field_definition' || parent.type.endsWith('field_definition')) {
120
+ return findFirstTypeAnnotationText(parent);
121
+ }
122
+ // Case 2: decorator is a sibling of the field in a class_body — walk
123
+ // forward through subsequent siblings until we find a node containing
124
+ // a type annotation.
125
+ for (let i = 0; i < parent.namedChildCount; i++) {
126
+ const child = parent.namedChild(i);
127
+ if (child && child.id === decoratorNode.id) {
128
+ for (let j = i + 1; j < parent.namedChildCount; j++) {
129
+ const next = parent.namedChild(j);
130
+ if (!next)
131
+ continue;
132
+ if (next.type === 'decorator')
133
+ continue;
134
+ const typeText = findFirstTypeAnnotationText(next);
135
+ if (typeText)
136
+ return typeText;
137
+ return null;
138
+ }
139
+ return null;
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+ /**
145
+ * Recursively search `node` for the first `type_annotation` child and
146
+ * return the text of its inner `type_identifier`, or null. Handles
147
+ * both `public_field_definition` and its variants.
148
+ */
149
+ function findFirstTypeAnnotationText(node) {
150
+ if (node.type === 'type_annotation') {
151
+ for (let i = 0; i < node.namedChildCount; i++) {
152
+ const child = node.namedChild(i);
153
+ if (!child)
154
+ continue;
155
+ if (child.type === 'type_identifier')
156
+ return child.text;
157
+ }
158
+ return null;
159
+ }
160
+ for (let i = 0; i < node.namedChildCount; i++) {
161
+ const child = node.namedChild(i);
162
+ if (!child)
163
+ continue;
164
+ const found = findFirstTypeAnnotationText(child);
165
+ if (found)
166
+ return found;
167
+ }
168
+ return null;
169
+ }
170
+ function scanBundle(bundle, tree) {
171
+ const out = [];
172
+ // ─── Provider: @GrpcMethod('Service', 'Method') ──────────────────
173
+ for (const match of runCompiledPatterns(bundle.grpcMethod, tree)) {
174
+ const svcNode = match.captures.service;
175
+ const methodNode = match.captures.method;
176
+ if (!svcNode || !methodNode)
177
+ continue;
178
+ const svc = unquoteLiteral(svcNode.text);
179
+ const mth = unquoteLiteral(methodNode.text);
180
+ if (!svc || !mth)
181
+ continue;
182
+ out.push({
183
+ role: 'provider',
184
+ serviceName: svc,
185
+ symbolName: `${svc}.${mth}`,
186
+ source: 'ts_grpc_method',
187
+ methodName: mth,
188
+ // @GrpcMethod hard-coded confidence 0.8 in the original code
189
+ // regardless of whether the proto map has a match.
190
+ confidenceWithProto: 0.8,
191
+ confidenceWithoutProto: 0.8,
192
+ });
193
+ }
194
+ // ─── Consumer: @GrpcClient() field with XxxServiceClient type ────
195
+ for (const match of runCompiledPatterns(bundle.grpcClient, tree)) {
196
+ const decoratorNode = match.captures.grpc_client_decorator;
197
+ if (!decoratorNode)
198
+ continue;
199
+ const typeText = resolveGrpcClientFieldType(decoratorNode);
200
+ if (!typeText)
201
+ continue;
202
+ const svcMatch = SERVICE_CLIENT_RE.exec(typeText);
203
+ if (!svcMatch)
204
+ continue;
205
+ const serviceName = svcMatch[1];
206
+ out.push({
207
+ role: 'consumer',
208
+ serviceName,
209
+ symbolName: `${serviceName}Client`,
210
+ source: 'ts_grpc_client_decorator',
211
+ confidenceWithProto: 0.75,
212
+ confidenceWithoutProto: 0.55,
213
+ });
214
+ }
215
+ // ─── Consumer: client.getService<X>('Service') ───────────────────
216
+ for (const match of runCompiledPatterns(bundle.getService, tree)) {
217
+ const svcNode = match.captures.service;
218
+ if (!svcNode)
219
+ continue;
220
+ const svc = unquoteLiteral(svcNode.text);
221
+ if (!svc)
222
+ continue;
223
+ out.push({
224
+ role: 'consumer',
225
+ serviceName: svc,
226
+ symbolName: `${svc}Client`,
227
+ source: 'ts_client_grpc_get_service',
228
+ confidenceWithProto: 0.75,
229
+ confidenceWithoutProto: 0.55,
230
+ });
231
+ }
232
+ // ─── Consumer: new XxxServiceClient(...) ─────────────────────────
233
+ for (const match of runCompiledPatterns(bundle.newSimpleCtor, tree)) {
234
+ const ctorNode = match.captures.ctor;
235
+ if (!ctorNode)
236
+ continue;
237
+ const svcMatch = SERVICE_CLIENT_RE.exec(ctorNode.text);
238
+ if (!svcMatch)
239
+ continue;
240
+ const serviceName = svcMatch[1];
241
+ out.push({
242
+ role: 'consumer',
243
+ serviceName,
244
+ symbolName: `${serviceName}Client`,
245
+ source: 'ts_generated_client',
246
+ confidenceWithProto: 0.75,
247
+ confidenceWithoutProto: 0.55,
248
+ });
249
+ }
250
+ // ─── Consumer: loadPackageDefinition dynamic proto loader ────────
251
+ // Only emit when the file uses loadPackageDefinition, otherwise a
252
+ // generic `new foo.bar.Something()` in unrelated code would falsely
253
+ // register as a gRPC consumer. Check structurally via a dedicated
254
+ // query — avoids materializing `tree.rootNode.text` for the whole
255
+ // file (expensive on large files).
256
+ const usesLoadPackage = runCompiledPatterns(bundle.loadPackageDefinition, tree).length > 0;
257
+ if (usesLoadPackage) {
258
+ for (const match of runCompiledPatterns(bundle.newQualifiedCtor, tree)) {
259
+ const ctorNode = match.captures.ctor;
260
+ if (!ctorNode)
261
+ continue;
262
+ if (!CAPITALIZED_SERVICE_RE.test(ctorNode.text))
263
+ continue;
264
+ out.push({
265
+ role: 'consumer',
266
+ serviceName: ctorNode.text,
267
+ symbolName: `${ctorNode.text}Client`,
268
+ source: 'ts_load_package_definition',
269
+ confidenceWithProto: 0.75,
270
+ confidenceWithoutProto: 0.55,
271
+ });
272
+ }
273
+ }
274
+ return out;
275
+ }
276
+ export const JAVASCRIPT_GRPC_PLUGIN = {
277
+ name: 'javascript-grpc',
278
+ language: JavaScript,
279
+ scan: (tree) => scanBundle(JAVASCRIPT_BUNDLE, tree),
280
+ };
281
+ export const TYPESCRIPT_GRPC_PLUGIN = {
282
+ name: 'typescript-grpc',
283
+ language: TypeScript.typescript,
284
+ scan: (tree) => scanBundle(TYPESCRIPT_BUNDLE, tree),
285
+ };
286
+ export const TSX_GRPC_PLUGIN = {
287
+ name: 'tsx-grpc',
288
+ language: TypeScript.tsx,
289
+ scan: (tree) => scanBundle(TSX_BUNDLE, tree),
290
+ };
@@ -0,0 +1,9 @@
1
+ import type { GrpcLanguagePlugin } from './types.js';
2
+ /**
3
+ * The proto plugin, or `null` if tree-sitter-proto is not available.
4
+ * The orchestrator checks this at import time and decides whether to
5
+ * use the tree-sitter path or the fallback manual parser.
6
+ */
7
+ export declare const PROTO_GRPC_PLUGIN: GrpcLanguagePlugin | null;
8
+ /** The package declaration text from a proto file's tree. */
9
+ export declare function extractPackageFromTree(tree: import('tree-sitter').Tree): string;
@@ -0,0 +1,134 @@
1
+ import { createRequire } from 'node:module';
2
+ import { compilePatterns, runCompiledPatterns, } from '../tree-sitter-scanner.js';
3
+ /**
4
+ * Protobuf (.proto) tree-sitter plugin for gRPC contract extraction.
5
+ *
6
+ * Uses `tree-sitter-proto` (coder3101/tree-sitter-proto) as an
7
+ * optionalDependency — if the grammar is not installed (e.g. native
8
+ * compilation failed on an unusual platform), the plugin exports
9
+ * `null` and the orchestrator falls back to the existing manual
10
+ * string-sanitizing parser.
11
+ *
12
+ * The grammar is vendored in `vendor/tree-sitter-proto/` with
13
+ * parser.c regenerated against tree-sitter-cli 0.24 (ABI version 14)
14
+ * so it is compatible with the project's tree-sitter 0.25 runtime.
15
+ */
16
+ const _require = createRequire(import.meta.url);
17
+ let ProtoGrammar = null;
18
+ try {
19
+ ProtoGrammar = _require('tree-sitter-proto');
20
+ }
21
+ catch {
22
+ // Grammar not installed — PROTO_GRPC_PLUGIN will be null.
23
+ }
24
+ let PACKAGE_PATTERNS = null;
25
+ let SERVICE_PATTERNS = null;
26
+ if (ProtoGrammar) {
27
+ try {
28
+ // Validate that the grammar actually loads end-to-end: compile queries
29
+ // AND parse + walk a trivial proto file. tree-sitter's internal
30
+ // `initializeLanguageNodeClasses` can fail with a TDZ error in some
31
+ // test runners (vitest forks) when SyntaxNode isn't fully initialized
32
+ // yet. Catching that here ensures `PROTO_GRPC_PLUGIN` stays null and
33
+ // the orchestrator falls back to the manual parser.
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ const _Parser = _require('tree-sitter');
36
+ // Smoke-test: parse + setLanguage to verify the grammar is
37
+ // end-to-end compatible with this tree-sitter runtime.
38
+ const _testParser = new _Parser();
39
+ _testParser.setLanguage(ProtoGrammar);
40
+ _testParser.parse('service X { rpc Y (R) returns (R); }');
41
+ PACKAGE_PATTERNS = compilePatterns({
42
+ name: 'proto-package',
43
+ language: ProtoGrammar,
44
+ patterns: [
45
+ {
46
+ meta: {},
47
+ query: `(package (full_ident) @pkg)`,
48
+ },
49
+ ],
50
+ });
51
+ SERVICE_PATTERNS = compilePatterns({
52
+ name: 'proto-service',
53
+ language: ProtoGrammar,
54
+ patterns: [
55
+ {
56
+ meta: {},
57
+ query: `
58
+ (service
59
+ (service_name) @service_name
60
+ (rpc
61
+ (rpc_name) @rpc_name))
62
+ `,
63
+ },
64
+ ],
65
+ });
66
+ }
67
+ catch {
68
+ // Compilation failed (grammar ABI mismatch?) — fall back to null.
69
+ PACKAGE_PATTERNS = null;
70
+ SERVICE_PATTERNS = null;
71
+ ProtoGrammar = null;
72
+ }
73
+ }
74
+ function buildPlugin() {
75
+ if (!ProtoGrammar || !PACKAGE_PATTERNS || !SERVICE_PATTERNS)
76
+ return null;
77
+ const pkgPatterns = PACKAGE_PATTERNS;
78
+ const svcPatterns = SERVICE_PATTERNS;
79
+ return {
80
+ name: 'proto-grpc',
81
+ language: ProtoGrammar,
82
+ scan(tree) {
83
+ const out = [];
84
+ // Extract `package` declaration (first match wins).
85
+ let pkg = '';
86
+ for (const match of runCompiledPatterns(pkgPatterns, tree)) {
87
+ const pkgNode = match.captures.pkg;
88
+ if (pkgNode) {
89
+ pkg = pkgNode.text;
90
+ break;
91
+ }
92
+ }
93
+ // Extract `service → rpc` pairs. The query returns one match per
94
+ // (service, rpc) combination thanks to the nested structure.
95
+ for (const match of runCompiledPatterns(svcPatterns, tree)) {
96
+ const serviceNode = match.captures.service_name;
97
+ const rpcNode = match.captures.rpc_name;
98
+ if (!serviceNode || !rpcNode)
99
+ continue;
100
+ const serviceName = serviceNode.text;
101
+ const methodName = rpcNode.text;
102
+ out.push({
103
+ role: 'provider',
104
+ serviceName,
105
+ symbolName: `${serviceName}.${methodName}`,
106
+ source: 'proto',
107
+ methodName,
108
+ // Proto definitions are the canonical source of truth — always
109
+ // high confidence regardless of cross-referencing.
110
+ confidenceWithProto: 0.85,
111
+ confidenceWithoutProto: 0.85,
112
+ });
113
+ }
114
+ return out;
115
+ },
116
+ };
117
+ }
118
+ /**
119
+ * The proto plugin, or `null` if tree-sitter-proto is not available.
120
+ * The orchestrator checks this at import time and decides whether to
121
+ * use the tree-sitter path or the fallback manual parser.
122
+ */
123
+ export const PROTO_GRPC_PLUGIN = buildPlugin();
124
+ /** The package declaration text from a proto file's tree. */
125
+ export function extractPackageFromTree(tree) {
126
+ if (!PACKAGE_PATTERNS)
127
+ return '';
128
+ for (const match of runCompiledPatterns(PACKAGE_PATTERNS, tree)) {
129
+ const pkgNode = match.captures.pkg;
130
+ if (pkgNode)
131
+ return pkgNode.text;
132
+ }
133
+ return '';
134
+ }
@@ -0,0 +1,2 @@
1
+ import type { GrpcLanguagePlugin } from './types.js';
2
+ export declare const PYTHON_GRPC_PLUGIN: GrpcLanguagePlugin;