docsgov 0.1.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.
Files changed (159) hide show
  1. package/README.md +242 -0
  2. package/dist/apispec/apispec.js +401 -0
  3. package/dist/apispec/apispec.test.js +444 -0
  4. package/dist/apispec/errors.js +17 -0
  5. package/dist/apispec/index.js +2 -0
  6. package/dist/check/doclinks.js +167 -0
  7. package/dist/check/index.js +8 -0
  8. package/dist/check/run.js +391 -0
  9. package/dist/check/run.test.js +513 -0
  10. package/dist/check/suggest.js +134 -0
  11. package/dist/check/suggest.test.js +92 -0
  12. package/dist/check/tokens.js +125 -0
  13. package/dist/cmd/main.js +330 -0
  14. package/dist/cmd/main.test.js +422 -0
  15. package/dist/codeq/cache.js +71 -0
  16. package/dist/codeq/cache.test.js +67 -0
  17. package/dist/codeq/errors.js +52 -0
  18. package/dist/codeq/grammars/tree-sitter-go.wasm +0 -0
  19. package/dist/codeq/grammars/tree-sitter-java.wasm +0 -0
  20. package/dist/codeq/grammars/tree-sitter-javascript.wasm +0 -0
  21. package/dist/codeq/grammars/tree-sitter-tsx.wasm +0 -0
  22. package/dist/codeq/grammars/tree-sitter-typescript.wasm +0 -0
  23. package/dist/codeq/index.js +11 -0
  24. package/dist/codeq/resolve.test.js +109 -0
  25. package/dist/codeq/resolver.js +128 -0
  26. package/dist/codeq/resolver.test.js +124 -0
  27. package/dist/codeq/resolvers/go.js +242 -0
  28. package/dist/codeq/resolvers/go.test.js +143 -0
  29. package/dist/codeq/resolvers/java.js +349 -0
  30. package/dist/codeq/resolvers/java.test.js +138 -0
  31. package/dist/codeq/resolvers/java_queries.js +63 -0
  32. package/dist/codeq/resolvers/javascript.js +412 -0
  33. package/dist/codeq/resolvers/javascript.test.js +125 -0
  34. package/dist/codeq/resolvers/javascript_queries.js +46 -0
  35. package/dist/codeq/resolvers/typescript.js +366 -0
  36. package/dist/codeq/resolvers/typescript.test.js +180 -0
  37. package/dist/codeq/resolvers/typescript_queries.js +78 -0
  38. package/dist/codeq/signature.js +50 -0
  39. package/dist/codeq/signature.test.js +50 -0
  40. package/dist/codeq/suggest.js +96 -0
  41. package/dist/codeq/treesitter.js +122 -0
  42. package/dist/codeq/treesitter.test.js +118 -0
  43. package/dist/config/config.js +74 -0
  44. package/dist/config/config.test.js +98 -0
  45. package/dist/config/fs.js +116 -0
  46. package/dist/config/glob.js +82 -0
  47. package/dist/config/glob.test.js +61 -0
  48. package/dist/config/index.js +4 -0
  49. package/dist/dedup/analyzer/analyzer.js +533 -0
  50. package/dist/dedup/analyzer/analyzer.test.js +530 -0
  51. package/dist/dedup/analyzer/canonical.js +74 -0
  52. package/dist/dedup/analyzer/canonical.test.js +70 -0
  53. package/dist/dedup/analyzer/cosine_clusters.js +169 -0
  54. package/dist/dedup/analyzer/cosine_clusters.test.js +131 -0
  55. package/dist/dedup/analyzer/distinctive.js +85 -0
  56. package/dist/dedup/analyzer/distinctive.test.js +49 -0
  57. package/dist/dedup/analyzer/exact_clusters.js +63 -0
  58. package/dist/dedup/analyzer/exact_clusters.test.js +81 -0
  59. package/dist/dedup/analyzer/index.js +14 -0
  60. package/dist/dedup/analyzer/multiplicity.js +110 -0
  61. package/dist/dedup/analyzer/multiplicity.test.js +123 -0
  62. package/dist/dedup/analyzer/order.js +22 -0
  63. package/dist/dedup/analyzer/partial_overlaps.js +65 -0
  64. package/dist/dedup/analyzer/partial_overlaps.test.js +161 -0
  65. package/dist/dedup/analyzer/preview.js +84 -0
  66. package/dist/dedup/analyzer/preview.test.js +46 -0
  67. package/dist/dedup/analyzer/safety.js +27 -0
  68. package/dist/dedup/analyzer/safety.test.js +39 -0
  69. package/dist/dedup/config.js +18 -0
  70. package/dist/dedup/configload.js +299 -0
  71. package/dist/dedup/configload.test.js +410 -0
  72. package/dist/dedup/dedup.index.test.js +203 -0
  73. package/dist/dedup/dedup.js +143 -0
  74. package/dist/dedup/dedup.test.js +212 -0
  75. package/dist/dedup/dedupcfg/config.js +112 -0
  76. package/dist/dedup/dedupcfg/config.test.js +70 -0
  77. package/dist/dedup/dedupcfg/index.js +1 -0
  78. package/dist/dedup/deduptypes/index.js +1 -0
  79. package/dist/dedup/deduptypes/types.js +9 -0
  80. package/dist/dedup/deduptypes/types.test.js +34 -0
  81. package/dist/dedup/embedder/cache.js +23 -0
  82. package/dist/dedup/embedder/cache.test.js +50 -0
  83. package/dist/dedup/embedder/constants.js +10 -0
  84. package/dist/dedup/embedder/embedder.js +76 -0
  85. package/dist/dedup/embedder/embedder.mock.test.js +128 -0
  86. package/dist/dedup/embedder/embedder.test.js +96 -0
  87. package/dist/dedup/embedder/errors.js +20 -0
  88. package/dist/dedup/embedder/errors.test.js +35 -0
  89. package/dist/dedup/embedder/index.js +4 -0
  90. package/dist/dedup/embedder/session.js +78 -0
  91. package/dist/dedup/embedder/session.test.js +172 -0
  92. package/dist/dedup/gitignore.js +97 -0
  93. package/dist/dedup/gitignore.test.js +98 -0
  94. package/dist/dedup/index.js +11 -0
  95. package/dist/dedup/indexdb/errors.js +48 -0
  96. package/dist/dedup/indexdb/index.js +6 -0
  97. package/dist/dedup/indexdb/indexdb.js +302 -0
  98. package/dist/dedup/indexdb/indexdb.test.js +739 -0
  99. package/dist/dedup/indexdb/load.js +110 -0
  100. package/dist/dedup/indexdb/migrations.js +58 -0
  101. package/dist/dedup/indexdb/schema.js +83 -0
  102. package/dist/dedup/indexer/index.js +9 -0
  103. package/dist/dedup/indexer/indexer.js +501 -0
  104. package/dist/dedup/indexer/indexer.test.js +510 -0
  105. package/dist/dedup/indexer/links.js +89 -0
  106. package/dist/dedup/mdsection/anchor.js +60 -0
  107. package/dist/dedup/mdsection/anchor.test.js +39 -0
  108. package/dist/dedup/mdsection/blocks.js +409 -0
  109. package/dist/dedup/mdsection/blocks.test.js +359 -0
  110. package/dist/dedup/mdsection/index.js +4 -0
  111. package/dist/dedup/mdsection/parse.js +21 -0
  112. package/dist/dedup/mdsection/section.js +234 -0
  113. package/dist/dedup/mdsection/section.test.js +221 -0
  114. package/dist/dedup/report/floatfmt.js +71 -0
  115. package/dist/dedup/report/floatfmt.test.js +42 -0
  116. package/dist/dedup/report/index.js +8 -0
  117. package/dist/dedup/report/quote.js +77 -0
  118. package/dist/dedup/report/quote.test.js +67 -0
  119. package/dist/dedup/report/text.js +251 -0
  120. package/dist/dedup/report/text.test.js +420 -0
  121. package/dist/dedup/report_types.js +8 -0
  122. package/dist/dedup/sectionid/index.js +1 -0
  123. package/dist/dedup/sectionid/sectionid.js +16 -0
  124. package/dist/dedup/sectionid/sectionid.test.js +49 -0
  125. package/dist/guard/api/errors.js +12 -0
  126. package/dist/guard/api/index.js +2 -0
  127. package/dist/guard/api/parser.js +81 -0
  128. package/dist/guard/api/parser.test.js +58 -0
  129. package/dist/guard/api/types.js +1 -0
  130. package/dist/guard/code/errors.js +16 -0
  131. package/dist/guard/code/index.js +2 -0
  132. package/dist/guard/code/parser.js +54 -0
  133. package/dist/guard/code/parser.test.js +111 -0
  134. package/dist/guard/code/types.js +6 -0
  135. package/dist/index.js +1 -0
  136. package/dist/index.test.js +5 -0
  137. package/dist/repo/boundary.js +92 -0
  138. package/dist/repo/boundary.test.js +65 -0
  139. package/dist/repo/errors.js +56 -0
  140. package/dist/repo/errors.test.js +85 -0
  141. package/dist/repo/exists.test.js +72 -0
  142. package/dist/repo/filename.js +46 -0
  143. package/dist/repo/filename.test.js +39 -0
  144. package/dist/repo/fs.js +53 -0
  145. package/dist/repo/index.js +7 -0
  146. package/dist/repo/overlay.js +36 -0
  147. package/dist/repo/overlay.test.js +80 -0
  148. package/dist/repo/repo.js +353 -0
  149. package/dist/repo/repo.test.js +255 -0
  150. package/dist/repo/testutil.js +27 -0
  151. package/dist/repo/write.test.js +125 -0
  152. package/dist/report/color.js +73 -0
  153. package/dist/report/index.js +1 -0
  154. package/dist/report/report.js +112 -0
  155. package/dist/report/report.test.js +368 -0
  156. package/dist/violation/index.js +1 -0
  157. package/dist/violation/types.js +22 -0
  158. package/dist/violation/types.test.js +70 -0
  159. package/package.json +48 -0
