Skip to content

Commit 7b2eb70

Browse files
authored
Feature/c manifest (#134)
* Dependency formatter * Added tests to formatter * C Manifest generation * Added C function to handler * Changed logic for function name resolution * Added C to languages * Fixed macro content not being parsed * Improved include path logic * Prevented overwriting of structs in header resolution * Recursively looks through inclusions * Changed standard inclusion data type * Safer query for typedef double-definitions * Updated JSDoc for header query * Separated function definition and signature * Emergency include resolution * Update crashcases.h * added metrics * added metrics to manifest
1 parent 10caa83 commit 7b2eb70

File tree

27 files changed

+1019
-162
lines changed

27 files changed

+1019
-162
lines changed

packages/cli/src/cli/handlers/init/prompts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { globSync } from "glob";
88
import {
99
csharpLanguage,
1010
pythonLanguage,
11+
cLanguage,
1112
} from "../../../helpers/treeSitter/parsers.js";
1213

1314
/**
@@ -145,7 +146,7 @@ async function collectIncludePatterns(
145146
Include patterns define which files NanoAPI will process and analyze.
146147
147148
Examples:
148-
- '**/*.py' for all Python files
149+
- '**/*.py' for all Python files
149150
- 'src/**' for all files in src directory
150151
- '*.py' for all Python files in the root directory
151152
`,
@@ -499,6 +500,7 @@ export async function generateConfig(
499500
choices: [
500501
{ name: "Python", value: pythonLanguage },
501502
{ name: "C#", value: csharpLanguage },
503+
{ name: "C", value: cLanguage },
502504
],
503505
});
504506

packages/cli/src/config/localConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import pythonStdlibList from "../scripts/generate_python_stdlib_list/output.json
55
import {
66
csharpLanguage,
77
pythonLanguage,
8+
cLanguage,
89
} from "../helpers/treeSitter/parsers.js";
910

1011
const pythonVersions = Object.keys(pythonStdlibList);
1112

1213
export const localConfigSchema = z.object({
13-
language: z.enum([pythonLanguage, csharpLanguage]),
14+
language: z.enum([pythonLanguage, csharpLanguage, cLanguage]),
1415
[pythonLanguage]: z
1516
.object({
1617
version: z

packages/cli/src/helpers/fileSystem/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
22
import { globSync } from "glob";
33
import { dirname, join } from "path";
4-
import { csharpLanguage, pythonLanguage } from "../treeSitter/parsers.js";
4+
import {
5+
cLanguage,
6+
csharpLanguage,
7+
pythonLanguage,
8+
} from "../treeSitter/parsers.js";
59

610
export function getExtensionsForLanguage(language: string) {
711
const supportedLanguages = {
812
[pythonLanguage]: ["py"],
913
[csharpLanguage]: ["cs", "csproj"],
14+
[cLanguage]: ["c", "h"],
1015
};
1116

1217
const supportedLanguage = supportedLanguages[language];
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, test, expect } from "vitest";
2+
import { getCFilesMap, cFilesFolder } from "../testFiles/index.js";
3+
import { CDependencyFormatter } from "./index.js";
4+
import path from "path";
5+
6+
describe("CDependencyFormatter", () => {
7+
const cFilesMap = getCFilesMap();
8+
const depFormatter = new CDependencyFormatter(cFilesMap);
9+
const burgersh = path.join(cFilesFolder, "burgers.h");
10+
const burgersc = path.join(cFilesFolder, "burgers.c");
11+
const personnelh = path.join(cFilesFolder, "personnel.h");
12+
const main = path.join(cFilesFolder, "main.c");
13+
14+
test("main.c", () => {
15+
const fmain = depFormatter.formatFile(main);
16+
expect(fmain).toBeDefined();
17+
expect(fmain.id).toBe(main);
18+
expect(fmain.dependencies[burgersh]).toBeDefined();
19+
expect(fmain.dependencies[burgersc]).not.toBeDefined();
20+
expect(fmain.dependencies[personnelh]).toBeDefined();
21+
expect(fmain.dependencies["<stdio.h>"]).toBeDefined();
22+
expect(fmain.dependencies[personnelh].isExternal).toBe(false);
23+
expect(fmain.dependencies[burgersh].isExternal).toBe(false);
24+
expect(fmain.dependencies["<stdio.h>"].isExternal).toBe(true);
25+
expect(fmain.dependencies[burgersh].symbols["Burger"]).toBeDefined();
26+
expect(fmain.dependencies[burgersh].symbols["create_burger"]).toBeDefined();
27+
expect(fmain.dependencies[personnelh].symbols["Employee"]).toBeDefined();
28+
expect(
29+
fmain.dependencies[personnelh].symbols["create_employee"],
30+
).toBeDefined();
31+
expect(
32+
fmain.dependencies[personnelh].symbols["print_employee_details"],
33+
).toBeDefined();
34+
expect(fmain.symbols["main"]).toBeDefined();
35+
expect(fmain.symbols["main"].type).toBe("function");
36+
expect(fmain.symbols["main"].lineCount > 1).toBe(true);
37+
expect(fmain.symbols["main"].characterCount > 1).toBe(true);
38+
expect(fmain.symbols["main"].dependents).toBeDefined();
39+
expect(fmain.symbols["main"].dependencies).toBeDefined();
40+
expect(fmain.symbols["main"].dependencies[burgersh]).toBeDefined();
41+
expect(fmain.symbols["main"].dependencies[burgersh].isExternal).toBe(false);
42+
expect(fmain.symbols["main"].dependencies[burgersh].symbols["Burger"]).toBe(
43+
"Burger",
44+
);
45+
expect(
46+
fmain.symbols["main"].dependencies[burgersh].symbols["create_burger"],
47+
).toBe("create_burger");
48+
expect(fmain.symbols["main"].dependencies[personnelh]).toBeDefined();
49+
expect(fmain.symbols["main"].dependencies[personnelh].isExternal).toBe(
50+
false,
51+
);
52+
expect(
53+
fmain.symbols["main"].dependencies[personnelh].symbols["Employee"],
54+
).toBe("Employee");
55+
expect(
56+
fmain.symbols["main"].dependencies[personnelh].symbols["create_employee"],
57+
).toBe("create_employee");
58+
expect(
59+
fmain.symbols["main"].dependencies[personnelh].symbols[
60+
"print_employee_details"
61+
],
62+
).toBe("print_employee_details");
63+
expect(fmain.symbols["main"].dependencies["<stdio.h>"]).not.toBeDefined();
64+
});
65+
});
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {
2+
C_DEP_FUNCTION_TYPE,
3+
CDependency,
4+
CDepFile,
5+
CDepSymbol,
6+
CDepSymbolType,
7+
} from "./types.js";
8+
import { CSymbolRegistry } from "../symbolRegistry/index.js";
9+
import { CIncludeResolver } from "../includeResolver/index.js";
10+
import { CInvocationResolver } from "../invocationResolver/index.js";
11+
import { CFile, Symbol } from "../symbolRegistry/types.js";
12+
import { Invocations } from "../invocationResolver/types.js";
13+
import Parser from "tree-sitter";
14+
import { C_VARIABLE_TYPE, SymbolType } from "../headerResolver/types.js";
15+
16+
export class CDependencyFormatter {
17+
symbolRegistry: CSymbolRegistry;
18+
includeResolver: CIncludeResolver;
19+
invocationResolver: CInvocationResolver;
20+
#registry: Map<string, CFile>;
21+
22+
constructor(
23+
files: Map<string, { path: string; rootNode: Parser.SyntaxNode }>,
24+
) {
25+
this.symbolRegistry = new CSymbolRegistry(files);
26+
this.#registry = this.symbolRegistry.getRegistry();
27+
this.includeResolver = new CIncludeResolver(this.symbolRegistry);
28+
this.invocationResolver = new CInvocationResolver(this.includeResolver);
29+
}
30+
31+
#formatSymbolType(st: SymbolType): CDepSymbolType {
32+
if (["struct", "enum", "union", "typedef", "variable"].includes(st)) {
33+
return st as CDepSymbolType;
34+
}
35+
if (
36+
["function_signature", "function_definition", "macro_function"].includes(
37+
st,
38+
)
39+
) {
40+
return C_DEP_FUNCTION_TYPE;
41+
}
42+
if (st === "macro_constant") {
43+
return C_VARIABLE_TYPE as CDepSymbolType;
44+
}
45+
}
46+
47+
/**
48+
* Formats the dependencies of a file.
49+
* @param fileDependencies - The dependencies of the file.
50+
* @returns A formatted record of dependencies.
51+
*/
52+
#formatDependencies(
53+
fileDependencies: Invocations,
54+
): Record<string, CDependency> {
55+
const dependencies: Record<string, CDependency> = {};
56+
const resolved = fileDependencies.resolved;
57+
for (const [symName, symbol] of resolved) {
58+
const filepath = symbol.declaration.filepath;
59+
const id = symName;
60+
if (!dependencies[filepath]) {
61+
dependencies[filepath] = {
62+
id: filepath,
63+
isExternal: false,
64+
symbols: {},
65+
};
66+
}
67+
dependencies[filepath].symbols[id] = id;
68+
// if (symbol instanceof Function) {
69+
// const defpath = symbol.definitionPath;
70+
// if (!dependencies[defpath]) {
71+
// dependencies[defpath] = {
72+
// id: defpath,
73+
// isExternal: false,
74+
// symbols: {},
75+
// };
76+
// }
77+
// dependencies[defpath].symbols[id] = id;
78+
// }
79+
}
80+
return dependencies;
81+
}
82+
83+
#formatStandardIncludes(stdincludes: string[]): Record<string, CDependency> {
84+
const dependencies: Record<string, CDependency> = {};
85+
for (const id of stdincludes) {
86+
if (!dependencies[id]) {
87+
dependencies[id] = {
88+
id: id,
89+
isExternal: true,
90+
symbols: {},
91+
};
92+
}
93+
}
94+
return dependencies;
95+
}
96+
97+
/**
98+
* Formats the symbols of a file.
99+
* @param fileSymbols - The symbols of the file.
100+
* @returns A formatted record of symbols.
101+
*/
102+
#formatSymbols(fileSymbols: Map<string, Symbol>): Record<string, CDepSymbol> {
103+
const symbols: Record<string, CDepSymbol> = {};
104+
for (const [symName, symbol] of fileSymbols) {
105+
const id = symName;
106+
const dependencies =
107+
this.invocationResolver.getInvocationsForSymbol(symbol);
108+
if (!symbols[id]) {
109+
symbols[id] = {
110+
id: id,
111+
type: this.#formatSymbolType(symbol.declaration.type),
112+
lineCount:
113+
symbol.declaration.node.endPosition.row -
114+
symbol.declaration.node.startPosition.row,
115+
characterCount:
116+
symbol.declaration.node.endIndex -
117+
symbol.declaration.node.startIndex,
118+
node: symbol.declaration.node,
119+
dependents: {},
120+
dependencies: this.#formatDependencies(dependencies),
121+
};
122+
}
123+
}
124+
return symbols;
125+
}
126+
127+
formatFile(filepath: string): CDepFile {
128+
const file = this.#registry.get(filepath);
129+
if (!file) {
130+
throw new Error(`File not found: ${filepath}`);
131+
}
132+
const fileSymbols = file.symbols;
133+
const fileDependencies =
134+
this.invocationResolver.getInvocationsForFile(filepath);
135+
const includes = this.includeResolver.getInclusions().get(filepath);
136+
const stdincludes = Array.from(includes.standard.keys());
137+
const invokedDependencies = this.#formatDependencies(fileDependencies);
138+
const stdDependencies = this.#formatStandardIncludes(stdincludes);
139+
const allDependencies = {
140+
...invokedDependencies,
141+
...stdDependencies,
142+
};
143+
const formattedFile: CDepFile = {
144+
id: filepath,
145+
filePath: file.file.path,
146+
rootNode: file.file.rootNode,
147+
lineCount: file.file.rootNode.endPosition.row,
148+
characterCount: file.file.rootNode.endIndex,
149+
dependencies: allDependencies,
150+
symbols: this.#formatSymbols(fileSymbols),
151+
};
152+
return formattedFile;
153+
}
154+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
C_ENUM_TYPE,
3+
C_UNION_TYPE,
4+
C_STRUCT_TYPE,
5+
C_TYPEDEF_TYPE,
6+
C_VARIABLE_TYPE,
7+
} from "../headerResolver/types.js";
8+
import Parser from "tree-sitter";
9+
10+
/**
11+
* Represents a dependency in a C file
12+
*/
13+
export interface CDependency {
14+
id: string;
15+
isExternal: boolean;
16+
symbols: Record<string, string>;
17+
}
18+
19+
/**
20+
* Represents a dependent in a C file
21+
*/
22+
export interface CDependent {
23+
id: string;
24+
symbols: Record<string, string>;
25+
}
26+
27+
export const C_DEP_FUNCTION_TYPE = "function";
28+
export type CDepSymbolType =
29+
| typeof C_ENUM_TYPE
30+
| typeof C_UNION_TYPE
31+
| typeof C_STRUCT_TYPE
32+
| typeof C_TYPEDEF_TYPE
33+
| typeof C_VARIABLE_TYPE
34+
| typeof C_DEP_FUNCTION_TYPE;
35+
36+
/**
37+
* Represents a symbol in a C file
38+
*/
39+
export interface CDepSymbol {
40+
id: string;
41+
type: CDepSymbolType;
42+
lineCount: number;
43+
characterCount: number;
44+
node: Parser.SyntaxNode;
45+
dependents: Record<string, CDependent>;
46+
dependencies: Record<string, CDependency>;
47+
}
48+
49+
/**
50+
* Represents a C file with its dependencies and symbols.
51+
*/
52+
export interface CDepFile {
53+
id: string;
54+
filePath: string;
55+
rootNode: Parser.SyntaxNode;
56+
lineCount: number;
57+
characterCount: number;
58+
dependencies: Record<string, CDependency>;
59+
symbols: Record<string, CDepSymbol>;
60+
}

0 commit comments

Comments
 (0)