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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ examples/csharp/EndpointExample/bin
auditResponse.json
.extracted/
napi-output/
coverage/
coverage/
.vite/
875 changes: 325 additions & 550 deletions deno.lock

Large diffs are not rendered by default.

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 @@ -7,6 +7,7 @@ import pythonStdlibList from "../../../scripts/generate_python_stdlib_list/outpu
import { confirm, input, number, search, select } from "@inquirer/prompts";
import { globSync } from "npm:glob";
import {
cLanguage,
csharpLanguage,
pythonLanguage,
} from "../../../helpers/treeSitter/parsers.ts";
Expand Down Expand Up @@ -146,7 +147,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 @@ -500,6 +501,7 @@ export async function generateConfig(
choices: [
{ name: "Python", value: pythonLanguage },
{ name: "C#", value: csharpLanguage },
{ name: "C", value: cLanguage },
],
});

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/cli/helpers/checkVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export async function checkVersionMiddleware() {
if (currentVersion !== latestVersion) {
console.warn(
`
You are using version ${currentVersion}.
The latest version is ${latestVersion}.
You are using version ${currentVersion}.
The latest version is ${latestVersion}.
Please update to the latest version to continue using napi.

You can update the version by running the following command:
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/config/localConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import pythonStdlibList from "../scripts/generate_python_stdlib_list/output.json
type: "json",
};
import {
cLanguage,
csharpLanguage,
pythonLanguage,
} from "../helpers/treeSitter/parsers.ts";

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 All @@ -24,6 +25,11 @@ export const localConfigSchema = z.object({
.optional(),
})
.optional(), // python specific config
[cLanguage]: z
.object({
includedirs: z.array(z.string()).optional(),
})
.optional(), // c specific config
project: z.object({
include: z.array(z.string()),
exclude: z.array(z.string()).optional(),
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,11 +1,16 @@
import { globSync } from "npm:glob";
import { dirname, join } from "@std/path";
import { csharpLanguage, pythonLanguage } from "../treeSitter/parsers.ts";
import {
cLanguage,
csharpLanguage,
pythonLanguage,
} from "../treeSitter/parsers.ts";

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

const supportedLanguage = supportedLanguages[language];
Expand Down
14 changes: 13 additions & 1 deletion packages/cli/src/helpers/treeSitter/parsers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Parser, { type Language } from "npm:tree-sitter";
import Python from "npm:tree-sitter-python";
import CSharp from "npm:tree-sitter-c-sharp";
import C from "npm:tree-sitter-c";

const pythonParser = new Parser();
pythonParser.setLanguage(Python as Language);
Expand All @@ -10,4 +11,15 @@ const csharpParser = new Parser();
csharpParser.setLanguage(CSharp as Language);
const csharpLanguage = CSharp.name as "c-sharp";

export { csharpLanguage, csharpParser, pythonLanguage, pythonParser };
const cParser = new Parser();
cParser.setLanguage(C as Language);
const cLanguage = C.name as "c";

export {
cLanguage,
cParser,
csharpLanguage,
csharpParser,
pythonLanguage,
pythonParser,
};
181 changes: 181 additions & 0 deletions packages/cli/src/languagePlugins/c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# NanoAPI C Plugin

This plugin manages parsing and mapping of dependencies in C projects.

**Warning :** This plugin relies on tree-sitter, which has an unreliable parser
for C. Not every C project is entirely compatible. Warnings may be issued where
tree-sitter finds errors.

## Class diagram

```mermaid
classDiagram
class CMetricsAnalyzer {
+analyzeNode(node: Parser.SyntaxNode): CComplexityMetrics
}

class CExtractor {
-manifest: DependencyManifest
-registry: Map<string, CFile>
-includeResolver: CIncludeResolver
+extractSymbols(symbolsMap: Map<string, SymbolsToExtract>): Map<string, File>
}

class CSymbolRegistry {
-headerResolver: CHeaderResolver
-files: Map<string, File>
+getRegistry(): Map<string, CFile>
}

class CHeaderResolver {
+resolveSymbols(file: File): ExportedSymbol[]
}

class CIncludeResolver {
-symbolRegistry: Map<string, CFile>
-files: Map<string, File>
+getInclusions(): Map<string, Inclusions>
}

class CInvocationResolver {
-includeResolver: CIncludeResolver
+getInvocationsForSymbol(symbol: Symbol): Invocations
+getInvocationsForFile(filepath: string): Invocations
}

class CDependencyFormatter {
-symbolRegistry: CSymbolRegistry
-includeResolver: CIncludeResolver
-invocationResolver: CInvocationResolver
+formatFile(filepath: string): CDepFile
}

class Symbol {
<<abstract>>
-name: string
-declaration: ExportedSymbol
}

class FunctionSignature {
-definition: FunctionDefinition
-isMacro: boolean
}

class FunctionDefinition {
-signature: FunctionSignature
-isMacro: boolean
}

class DataType {
-typedefs: Map<string, Typedef>
}

class Typedef {
-datatype: DataType
}

class Variable {
-isMacro: boolean
}

class CFile {
-file: File
-symbols: Map<string, Symbol>
-type: CFileType
}

class ExportedSymbol {
-name: string
-type: SymbolType
-specifiers: StorageClassSpecifier[]
-qualifiers: TypeQualifier[]
-node: Parser.SyntaxNode
-identifierNode: Parser.SyntaxNode
-filepath: string
}

class Inclusions {
-filepath: string
-symbols: Map<string, Symbol>
-internal: string[]
-standard: Map<string, Parser.SyntaxNode>
}

class Invocations {
-resolved: Map<string, Symbol>
-unresolved: Set<string>
}

class CDependency {
-id: string
-isExternal: boolean
-symbols: Record<string, string>
}

class CDepFile {
-id: string
-filePath: string
-rootNode: Parser.SyntaxNode
-lineCount: number
-characterCount: number
-dependencies: Record<string, CDependency>
-symbols: Record<string, CDepSymbol>
}

class CDepSymbol {
-id: string
-type: CDepSymbolType
-lineCount: number
-characterCount: number
-node: Parser.SyntaxNode
-dependents: Record<string, CDependent>
-dependencies: Record<string, CDependency>
}

class CComplexityMetrics {
-cyclomaticComplexity: number
-codeLinesCount: number
-linesCount: number
-codeCharacterCount: number
-characterCount: number
}

class CodeCounts {
-lines: number
-characters: number
}

class CommentSpan {
-start: Point
-end: Point
}

%% Relationships
Symbol <|-- FunctionSignature
Symbol <|-- FunctionDefinition
Symbol <|-- DataType
Symbol <|-- Typedef
Symbol <|-- Variable
CSymbolRegistry --> CFile
CSymbolRegistry --> CHeaderResolver
CIncludeResolver --> CSymbolRegistry
CInvocationResolver --> CIncludeResolver
CDependencyFormatter --> CSymbolRegistry
CDependencyFormatter --> CIncludeResolver
CDependencyFormatter --> CInvocationResolver
CExtractor --> CSymbolRegistry
CExtractor --> CIncludeResolver
CExtractor --> CFile
CFile --> Symbol
Typedef --> DataType
DataType --> Typedef
Invocations --> Symbol
Inclusions --> Symbol
CDepFile --> CDependency
CDepFile --> CDepSymbol
CDepSymbol --> CDependent
CDepSymbol --> CDependency
CMetricsAnalyzer --> CComplexityMetrics
CMetricsAnalyzer --> CodeCounts
CMetricsAnalyzer --> CommentSpan
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, test } from "@std/testing/bdd";
import { expect } from "@std/expect";
import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts";
import { CDependencyFormatter } from "./index.ts";
import { join } from "@std/path";

describe("CDependencyFormatter", () => {
const cFilesMap = getCFilesMap();
const depFormatter = new CDependencyFormatter(cFilesMap);
const burgersh = join(cFilesFolder, "burgers.h");
const burgersc = join(cFilesFolder, "burgers.c");
const personnelh = join(cFilesFolder, "personnel.h");
const main = 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();
});
});
Loading