@@ -0,0 +1,143 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { ParseFailedError } from "../errors.js";
3
+ import { loadGrammar } from "../treesitter.js";
4
+ import { createGoResolver } from "./go.js";
5
+ // These tests port the behaviour-encoding cases from internal/codeq/go_lang_test.go.
6
+ //
7
+ // WHY: The facet queries (symbol/member/param) and collectGoParamTypes are
8
+ // hand-authored tree-sitter patterns + a parameter walk. A malformed query, a
9
+ // wrong capture name, or a broken param walk would make the resolver silently
10
+ // return false for every lookup — so the code-guard would report "symbol absent"
11
+ // even when it exists, giving false positives on every future guard run. The
12
+ // parse-failure path must THROW ParseFailedError (not return false) so callers
13
+ // distinguish "could not check" from "genuinely missing".
14
+ //
15
+ // The Go test uses a DefaultResolver over an fs.FS; here the per-language
16
+ // resolver is sync and takes the source string directly (the I/O split recorded
17
+ // in resolver.ts). File-not-found is the dispatch layer's concern, NOT the
18
+ // resolver's, so the ErrFileNotFound case from go_lang_test.go is intentionally
19
+ // not ported here.
20
+ // CodeRef requires non-optional Symbol/Member/Param strings; this fills the
21
+ // empties so each test states only the facets it exercises.
22
+ function ref(partial) {
23
+ return {
24
+ Symbol: "",
25
+ Member: "",
26
+ Param: "",
27
+ ...partial,
28
+ };
29
+ }
30
+ describe("go resolver", () => {
31
+ let resolver;
32
+ // The grammar loads in milliseconds; no env-gating needed.
33
+ beforeAll(async () => {
34
+ const go = await loadGrammar("go");
35
+ resolver = createGoResolver(go);
36
+ });
37
+ // Helper: resolve against an inline source sample. `path` only feeds the
38
+ // ParseFailedError message; it is not used for I/O.
39
+ const resolve = (source, r) => resolver.resolve(source, r.Path, r);
40
+ // --- Symbols: func / type / var / const (TestGoResolverSymbols) ---
41
+ describe("top-level symbols", () => {
42
+ const src = "package pkg\n" +
43
+ "func Bar(){}\n" +
44
+ "type Baz struct{}\n" +
45
+ "var Qux = 1\n" +
46
+ "const Quux = 2\n";
47
+ // WHY: if the resolver misses a declaration KIND (e.g. var or const), a ref
48
+ // to it is silently reported absent even though it exists. Lock all four.
49
+ it.each(["Bar", "Baz", "Qux", "Quux"])("finds top-level declaration %s", (sym) => {
50
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: sym }))).toBe(true);
51
+ });
52
+ it("returns false (not an error) for an absent symbol", () => {
53
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Absent" }))).toBe(false);
54
+ });
55
+ });
56
+ // --- Members and params (TestGoResolverFacets) ---
57
+ describe("members and params", () => {
58
+ const src = "package pkg\n" +
59
+ "type Baz struct { Field int }\n" +
60
+ "func (b Baz) Method(x int, y string) error { return nil }\n";
61
+ it("resolves a struct field as a member", () => {
62
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Baz", Member: "Field" }))).toBe(true);
63
+ });
64
+ it("resolves a value-receiver method as a member", () => {
65
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Baz", Member: "Method" }))).toBe(true);
66
+ });
67
+ it("returns false for an absent member", () => {
68
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Baz", Member: "NoSuch" }))).toBe(false);
69
+ });
70
+ it.each(["x", "y"])("resolves parameter %s of a method", (p) => {
71
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Method", Param: p }))).toBe(true);
72
+ });
73
+ it("returns false for an absent parameter", () => {
74
+ expect(resolve(src, ref({ Path: "pkg/foo.go", Symbol: "Method", Param: "z" }))).toBe(false);
75
+ });
76
+ });
77
+ // --- Pointer-receiver members do NOT resolve (preserve Go behaviour) ---
78
+ // WHY: the member query binds the receiver type via (type_identifier), which
79
+ // matches only a value receiver "(b Baz)". A pointer receiver "(b *Baz)"
80
+ // parses as (pointer_type ...) and is intentionally NOT matched. This is a
81
+ // load-bearing limitation recorded in the port memory; lock it so a future
82
+ // query "fix" that starts matching pointer receivers fails loudly.
83
+ it("does NOT resolve a pointer-receiver method as a member", () => {
84
+ const src = "package pkg\n" +
85
+ "type Baz struct{}\n" +
86
+ "func (b *Baz) PtrMethod() {}\n";
87
+ expect(resolve(src, ref({ Path: "pkg/p.go", Symbol: "Baz", Member: "PtrMethod" }))).toBe(false);
88
+ });
89
+ // --- Parse failure throws ParseFailedError (TestGoResolverFacets bad.go) ---
90
+ it("throws ParseFailedError on invalid Go source", () => {
91
+ const bad = "package pkg\nfunc {{{INVALID";
92
+ expect(() => resolve(bad, ref({ Path: "pkg/bad.go", Symbol: "X" }))).toThrow(ParseFailedError);
93
+ });
94
+ // --- Signature overload disambiguation (TestGoResolver_Signature) ---
95
+ describe("signature disambiguation", () => {
96
+ const src = "package pkg\n" +
97
+ "type Request struct{}\n" +
98
+ "type Ctl struct{}\n" +
99
+ 'func (c Ctl) Get(names []string, id int, req Request) string { return "" }\n';
100
+ it("matches a member with the correct param-type signature", () => {
101
+ expect(resolve(src, ref({
102
+ Path: "pkg/c.go",
103
+ Symbol: "Ctl",
104
+ Member: "Get",
105
+ Signature: ["[]string", "int", "Request"],
106
+ }))).toBe(true);
107
+ });
108
+ it("does NOT match a member with a wrong signature", () => {
109
+ expect(resolve(src, ref({
110
+ Path: "pkg/c.go",
111
+ Symbol: "Ctl",
112
+ Member: "Get",
113
+ Signature: ["int"],
114
+ }))).toBe(false);
115
+ });
116
+ });
117
+ // --- Signature edge cases (TestGoResolver_SignatureEdgeCases) ---
118
+ // WHY: collectGoParamTypes must handle variadic_parameter_declaration nodes
119
+ // (xs ...string → []string) or the effective arity is wrong; grouped params
120
+ // (a, b int) must expand per name count; a zero-arg method must match the
121
+ // empty (zero-arg) signature.
122
+ describe("signature edge cases", () => {
123
+ const src = "package pkg\n" +
124
+ "type Ctl struct{}\n" +
125
+ 'func (c Ctl) Var(a int, xs ...string) string { return "" }\n' +
126
+ 'func (c Ctl) G(a, b int, s string) string { return "" }\n' +
127
+ 'func (c Ctl) Zero() string { return "" }\n';
128
+ const resolveSig = (member, sig) => resolve(src, ref({ Path: "pkg/e.go", Symbol: "Ctl", Member: member, Signature: sig }));
129
+ it("variadic: (int, []string) matches; collapsed (int) does not", () => {
130
+ expect(resolveSig("Var", ["int", "[]string"])).toBe(true);
131
+ // Arity is 2; the collapsed form that drops the variadic must not match.
132
+ expect(resolveSig("Var", ["int"])).toBe(false);
133
+ });
134
+ it("grouped: (int, int, string) matches; collapsed (int, string) does not", () => {
135
+ expect(resolveSig("G", ["int", "int", "string"])).toBe(true);
136
+ // Arity is 3; treating (a, b int) as one type must not match.
137
+ expect(resolveSig("G", ["int", "string"])).toBe(false);
138
+ });
139
+ it("zero-arg: empty signature matches a no-param method", () => {
140
+ expect(resolveSig("Zero", [])).toBe(true);
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,349 @@
1
+ // java.ts ports internal/codeq/java_lang.go: the per-language Resolver for Java
2
+ // source. It is the largest grammar's resolver — it disambiguates method and
3
+ // constructor overloads by parameter type (the Signature facet of a CodeRef).
4
+ //
5
+ // Faithful port of one *_lang.go Resolve body MINUS the cache.Read +
6
+ // ErrFileNotFound prelude (the dispatch layer owns file I/O — see resolver.ts).
7
+ // The dispatch precedence in resolve() mirrors the Go Resolve switch EXACTLY:
8
+ //
9
+ // Member && Param -> findMemberParam
10
+ // Member -> findMember
11
+ // Signature && Param -> findConstructorParam (bare class + sig + param)
12
+ // Signature -> findConstructor (bare class + sig)
13
+ // Param -> findParam
14
+ // default -> findSymbol
15
+ //
16
+ // Constructor dispatch: when Member is empty and Signature is non-nil, the ref
17
+ // denotes a constructor overload (#Class(sig)) rather than the type itself.
18
+ // #Class(sig)@params.name selects a parameter of that constructor overload.
19
+ // #Class with no signature still resolves the type via findSymbol.
20
+ import { ParseFailedError } from "../errors.js";
21
+ import { signaturesMatch } from "../signature.js";
22
+ import { collectCapture, collectOwned, refKind, suggestFromExtractors, } from "../suggest.js";
23
+ import { Parser, compileQuery, nodeText, parseTree, runQuery, } from "../treesitter.js";
24
+ import { CONSTRUCTOR_DECL, MEMBER, MEMBER_DECL, PARAM, SYMBOL, } from "./java_queries.js";
25
+ /**
26
+ * createJavaResolver builds the Java Resolver from an already-loaded Java
27
+ * Language. It compiles and caches the grammar's queries eagerly (the analogue
28
+ * of Go's package-level mustJavaQuery vars / newJavaResolver). compileQuery
29
+ * throws on a bad query — surfacing a hard-coded-query programming error at
30
+ * construction time, exactly as Go's mustJavaQuery panics.
31
+ */
32
+ export function createJavaResolver(language) {
33
+ const symbolQuery = compileQuery(language, SYMBOL);
34
+ const memberQuery = compileQuery(language, MEMBER);
35
+ const memberDeclQuery = compileQuery(language, MEMBER_DECL);
36
+ const paramQuery = compileQuery(language, PARAM);
37
+ const constructorDeclQuery = compileQuery(language, CONSTRUCTOR_DECL);
38
+ return new JavaResolver(language, symbolQuery, memberQuery, memberDeclQuery, paramQuery, constructorDeclQuery);
39
+ }
40
+ class JavaResolver {
41
+ language;
42
+ symbolQuery;
43
+ memberQuery;
44
+ memberDeclQuery;
45
+ paramQuery;
46
+ constructorDeclQuery;
47
+ constructor(language, symbolQuery, memberQuery, memberDeclQuery, paramQuery, constructorDeclQuery) {
48
+ this.language = language;
49
+ this.symbolQuery = symbolQuery;
50
+ this.memberQuery = memberQuery;
51
+ this.memberDeclQuery = memberDeclQuery;
52
+ this.paramQuery = paramQuery;
53
+ this.constructorDeclQuery = constructorDeclQuery;
54
+ }
55
+ /**
56
+ * resolve parses `source` and dispatches on ref facets exactly as Go's
57
+ * javaResolver.Resolve switch does. Throws ParseFailedError(path) when the
58
+ * parse tree has ERROR nodes (Go's ErrParseFailed). `path` is carried only to
59
+ * build that error — not used for I/O.
60
+ */
61
+ resolve(source, path, ref) {
62
+ // Per-call parser — cheap to create, must not be shared (mirrors Go's
63
+ // per-call sitter.NewParser()).
64
+ const parser = new Parser();
65
+ parser.setLanguage(this.language);
66
+ const root = parseTree(parser, source);
67
+ // Invalid Java produces ERROR nodes, not a parse() throw.
68
+ if (root.hasError) {
69
+ throw new ParseFailedError(path);
70
+ }
71
+ const hasSig = ref.Signature !== undefined;
72
+ if (ref.Member !== "" && ref.Param !== "") {
73
+ return this.findMemberParam(root, ref.Symbol, ref.Member, ref.Param, ref.Signature);
74
+ }
75
+ if (ref.Member !== "") {
76
+ return this.findMember(root, ref.Symbol, ref.Member, ref.Signature);
77
+ }
78
+ if (hasSig && ref.Param !== "") {
79
+ // bare class symbol + signature + param → a parameter of a constructor overload
80
+ return this.findConstructorParam(root, ref.Symbol, ref.Param, ref.Signature);
81
+ }
82
+ if (hasSig) {
83
+ // bare class symbol + signature → a constructor overload (not the type)
84
+ return this.findConstructor(root, ref.Symbol, ref.Signature);
85
+ }
86
+ if (ref.Param !== "") {
87
+ return this.findParam(root, ref.Symbol, ref.Param);
88
+ }
89
+ return this.findSymbol(root, ref.Symbol);
90
+ }
91
+ /**
92
+ * suggest lists same-kind candidate names for a not-found ref (best-effort).
93
+ * A parse error yields no candidates rather than throwing — suggestions never
94
+ * gate a violation. Members come from the flat MEMBER query (fields, methods,
95
+ * enum constants); params from the PARAM query keyed by method name (matching
96
+ * findParam, which ignores the owning class for a bare Param ref).
97
+ */
98
+ suggest(source, _path, ref) {
99
+ const parser = new Parser();
100
+ parser.setLanguage(this.language);
101
+ const root = parseTree(parser, source);
102
+ if (root.hasError) {
103
+ return { kind: refKind(ref), candidates: [] };
104
+ }
105
+ return suggestFromExtractors(root, ref, {
106
+ symbolNames: (r) => collectCapture(r, this.symbolQuery, "name"),
107
+ memberNames: (r, owner) => collectOwned(r, this.memberQuery, owner, "owner", "name"),
108
+ paramNames: (r, fn) => collectOwned(r, this.paramQuery, fn, "fn", "param"),
109
+ });
110
+ }
111
+ /**
112
+ * findSymbol searches for a type declaration (class/interface/enum/record)
113
+ * with the given name. The query is unanchored, so nested types resolve too.
114
+ */
115
+ findSymbol(root, name) {
116
+ for (const m of runQuery(root, this.symbolQuery)) {
117
+ const n = m.captures["name"];
118
+ if (n !== undefined && nodeText(n) === name) {
119
+ return true;
120
+ }
121
+ }
122
+ return false;
123
+ }
124
+ /**
125
+ * findMember searches for a field, method, or enum constant named `member` on
126
+ * the type named `symbol`. When `sig` is given, only method overloads whose
127
+ * parameter types satisfy signaturesMatch are considered; fields/enum
128
+ * constants are matched by name only (and only via the name-only path, since
129
+ * they have no signature).
130
+ */
131
+ findMember(root, symbol, member, sig) {
132
+ if (sig === undefined) {
133
+ // Name-only: the flat member query (fields, methods, enum constants).
134
+ for (const m of runQuery(root, this.memberQuery)) {
135
+ const owner = textOf(m.captures["owner"]);
136
+ const name = textOf(m.captures["name"]);
137
+ if (owner === symbol && name === member) {
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+ // Signature-filtered: iterate method declarations and check param types.
144
+ for (const m of runQuery(root, this.memberDeclQuery)) {
145
+ const owner = textOf(m.captures["owner"]);
146
+ const mname = textOf(m.captures["mname"]);
147
+ const mdecl = m.captures["mdecl"];
148
+ if (owner === symbol &&
149
+ mname === member &&
150
+ mdecl !== undefined &&
151
+ signaturesMatch(sig, collectParamTypes(mdecl))) {
152
+ return true;
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * findMemberParam searches for a parameter named `paramName` on a method named
159
+ * `member` of the type named `symbol`. When `sig` is given, only the overload
160
+ * whose parameter types satisfy signaturesMatch is considered.
161
+ */
162
+ findMemberParam(root, symbol, member, paramName, sig) {
163
+ if (sig === undefined) {
164
+ // Name-only: any overload with the matching param name.
165
+ for (const m of runQuery(root, this.paramQuery)) {
166
+ const owner = textOf(m.captures["owner"]);
167
+ const fn = textOf(m.captures["fn"]);
168
+ const param = textOf(m.captures["param"]);
169
+ if (owner === symbol && fn === member && param === paramName) {
170
+ return true;
171
+ }
172
+ }
173
+ return false;
174
+ }
175
+ // Signature-filtered: find the specific overload, then look for the param.
176
+ for (const m of runQuery(root, this.memberDeclQuery)) {
177
+ const owner = textOf(m.captures["owner"]);
178
+ const mname = textOf(m.captures["mname"]);
179
+ const mdecl = m.captures["mdecl"];
180
+ if (owner !== symbol || mname !== member || mdecl === undefined) {
181
+ continue;
182
+ }
183
+ if (!signaturesMatch(sig, collectParamTypes(mdecl))) {
184
+ continue;
185
+ }
186
+ // Correct overload — now check for the param name.
187
+ if (paramExistsInMethod(mdecl, paramName)) {
188
+ return true;
189
+ }
190
+ }
191
+ return false;
192
+ }
193
+ /**
194
+ * findConstructor searches for a constructor of the type named `symbol` whose
195
+ * parameter types satisfy signaturesMatch(sig, declared types). Reuses
196
+ * collectParamTypes (declaration-kind-agnostic) on the constructor_declaration
197
+ * node captured by the constructor-decl query.
198
+ */
199
+ findConstructor(root, symbol, sig) {
200
+ for (const m of runQuery(root, this.constructorDeclQuery)) {
201
+ const owner = textOf(m.captures["owner"]);
202
+ const cdecl = m.captures["cdecl"];
203
+ if (owner === symbol &&
204
+ cdecl !== undefined &&
205
+ signaturesMatch(sig, collectParamTypes(cdecl))) {
206
+ return true;
207
+ }
208
+ }
209
+ return false;
210
+ }
211
+ /**
212
+ * findConstructorParam searches for a parameter named `paramName` in a
213
+ * constructor of the type named `symbol` whose parameter types satisfy
214
+ * signaturesMatch(sig, declared types).
215
+ */
216
+ findConstructorParam(root, symbol, paramName, sig) {
217
+ for (const m of runQuery(root, this.constructorDeclQuery)) {
218
+ const owner = textOf(m.captures["owner"]);
219
+ const cdecl = m.captures["cdecl"];
220
+ if (owner !== symbol || cdecl === undefined) {
221
+ continue;
222
+ }
223
+ if (!signaturesMatch(sig, collectParamTypes(cdecl))) {
224
+ continue;
225
+ }
226
+ if (paramExistsInMethod(cdecl, paramName)) {
227
+ return true;
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+ /**
233
+ * findParam searches for a parameter named `paramName` on any method named
234
+ * `symbol` (the rare case where only Param is set without Member). Signature
235
+ * is ignored here, mirroring Go's findParam (which takes but discards sig).
236
+ */
237
+ findParam(root, symbol, paramName) {
238
+ for (const m of runQuery(root, this.paramQuery)) {
239
+ const fn = textOf(m.captures["fn"]);
240
+ const param = textOf(m.captures["param"]);
241
+ if (fn === symbol && param === paramName) {
242
+ return true;
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+ }
248
+ /** textOf returns nodeText(n) or "" when the capture is absent (mirrors Go's
249
+ * zero-value string for a missing capture). */
250
+ function textOf(n) {
251
+ return n === undefined ? "" : nodeText(n);
252
+ }
253
+ /**
254
+ * spreadParamType returns the type node text for a spread_parameter (varargs)
255
+ * node. The grammar is:
256
+ * spread_parameter: [modifiers] _type "..." variable_declarator
257
+ * — the type sits immediately before the anonymous "..." token. Using child(0)
258
+ * is fragile because an optional leading modifiers node shifts the index;
259
+ * instead we scan for the "..." sibling and take its predecessor.
260
+ */
261
+ function spreadParamType(sp) {
262
+ for (let i = 1; i < sp.childCount; i++) {
263
+ if (sp.child(i)?.type === "...") {
264
+ const prev = sp.child(i - 1);
265
+ return prev === null ? "" : nodeText(prev);
266
+ }
267
+ }
268
+ return "";
269
+ }
270
+ /**
271
+ * collectParamTypes walks a method or constructor declaration node's
272
+ * formal_parameters and returns the declared parameter types in document order.
273
+ * formal_parameter nodes use the named "type" field (safe even with modifiers);
274
+ * spread_parameter (varargs) nodes are handled by spreadParamType — the "[]"
275
+ * suffix is appended so normalizeType can unify them with array declarations
276
+ * (T...≡T[]).
277
+ */
278
+ function collectParamTypes(methodDecl) {
279
+ const formalParams = formalParametersOf(methodDecl);
280
+ if (formalParams === null) {
281
+ return [];
282
+ }
283
+ const types = [];
284
+ for (let i = 0; i < formalParams.childCount; i++) {
285
+ const c = formalParams.child(i);
286
+ if (c === null) {
287
+ continue;
288
+ }
289
+ if (c.type === "formal_parameter") {
290
+ const tn = c.childForFieldName("type");
291
+ if (tn !== null) {
292
+ types.push(nodeText(tn));
293
+ }
294
+ }
295
+ else if (c.type === "spread_parameter") {
296
+ const t = spreadParamType(c);
297
+ if (t !== "") {
298
+ types.push(t + "[]");
299
+ }
300
+ }
301
+ }
302
+ return types;
303
+ }
304
+ /**
305
+ * paramExistsInMethod reports whether a formal_parameter named `paramName`
306
+ * exists in a method or constructor declaration node's formal_parameters list.
307
+ */
308
+ function paramExistsInMethod(methodDecl, paramName) {
309
+ const formalParams = formalParametersOf(methodDecl);
310
+ if (formalParams === null) {
311
+ return false;
312
+ }
313
+ for (let i = 0; i < formalParams.childCount; i++) {
314
+ const c = formalParams.child(i);
315
+ if (c === null) {
316
+ continue;
317
+ }
318
+ if (c.type === "formal_parameter") {
319
+ const nn = c.childForFieldName("name");
320
+ if (nn !== null && nodeText(nn) === paramName) {
321
+ return true;
322
+ }
323
+ }
324
+ else if (c.type === "spread_parameter") {
325
+ // spread_parameter: last child is variable_declarator containing name.
326
+ if (c.childCount > 0) {
327
+ const vd = c.child(c.childCount - 1);
328
+ if (vd !== null && vd.type === "variable_declarator" && vd.childCount > 0) {
329
+ const nameNode = vd.child(0);
330
+ if (nameNode !== null && nodeText(nameNode) === paramName) {
331
+ return true;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ }
337
+ return false;
338
+ }
339
+ /** formalParametersOf returns the first formal_parameters child of a method or
340
+ * constructor declaration, or null when absent (mirrors Go's scan + nil). */
341
+ function formalParametersOf(decl) {
342
+ for (let i = 0; i < decl.childCount; i++) {
343
+ const c = decl.child(i);
344
+ if (c !== null && c.type === "formal_parameters") {
345
+ return c;
346
+ }
347
+ }
348
+ return null;
349
+ }
@@ -0,0 +1,138 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { ParseFailedError } from "../errors.js";
3
+ import { loadGrammar } from "../treesitter.js";
4
+ import { createJavaResolver } from "./java.js";
5
+ // WHY: the Java resolver is the largest grammar's resolver and the only one that
6
+ // disambiguates overloads by parameter type. These tests port the
7
+ // behavior-encoding cases from internal/codeq/java_lang_test.go so the TS port
8
+ // resolves identically: symbol/member/param existence, the ParseFailedError
9
+ // trigger, overload disambiguation by Signature (including varargs-with-modifier
10
+ // T...≡T[]), constructor overloads (#Class(sig) vs the bare type), and nested
11
+ // enum constants. The grammar loads in ms, so no env-gating is needed.
12
+ // Mirrors the Go test fixture (internal/codeq/java_lang_test.go `javaSrc`): a
13
+ // class C with overloaded constructors and an overloaded `get` method (including
14
+ // a varargs overload with a `final` modifier), plus a nested enum.
15
+ const JAVA_SRC = `package com.example;
16
+ public class C {
17
+ public C() {}
18
+ public C(Integer id) {}
19
+ public C(String[] names, AbRequest req) {}
20
+ public String get() { return ""; }
21
+ public String get(Integer id) { return ""; }
22
+ public String get(String[] names, Integer id, AbRequest req) { return ""; }
23
+ public String get(final String... tags) { return ""; }
24
+ public enum Status { PENDING, COMPLETED, CANCELLED }
25
+ }
26
+ `;
27
+ let resolver;
28
+ const PATH = "C.java";
29
+ // A {{code:…}} CodeRef defaults: Signature undefined = name-only (no overload
30
+ // filter); Member/Param "" = facet absent. This builder keeps each case to the
31
+ // facets it exercises, matching the Go `code.CodeRef{…}` struct literals.
32
+ function ref(partial) {
33
+ return {
34
+ Path: PATH,
35
+ Symbol: "",
36
+ Member: "",
37
+ Param: "",
38
+ ...partial,
39
+ };
40
+ }
41
+ beforeAll(async () => {
42
+ const language = await loadGrammar("java");
43
+ resolver = createJavaResolver(language);
44
+ });
45
+ describe("java resolver — symbol/member/param", () => {
46
+ // Each case states what facet combination it pins and why it must (not) resolve.
47
+ it.each([
48
+ ["class type resolves", { Symbol: "C" }, true],
49
+ ["method member resolves", { Symbol: "C", Member: "get" }, true],
50
+ ["absent member does not resolve", { Symbol: "C", Member: "nope" }, false],
51
+ [
52
+ "named param of a method resolves",
53
+ { Symbol: "C", Member: "get", Param: "id" },
54
+ true,
55
+ ],
56
+ [
57
+ "absent param does not resolve",
58
+ { Symbol: "C", Member: "get", Param: "zzz" },
59
+ false,
60
+ ],
61
+ ])("%s", (_name, partial, want) => {
62
+ expect(resolver.resolve(JAVA_SRC, PATH, ref(partial))).toBe(want);
63
+ });
64
+ });
65
+ describe("java resolver — parse failure", () => {
66
+ it("throws ParseFailedError on source with ERROR nodes", () => {
67
+ // WHY: invalid Java yields ERROR nodes (not a parse() throw); the resolver
68
+ // maps rootNode.hasError → ParseFailedError carrying the path. This is the
69
+ // ErrParseFailed contract the dispatch layer propagates.
70
+ expect(() => resolver.resolve("class {{{", "bad.java", ref({ Symbol: "X" }))).toThrow(ParseFailedError);
71
+ });
72
+ });
73
+ describe("java resolver — method overload disambiguation by signature", () => {
74
+ // Member="get" + Signature selects a specific overload by parameter types.
75
+ // Signature [] means the zero-arg overload "()"; undefined would be name-only.
76
+ const mref = (sig, param) => ref({ Symbol: "C", Member: "get", Signature: sig, Param: param });
77
+ it.each([
78
+ ["3-arg overload matches", ["String[]", "Integer", "AbRequest"], "", true],
79
+ ["zero-arg overload matches", [], "", true],
80
+ ["no overload with these types", ["String[]", "String[]"], "", false],
81
+ [
82
+ "param of the specific overload resolves",
83
+ ["String[]", "Integer", "AbRequest"],
84
+ "req",
85
+ true,
86
+ ],
87
+ ["param absent in the selected overload", ["Integer"], "req", false],
88
+ // WHY: a spread_parameter with a leading modifier ("final String... tags")
89
+ // puts modifiers at child(0); the type must be located as the child before
90
+ // the "..." token, then normalized T...≡T[] to match "String[]".
91
+ ["varargs overload matches String[]", ["String[]"], "", true],
92
+ ["varargs param tags resolves", ["String[]"], "tags", true],
93
+ ])("%s", (_name, sig, param, want) => {
94
+ expect(resolver.resolve(JAVA_SRC, PATH, mref(sig, param))).toBe(want);
95
+ });
96
+ });
97
+ describe("java resolver — constructor overloads", () => {
98
+ // Bare class Symbol + Signature (no Member) denotes a constructor overload
99
+ // #C(sig), distinct from the type itself.
100
+ const cref = (sig, param) => ref({ Symbol: "C", Signature: sig, Param: param });
101
+ it.each([
102
+ ["no-arg constructor", [], "", true],
103
+ ["1-arg constructor", ["Integer"], "", true],
104
+ ["2-arg constructor", ["String[]", "AbRequest"], "", true],
105
+ ["no constructor with these types", ["String[]"], "", false],
106
+ [
107
+ "param of a constructor overload resolves",
108
+ ["String[]", "AbRequest"],
109
+ "req",
110
+ true,
111
+ ],
112
+ ["param absent in the constructor", ["Integer"], "zzz", false],
113
+ ])("%s", (_name, sig, param, want) => {
114
+ expect(resolver.resolve(JAVA_SRC, PATH, cref(sig, param))).toBe(want);
115
+ });
116
+ it("bare class name with no signature still resolves the TYPE", () => {
117
+ // Regression from Go: #C (no signature) must hit findSymbol, not the
118
+ // constructor path — the type resolves even when no constructor matches.
119
+ expect(resolver.resolve(JAVA_SRC, PATH, ref({ Symbol: "C" }))).toBe(true);
120
+ });
121
+ });
122
+ describe("java resolver — nested enum and its constants", () => {
123
+ it.each([
124
+ ["nested enum type resolves", { Symbol: "Status" }, true],
125
+ [
126
+ "enum constant resolves",
127
+ { Symbol: "Status", Member: "COMPLETED" },
128
+ true,
129
+ ],
130
+ [
131
+ "absent enum constant does not resolve",
132
+ { Symbol: "Status", Member: "SHIPPED" },
133
+ false,
134
+ ],
135
+ ])("%s", (_name, partial, want) => {
136
+ expect(resolver.resolve(JAVA_SRC, PATH, ref(partial))).toBe(want);
137
+ });
138
+ });