Skip to content

Commit bb53430

Browse files
committed
usingResolver part 2
1 parent 0acb5bc commit bb53430

File tree

4 files changed

+176
-72
lines changed

4 files changed

+176
-72
lines changed

packages/cli/src/languagePlugins/csharp/dependencyResolver/index.ts

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,66 +9,10 @@ import { csharpParser } from "../../../helpers/treeSitter/parsers";
99

1010
export class CSharpDependencyResolver {
1111
parser: Parser = csharpParser;
12-
private nsTree: NamespaceNode;
12+
private nsMapper: CSharpNamespaceMapper;
1313

1414
constructor(nsMapper: CSharpNamespaceMapper) {
15-
this.nsTree = nsMapper.buildNamespaceTree();
16-
}
17-
18-
#findNamespaceInTree(
19-
tree: NamespaceNode,
20-
namespaceName: string,
21-
): NamespaceNode | null {
22-
if (namespaceName.includes(".")) {
23-
const parts = namespaceName.split(".");
24-
const simpleNamespaceName = parts[0];
25-
const rest = parts.slice(1).join(".");
26-
27-
const namespace = tree.childrenNamespaces.find(
28-
(ns) => ns.name === simpleNamespaceName,
29-
);
30-
if (namespace) {
31-
return this.#findNamespaceInTree(namespace, rest);
32-
}
33-
}
34-
35-
return (
36-
tree.childrenNamespaces.find((ns) => ns.name === namespaceName) ?? null
37-
);
38-
}
39-
40-
#findClassInTree(tree: NamespaceNode, className: string): SymbolNode | null {
41-
// Management of qualified names
42-
if (className.includes(".")) {
43-
const parts = className.split(".");
44-
const namespaceName = parts.slice(0, -1).join(".");
45-
const simpleClassName = parts[parts.length - 1];
46-
47-
const namespace = this.#findNamespaceInTree(tree, namespaceName);
48-
if (namespace) {
49-
return (
50-
namespace.exports.find((cls) => cls.name === simpleClassName) ?? null
51-
);
52-
} else {
53-
// In case the qualifier is actually not a namespace but a class
54-
// Check OuterInnerClass in the tests.
55-
return this.#findClassInTree(this.nsTree, namespaceName);
56-
}
57-
}
58-
// Find the class in the current node's classes.
59-
if (tree.exports.some((cls) => cls.name === className)) {
60-
return tree.exports.find((cls) => cls.name === className) ?? null;
61-
}
62-
63-
// Recursively search in children namespaces.
64-
for (const namespace of tree.childrenNamespaces) {
65-
const found = this.#findClassInTree(namespace, className);
66-
if (found) {
67-
return found;
68-
}
69-
}
70-
71-
return null;
15+
this.nsMapper = nsMapper;
7216
}
7317

7418
// Gets the classes used in a file.
@@ -100,14 +44,14 @@ export class CSharpDependencyResolver {
10044
.captures(node)
10145
.map((capture) => {
10246
const className = capture.node.text;
103-
return this.#findClassInTree(namespaceTree, className);
47+
return this.nsMapper.findClassInTree(namespaceTree, className);
10448
})
10549
.filter((cls): cls is SymbolNode => cls !== null);
10650
}
10751

