Skip to content

Commit 4ab9ef1

Browse files
committed
Merge branch 'feature/java-module' of github.com:nanoapi-io/napi into feature/java-module
2 parents a295a4e + 1739405 commit 4ab9ef1

12 files changed

Lines changed: 372 additions & 1 deletion

File tree

deno.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, test } from "@std/testing/bdd";
2+
import { expect } from "@std/expect";
3+
import { getJavaFilesMap } from "../testFiles/index.ts";
4+
import {
5+
BURGER,
6+
CONDIMENT,
7+
DOUBLEBURGER,
8+
FOOD,
9+
STEAK,
10+
} from "../testFiles/constants.ts";
11+
import { JavaPackageResolver } from "./index.ts";
12+
13+
describe("Java Package Resolver", () => {
14+
const files = getJavaFilesMap();
15+
const condiment = files.get(CONDIMENT)!;
16+
const food = files.get(FOOD)!;
17+
const steak = files.get(STEAK)!;
18+
const burger = files.get(BURGER)!;
19+
const doubleburger = files.get(DOUBLEBURGER)!;
20+
const packageResolver = new JavaPackageResolver();
21+
22+
test("parses Food.java", () => {
23+
const result = packageResolver.resolveFile(food);
24+
expect(result).toBeDefined();
25+
expect(result.path).toStrictEqual(FOOD);
26+
expect(result.package).toBe("io.nanoapi.testfiles.food");
27+
expect(result.imports.length).toBe(0);
28+
const symbol = result.symbol;
29+
expect(symbol).toBeDefined();
30+
expect(symbol.name).toBe("Food");
31+
expect(symbol.type).toBe("interface");
32+
expect(symbol.modifiers).toContain("public");
33+
expect(symbol.modifiers.length).toBe(1);
34+
expect(symbol.typeParamCount).toBe(0);
35+
expect(symbol.superclass).toBeUndefined();
36+
expect(symbol.interfaces.length).toBe(0);
37+
expect(symbol.children.length).toBe(0);
38+
});
39+
40+
test("parses Condiment.java", () => {
41+
const result = packageResolver.resolveFile(condiment);
42+
expect(result).toBeDefined();
43+
expect(result.path).toStrictEqual(CONDIMENT);
44+
expect(result.package).toBe("io.nanoapi.testfiles.food");
45+
expect(result.imports.length).toBe(0);
46+
const symbol = result.symbol;
47+
expect(symbol).toBeDefined();
48+
expect(symbol.name).toBe("Condiment");
49+
expect(symbol.type).toBe("enum");
50+
expect(symbol.modifiers).toContain("public");
51+
expect(symbol.modifiers.length).toBe(1);
52+
expect(symbol.typeParamCount).toBe(0);
53+
expect(symbol.superclass).toBeUndefined();
54+
expect(symbol.interfaces.length).toBe(0);
55+
expect(symbol.children.length).toBe(0);
56+
});
57+
58+
test("parses Steak.java", () => {
59+
const result = packageResolver.resolveFile(steak);
60+
expect(result).toBeDefined();
61+
expect(result.path).toStrictEqual(STEAK);
62+
expect(result.package).toBe("io.nanoapi.testfiles.food");
63+
expect(result.imports.length).toBe(0);
64+
const symbol = result.symbol;
65+
expect(symbol).toBeDefined();
66+
expect(symbol.name).toBe("Steak");
67+
expect(symbol.type).toBe("class");
68+
expect(symbol.modifiers).toContain("public");
69+
expect(symbol.modifiers.length).toBe(1);
70+
expect(symbol.typeParamCount).toBe(0);
71+
expect(symbol.superclass).toBeUndefined();
72+
expect(symbol.interfaces.length).toBe(1);
73+
expect(symbol.interfaces[0]).toBe("Food");
74+
expect(symbol.children.length).toBe(1);
75+
const child = symbol.children[0];
76+
expect(child).toBeDefined();
77+
expect(child.name).toBe("Tapeworm");
78+
expect(child.type).toBe("class");
79+
expect(child.modifiers).toContain("private");
80+
expect(child.modifiers).toContain("static");
81+
expect(child.modifiers.length).toBe(2);
82+
expect(child.typeParamCount).toBe(0);
83+
expect(child.superclass).toBeUndefined();
84+
expect(child.interfaces.length).toBe(0);
85+
expect(child.children.length).toBe(0);
86+
});
87+
88+
test("parses Burger.java", () => {
89+
const result = packageResolver.resolveFile(burger);
90+
expect(result).toBeDefined();
91+
expect(result.path).toStrictEqual(BURGER);
92+
expect(result.package).toBe("io.nanoapi.testfiles.food");
93+
expect(result.imports.length).toBe(2);
94+
const symbol = result.symbol;
95+
expect(symbol).toBeDefined();
96+
expect(symbol.name).toBe("Burger");
97+
expect(symbol.type).toBe("class");
98+
expect(symbol.typeParamCount).toBe(1);
99+
expect(symbol.superclass).toBeUndefined();
100+
expect(symbol.interfaces.length).toBe(1);
101+
});
102+
103+
test("parses DoubleBurger.java", () => {
104+
const result = packageResolver.resolveFile(doubleburger);
105+
expect(result).toBeDefined();
106+
expect(result.path).toStrictEqual(DOUBLEBURGER);
107+
expect(result.package).toBe("io.nanoapi.testfiles.food");
108+
const symbol = result.symbol;
109+
expect(symbol).toBeDefined();
110+
expect(symbol.name).toBe("DoubleBurger");
111+
expect(symbol.type).toBe("class");
112+
expect(symbol.typeParamCount).toBe(2);
113+
expect(symbol.superclass).toBeDefined();
114+
expect(symbol.superclass).toBe("Burger");
115+
expect(symbol.interfaces.length).toBe(0);
116+
});
117+
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type Parser from "tree-sitter";
2+
import type {
3+
ExportedSymbol,
4+
JavaFile,
5+
Modifier,
6+
SymbolType,
7+
} from "./types.ts";
8+
import { JAVA_PROGRAM_QUERY } from "./queries.ts";
9+
import { javaParser } from "../../../helpers/treeSitter/parsers.ts";
10+
11+
export class JavaPackageResolver {
12+
parser: Parser = javaParser;
13+
14+
resolveFile(file: {
15+
path: string;
16+
rootNode: Parser.SyntaxNode;
17+
}): JavaFile {
18+
const captures = JAVA_PROGRAM_QUERY.captures(file.rootNode);
19+
const packagename = captures.find((c) => c.name === "package")?.node.text ??
20+
"";
21+
const imports = captures.filter((c) => c.name === "import").map((c) =>
22+
c.node.text
23+
);
24+
const declaration = captures.find((c) =>
25+
!["package", "import"].includes(c.name)
26+
);
27+
if (!declaration) {
28+
throw Error(`No declaration found for ${file.path}`);
29+
}
30+
const symbol = this.#processDeclaration(
31+
declaration.node,
32+
declaration.name as SymbolType,
33+
);
34+
return {
35+
path: file.path,
36+
rootNode: file.rootNode,
37+
symbol,
38+
package: packagename,
39+
imports,
40+
};
41+
}
42+
43+
#processDeclaration(
44+
node: Parser.SyntaxNode,
45+
type: SymbolType,
46+
): ExportedSymbol {
47+
const modifiersNode = node.children.find((c) => c.type === "modifiers");
48+
const modifiers: Modifier[] = [];
49+
if (modifiersNode) {
50+
modifiers.push(...modifiersNode.children.map((c) => c.text as Modifier));
51+
}
52+
const idNode = node.childForFieldName("name")!;
53+
const name = idNode.text;
54+
const typeParams = node.childForFieldName("type_parameters");
55+
let typeParamCount = 0;
56+
if (typeParams) {
57+
typeParamCount = typeParams.namedChildren.length;
58+
}
59+
let superclass: string | undefined = undefined;
60+
const superclassNode = node.childForFieldName("superclass");
61+
if (superclassNode) {
62+
superclass = superclassNode.namedChildren[0].text;
63+
}
64+
const interfacesNode = node.childForFieldName("interfaces");
65+
const interfaces: string[] = [];
66+
if (interfacesNode) {
67+
interfaces.push(
68+
...interfacesNode.namedChildren[0].namedChildren.map((c) => c.text),
69+
);
70+
}
71+
const children: ExportedSymbol[] = [];
72+
const bodyNode = node.childForFieldName("body");
73+
if (bodyNode) {
74+
children.push(
75+
...bodyNode.children.filter((c) =>
76+
[
77+
"class_declaration",
78+
"interface_declaration",
79+
"enum_declaration",
80+
"record_declaration",
81+
"annotation_type_declaration",
82+
].includes(c.type)
83+
).map((c) =>
84+
this.#processDeclaration(c, c.type.split("_")[0] as SymbolType)
85+
),
86+
);
87+
}
88+
return {
89+
name,
90+
type,
91+
modifiers,
92+
typeParamCount,
93+
superclass,
94+
interfaces,
95+
children,
96+
node,
97+
idNode,
98+
};
99+
}
100+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Parser from "npm:tree-sitter";
2+
import { javaParser } from "../../../helpers/treeSitter/parsers.ts";
3+
4+
export const JAVA_PROGRAM_QUERY = new Parser.Query(
5+
javaParser.getLanguage(),
6+
`(program [
7+
(package_declaration (_) @package)
8+
(import_declaration (_) @import)
9+
(class_declaration) @class
10+
(interface_declaration) @interface
11+
(enum_declaration) @enum
12+
(record_declaration) @record
13+
(annotation_type_declaration) @annotation
14+
])`,
15+
);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type Parser from "tree-sitter";
2+
3+
export const JAVA_PUBLIC_MODIFIER = "public";
4+
export const JAVA_PRIVATE_MODIFIER = "private";
5+
export const JAVA_PROTECTED_MODIFIER = "protected";
6+
export const JAVA_ABSTRACT_MODIFIER = "abstract";
7+
export const JAVA_FINAL_MODIFIER = "final";
8+
export const JAVA_STATIC_MODIFIER = "static";
9+
export const JAVA_STRICTFP_MODIFIER = "strictfp";
10+
export const JAVA_SEALED_MODIFIER = "sealed";
11+
export const JAVA_NONSEALED_MODIFIER = "non-sealed";
12+
13+
export type Modifier =
14+
| typeof JAVA_PUBLIC_MODIFIER
15+
| typeof JAVA_PRIVATE_MODIFIER
16+
| typeof JAVA_PROTECTED_MODIFIER
17+
| typeof JAVA_ABSTRACT_MODIFIER
18+
| typeof JAVA_FINAL_MODIFIER
19+
| typeof JAVA_STATIC_MODIFIER
20+
| typeof JAVA_STRICTFP_MODIFIER
21+
| typeof JAVA_SEALED_MODIFIER
22+
| typeof JAVA_NONSEALED_MODIFIER;
23+
24+
export const JAVA_CLASS_TYPE = "class";
25+
export const JAVA_INTERFACE_TYPE = "interface";
26+
export const JAVA_ENUM_TYPE = "enum";
27+
export const JAVA_RECORD_TYPE = "record";
28+
export const JAVA_ANNOTATION_TYPE = "annotation";
29+
30+
export type SymbolType =
31+
| typeof JAVA_CLASS_TYPE
32+
| typeof JAVA_INTERFACE_TYPE
33+
| typeof JAVA_ENUM_TYPE
34+
| typeof JAVA_RECORD_TYPE
35+
| typeof JAVA_ANNOTATION_TYPE;
36+
37+
export interface JavaFile {
38+
path: string;
39+
rootNode: Parser.SyntaxNode;
40+
symbol: ExportedSymbol;
41+
package: string;
42+
imports: string[];
43+
}
44+
45+
export interface ExportedSymbol {
46+
name: string;
47+
type: SymbolType;
48+
modifiers: Modifier[];
49+
typeParamCount: number;
50+
superclass?: string;
51+
interfaces: string[];
52+
children: ExportedSymbol[];
53+
node: Parser.SyntaxNode;
54+
idNode: Parser.SyntaxNode;
55+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { javaFilesFolder } from "./index.ts";
2+
import { join } from "@std/path";
3+
4+
// Root
5+
export const APP = join(javaFilesFolder, "App.java");
6+
7+
// Food package
8+
export const CONDIMENT = join(javaFilesFolder, "food/Condiment.java");
9+
export const FOOD = join(javaFilesFolder, "food/Food.java");
10+
export const STEAK = join(javaFilesFolder, "food/Steak.java");
11+
export const BURGER = join(javaFilesFolder, "food/Burger.java");
12+
export const DOUBLEBURGER = join(javaFilesFolder, "food/DoubleBurger.java");

packages/cli/src/languagePlugins/java/testFiles/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
if (!import.meta.dirname) {
1010
throw new Error("import.meta.dirname is not defined");
1111
}
12-
export const javaFilesFolder = join(import.meta.dirname, "napi-tests");
12+
export const javaFilesFolder = join(
13+
import.meta.dirname,
14+
"napi-tests/src/main/java/io/nanoapi/testfiles",
15+
);
1316
const javaFilesMap = new Map<
1417
string,
1518
{ path: string; rootNode: Parser.SyntaxNode }
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.nanoapi.testfiles.food;
2+
3+
import java.io;
4+
import java.lang.System;
5+
6+
public class Burger<T> implements Food {
7+
8+
public void eat() {
9+
System.out.println("Yummy !");
10+
}
11+
12+
public double getPrice() {
13+
T t = new T();
14+
if (t instanceof Food) {
15+
return t.getPrice() + 2.0;
16+
} else {
17+
return 2.0;
18+
}
19+
}
20+
21+
public double getCalories() {
22+
T t = new T();
23+
if (t instanceof Food) {
24+
return t.getCalories + 50.0;
25+
} else {
26+
return 50.0;
27+
}
28+
}
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.nanoapi.testfiles.food;
2+
3+
public enum Condiment {
4+
SALAD,
5+
TOMATO,
6+
ONION,
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.nanoapi.testfiles.food;
2+
3+
public class DoubleBurger<T, N> extends Burger {}

0 commit comments

Comments
 (0)