Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/cli/src/cli/handlers/init/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { globSync } from "glob";
import {
csharpLanguage,
pythonLanguage,
cLanguage,
} from "../../../helpers/treeSitter/parsers.js";

/**
Expand Down Expand Up @@ -145,7 +146,7 @@ async function collectIncludePatterns(
Include patterns define which files NanoAPI will process and analyze.

Examples:
- '**/*.py' for all Python files
- '**/*.py' for all Python files
- 'src/**' for all files in src directory
- '*.py' for all Python files in the root directory
`,
Expand Down Expand Up @@ -499,6 +500,7 @@ export async function generateConfig(
choices: [
{ name: "Python", value: pythonLanguage },
{ name: "C#", value: csharpLanguage },
{ name: "C", value: cLanguage },
],
});

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/config/localConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import pythonStdlibList from "../scripts/generate_python_stdlib_list/output.json
import {
csharpLanguage,
pythonLanguage,
cLanguage,
} from "../helpers/treeSitter/parsers.js";

const pythonVersions = Object.keys(pythonStdlibList);

export const localConfigSchema = z.object({
language: z.enum([pythonLanguage, csharpLanguage]),
language: z.enum([pythonLanguage, csharpLanguage, cLanguage]),
[pythonLanguage]: z
.object({
version: z
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/helpers/fileSystem/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
import { globSync } from "glob";
import { dirname, join } from "path";
import { csharpLanguage, pythonLanguage } from "../treeSitter/parsers.js";
import {
cLanguage,
csharpLanguage,
pythonLanguage,
} from "../treeSitter/parsers.js";

export function getExtensionsForLanguage(language: string) {
const supportedLanguages = {
[pythonLanguage]: ["py"],
[csharpLanguage]: ["cs", "csproj"],
[cLanguage]: ["c", "h"],
};

const supportedLanguage = supportedLanguages[language];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, test, expect } from "vitest";
import { getCFilesMap, cFilesFolder } from "../testFiles/index.js";
import { CDependencyFormatter } from "./index.js";
import path from "path";

describe("CDependencyFormatter", () => {
const cFilesMap = getCFilesMap();
const depFormatter = new CDependencyFormatter(cFilesMap);
const burgersh = path.join(cFilesFolder, "burgers.h");
const burgersc = path.join(cFilesFolder, "burgers.c");
const personnelh = path.join(cFilesFolder, "personnel.h");
const main = path.join(cFilesFolder, "main.c");

test("main.c", () => {
const fmain = depFormatter.formatFile(main);
expect(fmain).toBeDefined();
expect(fmain.id).toBe(main);
expect(fmain.dependencies[burgersh]).toBeDefined();
expect(fmain.dependencies[burgersc]).not.toBeDefined();
expect(fmain.dependencies[personnelh]).toBeDefined();
expect(fmain.dependencies["<stdio.h>"]).toBeDefined();
expect(fmain.dependencies[personnelh].isExternal).toBe(false);
expect(fmain.dependencies[burgersh].isExternal).toBe(false);
expect(fmain.dependencies["<stdio.h>"].isExternal).toBe(true);
expect(fmain.dependencies[burgersh].symbols["Burger"]).toBeDefined();
expect(fmain.dependencies[burgersh].symbols["create_burger"]).toBeDefined();
expect(fmain.dependencies[personnelh].symbols["Employee"]).toBeDefined();
expect(
fmain.dependencies[personnelh].symbols["create_employee"],
).toBeDefined();
expect(
fmain.dependencies[personnelh].symbols["print_employee_details"],
).toBeDefined();
expect(fmain.symbols["main"]).toBeDefined();
expect(fmain.symbols["main"].type).toBe("function");
expect(fmain.symbols["main"].lineCount > 1).toBe(true);
expect(fmain.symbols["main"].characterCount > 1).toBe(true);
expect(fmain.symbols["main"].dependents).toBeDefined();
expect(fmain.symbols["main"].dependencies).toBeDefined();
expect(fmain.symbols["main"].dependencies[burgersh]).toBeDefined();
expect(fmain.symbols["main"].dependencies[burgersh].isExternal).toBe(false);
expect(fmain.symbols["main"].dependencies[burgersh].symbols["Burger"]).toBe(
"Burger",
);
expect(
fmain.symbols["main"].dependencies[burgersh].symbols["create_burger"],
).toBe("create_burger");
expect(fmain.symbols["main"].dependencies[personnelh]).toBeDefined();
expect(fmain.symbols["main"].dependencies[personnelh].isExternal).toBe(
false,
);
expect(
fmain.symbols["main"].dependencies[personnelh].symbols["Employee"],
).toBe("Employee");
expect(
fmain.symbols["main"].dependencies[personnelh].symbols["create_employee"],
).toBe("create_employee");
expect(
fmain.symbols["main"].dependencies[personnelh].symbols[
"print_employee_details"
],
).toBe("print_employee_details");
expect(fmain.symbols["main"].dependencies["<stdio.h>"]).not.toBeDefined();
});
});
154 changes: 154 additions & 0 deletions packages/cli/src/languagePlugins/c/dependencyFormatting/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
C_DEP_FUNCTION_TYPE,
CDependency,
CDepFile,
CDepSymbol,
CDepSymbolType,
} from "./types.js";
import { CSymbolRegistry } from "../symbolRegistry/index.js";
import { CIncludeResolver } from "../includeResolver/index.js";
import { CInvocationResolver } from "../invocationResolver/index.js";
import { CFile, Symbol } from "../symbolRegistry/types.js";
import { Invocations } from "../invocationResolver/types.js";
import Parser from "tree-sitter";
import { C_VARIABLE_TYPE, SymbolType } from "../headerResolver/types.js";

export class CDependencyFormatter {
symbolRegistry: CSymbolRegistry;
includeResolver: CIncludeResolver;
invocationResolver: CInvocationResolver;
#registry: Map<string, CFile>;

constructor(
files: Map<string, { path: string; rootNode: Parser.SyntaxNode }>,
) {
this.symbolRegistry = new CSymbolRegistry(files);
this.#registry = this.symbolRegistry.getRegistry();
this.includeResolver = new CIncludeResolver(this.symbolRegistry);
this.invocationResolver = new CInvocationResolver(this.includeResolver);
}

#formatSymbolType(st: SymbolType): CDepSymbolType {
if (["struct", "enum", "union", "typedef", "variable"].includes(st)) {
return st as CDepSymbolType;
}
if (
["function_signature", "function_definition", "macro_function"].includes(
st,
)
) {
return C_DEP_FUNCTION_TYPE;
}
if (st === "macro_constant") {
return C_VARIABLE_TYPE as CDepSymbolType;
}
}

/**
* Formats the dependencies of a file.
* @param fileDependencies - The dependencies of the file.
* @returns A formatted record of dependencies.
*/
#formatDependencies(
fileDependencies: Invocations,
): Record<string, CDependency> {
const dependencies: Record<string, CDependency> = {};
const resolved = fileDependencies.resolved;
for (const [symName, symbol] of resolved) {
const filepath = symbol.declaration.filepath;
const id = symName;
if (!dependencies[filepath]) {
dependencies[filepath] = {
id: filepath,
isExternal: false,
symbols: {},
};
}
dependencies[filepath].symbols[id] = id;
// if (symbol instanceof Function) {
// const defpath = symbol.definitionPath;
// if (!dependencies[defpath]) {
// dependencies[defpath] = {
// id: defpath,
// isExternal: false,
// symbols: {},
// };
// }
// dependencies[defpath].symbols[id] = id;
// }
}
return dependencies;
}

#formatStandardIncludes(stdincludes: string[]): Record<string, CDependency> {
const dependencies: Record<string, CDependency> = {};
for (const id of stdincludes) {
if (!dependencies[id]) {
dependencies[id] = {
id: id,
isExternal: true,
symbols: {},
};
}
}
return dependencies;
}

/**
* Formats the symbols of a file.
* @param fileSymbols - The symbols of the file.
* @returns A formatted record of symbols.
*/
#formatSymbols(fileSymbols: Map<string, Symbol>): Record<string, CDepSymbol> {
const symbols: Record<string, CDepSymbol> = {};
for (const [symName, symbol] of fileSymbols) {
const id = symName;
const dependencies =
this.invocationResolver.getInvocationsForSymbol(symbol);
if (!symbols[id]) {
symbols[id] = {
id: id,
type: this.#formatSymbolType(symbol.declaration.type),
lineCount:
symbol.declaration.node.endPosition.row -
symbol.declaration.node.startPosition.row,
characterCount:
symbol.declaration.node.endIndex -
symbol.declaration.node.startIndex,
node: symbol.declaration.node,
dependents: {},
dependencies: this.#formatDependencies(dependencies),
};
}
}
return symbols;
}

formatFile(filepath: string): CDepFile {
const file = this.#registry.get(filepath);
if (!file) {
throw new Error(`File not found: ${filepath}`);
}
const fileSymbols = file.symbols;
const fileDependencies =
this.invocationResolver.getInvocationsForFile(filepath);
const includes = this.includeResolver.getInclusions().get(filepath);
const stdincludes = Array.from(includes.standard.keys());
const invokedDependencies = this.#formatDependencies(fileDependencies);
const stdDependencies = this.#formatStandardIncludes(stdincludes);
const allDependencies = {
...invokedDependencies,
...stdDependencies,
};
const formattedFile: CDepFile = {
id: filepath,
filePath: file.file.path,
rootNode: file.file.rootNode,
lineCount: file.file.rootNode.endPosition.row,
characterCount: file.file.rootNode.endIndex,
dependencies: allDependencies,
symbols: this.#formatSymbols(fileSymbols),
};
return formattedFile;
}
}
60 changes: 60 additions & 0 deletions packages/cli/src/languagePlugins/c/dependencyFormatting/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
C_ENUM_TYPE,
C_UNION_TYPE,
C_STRUCT_TYPE,
C_TYPEDEF_TYPE,
C_VARIABLE_TYPE,
} from "../headerResolver/types.js";
import Parser from "tree-sitter";

/**
* Represents a dependency in a C file
*/
export interface CDependency {
id: string;
isExternal: boolean;
symbols: Record<string, string>;
}

/**
* Represents a dependent in a C file
*/
export interface CDependent {
id: string;
symbols: Record<string, string>;
}

export const C_DEP_FUNCTION_TYPE = "function";
export type CDepSymbolType =
| typeof C_ENUM_TYPE
| typeof C_UNION_TYPE
| typeof C_STRUCT_TYPE
| typeof C_TYPEDEF_TYPE
| typeof C_VARIABLE_TYPE
| typeof C_DEP_FUNCTION_TYPE;

/**
* Represents a symbol in a C file
*/
export interface CDepSymbol {
id: string;
type: CDepSymbolType;
lineCount: number;
characterCount: number;
node: Parser.SyntaxNode;
dependents: Record<string, CDependent>;
dependencies: Record<string, CDependency>;
}

/**
* Represents a C file with its dependencies and symbols.
*/
export interface CDepFile {
id: string;
filePath: string;
rootNode: Parser.SyntaxNode;
lineCount: number;
characterCount: number;
dependencies: Record<string, CDependency>;
symbols: Record<string, CDepSymbol>;
}
Loading