10852
// Gets the classes used in a file.
10953
getDependenciesFromFile(file: File): SymbolNode[] {
110-
return this.#getCalledClasses(file.rootNode, this.nsTree)
54+
return this.#getCalledClasses(file.rootNode, this.nsMapper.nsTree)
11155
.filter((cls) => cls.filepath !== "")
11256
.filter(
11357
(cls, index, self) =>
@@ -118,7 +62,7 @@ export class CSharpDependencyResolver {
11862
}
11963

12064
getDependenciesFromNode(node: Parser.SyntaxNode): SymbolNode[] {
121-
return this.#getCalledClasses(node, this.nsTree)
65+
return this.#getCalledClasses(node, this.nsMapper.nsTree)
12266
.filter((cls) => cls.filepath !== "")
12367
.filter(
12468
(cls, index, self) =>

packages/cli/src/languagePlugins/csharp/namespaceMapper/index.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ export interface SymbolNode {
1717
export class CSharpNamespaceMapper {
1818
#files: Map<string, { path: string; rootNode: Parser.SyntaxNode }>;
1919
#nsResolver: CSharpNamespaceResolver;
20+
nsTree: NamespaceNode;
2021

2122
constructor(
2223
files: Map<string, { path: string; rootNode: Parser.SyntaxNode }>,
2324
) {
2425
this.#files = files;
2526
this.#nsResolver = new CSharpNamespaceResolver();
27+
this.nsTree = this.buildNamespaceTree();
2628
}
2729

2830
getFile(key: string) {
@@ -102,4 +104,72 @@ export class CSharpNamespaceMapper {
102104
// I don't understand why, but the root element is always empty
103105
return namespaceTree;
104106
}
107+
108+
findNamespaceInTree(
109+
tree: NamespaceNode,
110+
namespaceName: string,
111+
): NamespaceNode | null {
112+
if (namespaceName.includes(".")) {
113+
const parts = namespaceName.split(".");
114+
const simpleNamespaceName = parts[0];
115+
const rest = parts.slice(1).join(".");
116+
117+
const namespace = tree.childrenNamespaces.find(
118+
(ns) => ns.name === simpleNamespaceName,
119+
);
120+
if (namespace) {
121+
return this.findNamespaceInTree(namespace, rest);
122+
}
123+
}
124+
125+
return (
126+
tree.childrenNamespaces.find((ns) => ns.name === namespaceName) ?? null
127+
);
128+
}
129+
130+
findClassInTree(tree: NamespaceNode, className: string): SymbolNode | null {
131+
// Management of qualified names
132+
if (className.includes(".")) {
133+
const parts = className.split(".");
134+
const namespaceName = parts.slice(0, -1).join(".");
135+
const simpleClassName = parts[parts.length - 1];
136+
137+
const namespace = this.findNamespaceInTree(tree, namespaceName);
138+
if (namespace) {
139+
return (
140+
namespace.exports.find((cls) => cls.name === simpleClassName) ?? null
141+
);
142+
} else {
143+
// In case the qualifier is actually not a namespace but a class
144+
// Check OuterInnerClass in the tests.
145+
return this.findClassInTree(this.nsTree, namespaceName);
146+
}
147+
}
148+
// Find the class in the current node's classes.
149+
if (tree.exports.some((cls) => cls.name === className)) {
150+
return tree.exports.find((cls) => cls.name === className) ?? null;
151+
}
152+
153+
// Recursively search in children namespaces.
154+
for (const namespace of tree.childrenNamespaces) {
155+
const found = this.findClassInTree(namespace, className);
156+
if (found) {
157+
return found;
158+
}
159+
}
160+
161+
return null;
162+
}
163+
164+
findAnyInTree(
165+
tree: NamespaceNode,
166+
name: string,
167+
): NamespaceNode | SymbolNode | null {
168+
const namespace = this.findNamespaceInTree(tree, name);
169+
if (namespace) {
170+
return namespace;
171+
}
172+
173+
return this.findClassInTree(tree, name);
174+
}
105175
}

packages/cli/src/languagePlugins/csharp/usingResolver/index.test.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,79 @@ describe("UsingResolver", () => {
1515
const nsmapper = new CSharpNamespaceMapper(files);
1616
const resolver = new CSharpUsingResolver(nsmapper);
1717

18-
test("Should resolve all use cases of 'using'", () => {
18+
test("Directive simple parsing", () => {
1919
const usingDirectives = resolver.parseUsingDirectives("Usage.cs");
2020
expect(usingDirectives).toMatchObject([
2121
{
2222
type: GLOBAL_USING,
23-
import: "System.IO",
23+
idf: "System.IO",
2424
},
2525
{
2626
type: LOCAL_USING,
27-
import: "System",
27+
idf: "System",
2828
},
2929
{
3030
type: LOCAL_USING,
31-
import: "System.Collections.Generic",
31+
idf: "System.Collections.Generic",
3232
},
3333
{
3434
type: USING_STATIC,
35-
import: "System.Math",
35+
idf: "System.Math",
3636
},
3737
{
3838
type: USING_ALIAS,
39-
import: "MyApp.Models.User",
39+
idf: "MyApp.Models.User",
4040
alias: "Guy",
4141
},
4242
{
4343
type: USING_ALIAS,
44-
import: "HalfNamespace",
44+
idf: "HalfNamespace",
4545
alias: "Valve",
4646
},
4747
]);
4848
});
49+
test("Directive resolving", () => {
50+
const resolved = resolver.resolveUsingDirectives("Usage.cs");
51+
expect(resolved).toMatchObject({
52+
internal: [
53+
{
54+
usingtype: USING_ALIAS,
55+
alias: "Guy",
56+
symbol: {
57+
name: "User",
58+
type: "class",
59+
namespace: "MyApp.Models",
60+
filepath: "Models.cs",
61+
},
62+
},
63+
{
64+
usingtype: USING_ALIAS,
65+
alias: "Valve",
66+
symbol: {
67+
name: "HalfNamespace",
68+
exports: expect.any(Array),
69+
childrenNamespaces: expect.any(Array),
70+
},
71+
},
72+
],
73+
external: [
74+
{
75+
usingtype: GLOBAL_USING,
76+
name: "System.IO",
77+
},
78+
{
79+
usingtype: LOCAL_USING,
80+
name: "System",
81+
},
82+
{
83+
usingtype: LOCAL_USING,
84+
name: "System.Collections.Generic",
85+
},
86+
{
87+
usingtype: USING_STATIC,
88+
name: "System.Math",
89+
},
90+
],
91+
});
92+
});
4993
});

packages/cli/src/languagePlugins/csharp/usingResolver/index.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import Parser from "tree-sitter";
2-
import { CSharpNamespaceMapper } from "../namespaceMapper";
2+
import {
3+
CSharpNamespaceMapper,
4+
NamespaceNode,
5+
SymbolNode,
6+
} from "../namespaceMapper";
37

48
export const GLOBAL_USING = "global";
59
export const LOCAL_USING = "local";
@@ -15,10 +19,27 @@ export type UsingType =
1519
export interface UsingDirective {
1620
node: Parser.SyntaxNode;
1721
type: UsingType;
18-
import: string;
22+
idf: string;
1923
alias?: string;
2024
}
2125

26+
export interface InternalSymbol {
27+
usingtype: UsingType;
28+
alias?: string;
29+
symbol: SymbolNode | NamespaceNode;
30+
}
31+
32+
export interface ExternalSymbol {
33+
usingtype: UsingType;
34+
alias?: string;
35+
name: string;
36+
}
37+
38+
export interface ResolvedImports {
39+
internal: InternalSymbol[];
40+
external: ExternalSymbol[];
41+
}
42+
2243
export class CSharpUsingResolver {
2344
private nsMapper: CSharpNamespaceMapper;
2445
private usingDirectives: UsingDirective[] = [];
@@ -45,10 +66,10 @@ export class CSharpUsingResolver {
4566
(child.type === "identifier" || child.type === "qualified_name") &&
4667
child !== node.childForFieldName("name"),
4768
);
48-
const imprt = importNode ? importNode.text : "";
69+
const idf = importNode ? importNode.text : "";
4970
const aliasNode = node.childForFieldName("name");
5071
const alias = aliasNode ? aliasNode.text : undefined;
51-
return { node, type, import: imprt, alias };
72+
return { node, type, idf, alias };
5273
});
5374
return this.usingDirectives;
5475
}
@@ -66,4 +87,29 @@ export class CSharpUsingResolver {
6687
}
6788
return LOCAL_USING;
6889
}
90+
91+
private resolveUsingDirective(
92+
directive: UsingDirective,
93+
): InternalSymbol | ExternalSymbol {
94+
const { type, idf, alias } = directive;
95+
const symbol = this.nsMapper.findAnyInTree(this.nsMapper.nsTree, idf);
96+
if (symbol) {
97+
return { usingtype: type, alias, symbol };
98+
}
99+
return { usingtype: type, alias, name: idf };
100+
}
101+
102+
public resolveUsingDirectives(filepath: string): ResolvedImports {
103+
const internal: InternalSymbol[] = [];
104+
const external: ExternalSymbol[] = [];
105+
this.parseUsingDirectives(filepath).forEach((directive) => {
106+
const resolved = this.resolveUsingDirective(directive);
107+
if ("symbol" in resolved) {
108+
internal.push(resolved);
109+
} else {
110+
external.push(resolved);
111+
}
112+
});
113+
return { internal, external };
114+
}
69115
}

0 commit comments

Comments
 (0)