Skip to content

Adding function information to the /render page #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f7b084e
Added new metadata type, extracted data from cfg and the function.
AlonBilman Mar 18, 2025
3151ea6
extracting all the data for issue #94 , tested the extraction with gi…
AlonBilman Mar 18, 2025
ca084e1
added the metadata to the page
AlonBilman Mar 18, 2025
7a7b13f
Updated .md files (changelog and readme)
AlonBilman Mar 19, 2025
2ae4d60
linting- commit in order to merge
AlonBilman Mar 23, 2025
d5bbffb
check and update lint
Bennahmias Mar 26, 2025
a2ba7d5
Trying to find a way to avoid fetching twice and to make the relevant…
AlonBilman Mar 27, 2025
7618ad8
Merge remote-tracking branch 'origin/main' into issue#94
AlonBilman Apr 2, 2025
d675eac
Added function name extraction in function-utils.ts
AlonBilman Apr 2, 2025
6e6d675
Add: functionDetils.test.ts file that test for now the extractFunctio…
Bennahmias Apr 2, 2025
e944b01
Merge remote-tracking branch 'origin/issue#94' into issue#94_testing
Bennahmias Apr 2, 2025
a71484b
Fix: update import path for extractFunctionName and add console log f…
Bennahmias Apr 2, 2025
34e3ae6
Refactor function name extraction to improve type handling and add su…
AlonBilman Apr 4, 2025
328d86d
Fixed a bug in the TypeScript generator's function name extraction.
AlonBilman Apr 4, 2025
dcea710
added documentation
AlonBilman Apr 4, 2025
69bf1a4
Refactor: update tests for extractFunctionName
Bennahmias Apr 5, 2025
0d3f03e
Merge remote-tracking branch 'origin/issue#94' into issue#94_testing
Bennahmias Apr 5, 2025
c279feb
simplify anonymous function naming
AlonBilman Apr 5, 2025
fac3bef
change the test suites
Bennahmias Apr 5, 2025
ce19dd5
Merge remote-tracking branch 'origin/issue#94' into issue#94_testing
Bennahmias Apr 5, 2025
4d7f20f
fix: add some test cases
Bennahmias Apr 5, 2025
542004b
test: add new function tests
Bennahmias Apr 5, 2025
5dc7ce2
remove comments from tests
Bennahmias Apr 5, 2025
dc4457f
Merge remote-tracking branch 'origin/main' into issue#94
AlonBilman Apr 14, 2025
b73437c
add experimental metadata display and control panel for function anal…
AlonBilman Apr 14, 2025
7253af7
Adjusting styles for dark/light modes
AlonBilman Apr 15, 2025
f69336f
Refactor function name extraction to return "<Anonymous>" for anonymo…
Bennahmias Apr 17, 2025
26c9883
refactor: separate all languages into individual files without changi…
AlonBilman Apr 23, 2025
1768167
Merge branch 'main' into issue#94
AlonBilman Apr 23, 2025
c17b84c
feat(parser): enhance C++ function name extraction for operator overl…
AlonBilman Apr 23, 2025
52c72f7
test: add tests for extracting function names from Go literals and sh…
AlonBilman Apr 23, 2025
8c74c99
refactor(tests): migrate from bun to vitest and update imports
Bennahmias Apr 24, 2025
89a9f51
extract var name from function assignment using Tree-sitter
AlonBilman Apr 24, 2025
051ccc6
Merge remote-tracking branch 'origin/issue#94_testing' into issue#94
AlonBilman Apr 24, 2025
8adbcfd
refactor: rename extractNameByNodeName to extractNameByNodeType and u…
AlonBilman Apr 24, 2025
9880873
refactor: replaced findNameInParentHierarchy with a query-based appro…
AlonBilman Apr 25, 2025
d145111
delete snapshot changes. (will be added if needed)
AlonBilman May 4, 2025
3ab9247
Improved render style to prevent sliding window from breaking on brow…
AlonBilman May 4, 2025
009f53a
feat: add Graph rendering with CFG-metadata options only
AlonBilman May 4, 2025
e6c8172
Merge remote-tracking branch 'origin/main' into issue#94
AlonBilman May 8, 2025
607b7fe
CHANGELOG with new features
AlonBilman May 10, 2025
2469d6d
Revert unnecessary changes. mb.
AlonBilman May 10, 2025
41ac697
Revert unnecessary changes. MB.
AlonBilman May 10, 2025
fd7aa24
Merge remote-tracking branch 'origin/main' into issue#94
AlonBilman May 10, 2025
421878f
Added a few failing tests that should pass.
tmr232 May 13, 2025
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how

## [Unreleased]

### Added

- Function name extraction with support for multiple programming languages.
- Unit tests for function name extraction, covering various structures and languages.
- Frontend logic in `render/src/App.svelte` to extract and display metadata based on render type.
- Display of both CFG and function metadata in the GitHub render view, and CFG metadata in the Graph render view.

## [0.0.16] - 2025-05-07

### Added
Expand Down
14 changes: 14 additions & 0 deletions src/control-flow/cfg-c.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,17 @@ function processSwitchlike(switchSyntax: SyntaxNode, ctx: Context): BasicBlock {

return blockHandler.update({ entry: headNode, exit: mergeNode });
}

const nodeType = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this constant for? Why not use the names directlu?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll modify it

functionDefinition: "function_definition",

// identifier lookups
identifier: "identifier",
};

export function extractCFunctionName(func: SyntaxNode): string | undefined {
if (func.type === nodeType.functionDefinition) {
return func.descendantsOfType(nodeType.identifier)[0]?.text;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where this really is undefined, or is it due to the weird typing in the current web-tree-sitter library? If it's the weird types, we have treeSitterNoNullNodes to deal with that in an "easy to find later" way.

}
return undefined;
}
70 changes: 70 additions & 0 deletions src/control-flow/cfg-cpp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
processReturnStatement,
processThrowStatement,
} from "./common-patterns.ts";
import { extractNameByNodeType } from "./function-utils.ts";
import {
type Context,
GenericCFGBuilder,
Expand Down Expand Up @@ -148,3 +149,72 @@ function processTryStatement(trySyntax: SyntaxNode, ctx: Context): BasicBlock {
});
});
}

const nodeType = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value do we get from this object?

functionDefinition: "function_definition",
lambdaExpression: "lambda_expression",

// inline/function‑assignment cases
variableDeclaration: "variable_declaration",
initDeclarator: "init_declarator",

// identifier lookups
identifier: "identifier",

//Unnamed functions
anonymous: "<anonymous>",

//Special cases , operator-name and destructor-name
operatorName: "operator_name",
destructorName: "destructor_name",
};

function findCppNameInParentHierarchy(
func: SyntaxNode,
parentType: string,
childType: string,
): string | undefined {
let parent = func.parent;
while (parent) {
if (parent.type === parentType) {
return extractNameByNodeType(parent, childType);
}
parent = parent.parent;
}
return undefined;
}

export function extractCppFunctionName(func: SyntaxNode): string | undefined {
if (func.type === nodeType.functionDefinition) {
// Look for an operator overload (This should happen before the identifier/destructor).
const operatorNode = func.descendantsOfType(nodeType.operatorName)[0];
if (operatorNode) return operatorNode.text;

// else, look for the destructor name, which is a special case because identifier returns without the ~
const destructorNode = func.descendantsOfType(nodeType.destructorName)[0];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of checks here does not work when we have nested classes. Consider the following:

int square(int num) {
    class X {
        ~X() {};
    };
    return num * num;
}

We'll get ~X instead of square.

I think it's important to add tests with nested functions and classes to ensure we don't have more of these.

I'm guessing that iterating through the children instead of all descendants will yield better results.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh..good catch. That's because I gave destructors higher priority when extracting the function name. I'll change it, maybe it's time to try queries for the C and C++ cases.

if (destructorNode) {
return destructorNode.text;
}
//if neither of those, look for the identifier
const idNode = func.descendantsOfType(nodeType.identifier)[0];
if (idNode) return idNode.text;
}
if (func.type === nodeType.lambdaExpression) {
// if the lambda is assigned to a variable, return the variable name
// otherwise return "<Anonymous>"
return (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an anonymous function is declared inside a named function, we'll get the name of the outer function here.

Consider:

void f() {
    auto my_func = [](){
        [](){}();
    };
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wrote a comment about this, same here.
Since each function stands alone, we get:

void f() {
   auto my_func = [](){
       [](){}();
   };
}

we get f.

   auto my_func = [](){
        [](){}();
    };

we get my_func

[](){}();
we get <Anonymous>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But functions do not stand alone.
When we analyze code, we get the AST for the entire file, and get the relevant Node for the function.
That Node exists in the AST. As such - we should be able to correctly query nested function and non-nested functions alike.

Copy link
Author

@AlonBilman AlonBilman May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But functions do not stand alone.
When we analyze code, we get the AST for the entire file, and get the relevant Node for the function.

For the render page, I believe we only get the function code and not the whole file (correct me if I'm wrong).
That's why I was thinking this way

get the relevant Node for the function.

If that were the case, the node would stand alone so querying the nested function should work exactly as I already did.
Does that make sense?
I'll try to figure out why that's not happening..

findCppNameInParentHierarchy(
func,
nodeType.variableDeclaration,
nodeType.identifier,
) ||
findCppNameInParentHierarchy(
func,
nodeType.initDeclarator,
nodeType.identifier,
) ||
nodeType.anonymous
);
}
return undefined;
}
76 changes: 76 additions & 0 deletions src/control-flow/cfg-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
processReturnStatement,
processStatementSequence,
} from "./common-patterns.ts";
import {
extractNameByNodeType,
extractTaggedValueFromTreeSitterQuery,
} from "./function-utils.ts";
import {
type Context,
GenericCFGBuilder,
Expand Down Expand Up @@ -419,3 +423,75 @@ function processSwitchlike(

return blockHandler.update({ entry: headNode, exit: mergeNode });
}

const nodeType = {
// Function-related node types
functionDeclaration: "function_declaration",
methodDeclaration: "method_declaration",
funcLiteral: "func_literal",

// identifier lookups
identifier: "identifier",
fieldIdentifier: "field_identifier",

// Unnamed functions
anonymous: "<anonymous>",
};

const shortVarQueryAndTag = {
query: `
(short_var_declaration
left: (expression_list
(identifier) @var.name))
`,
tag: "var.name",
};

const varDeclarationQueryAndTag = {
query: `
(var_declaration
(var_spec
(identifier) @var.name))
`,
tag: "var.name",
};

const assignmentQueryAndTag = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three queries can be unified with an alternation in the query.

query: `
(assignment_statement
left: (expression_list
(identifier) @var.name))
`,
tag: "var.name",
};

export function extractGoFunctionName(func: SyntaxNode): string | undefined {
switch (func.type) {
case nodeType.functionDeclaration:
return extractNameByNodeType(func, nodeType.identifier);
case nodeType.methodDeclaration:
return extractNameByNodeType(func, nodeType.fieldIdentifier);
case nodeType.funcLiteral:
// Check if the func_literal is assigned to a variable or is a standalone function
return (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is important to limit the depth of a query, so that they properly ignore nesting.
Consider:

func main() {
	var x = func() {
		y := func() {}
		y()
	}
	x()
}

See maxStartDepth (used in block-matcher.ts)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll check it out and make the changes.

extractTaggedValueFromTreeSitterQuery(
func,
shortVarQueryAndTag.query,
shortVarQueryAndTag.tag,
) ||
extractTaggedValueFromTreeSitterQuery(
func,
varDeclarationQueryAndTag.query,
varDeclarationQueryAndTag.tag,
) ||
extractTaggedValueFromTreeSitterQuery(
func,
assignmentQueryAndTag.query,
assignmentQueryAndTag.tag,
) ||
nodeType.anonymous
);
default:
return undefined;
}
}
17 changes: 17 additions & 0 deletions src/control-flow/cfg-python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
forEachLoopProcessor,
processStatementSequence,
} from "./common-patterns.ts";
import { extractNameByNodeType } from "./function-utils.ts";
import {
type Context,
GenericCFGBuilder,
Expand Down Expand Up @@ -624,3 +625,19 @@ function processWhileStatement(

return matcher.update({ entry: condBlock.entry, exit: exitNode });
}

const nodeType = {
functionDefinition: "function_definition",

// identifier lookups
identifier: "identifier",
};

export function extractPythonFunctionName(
func: SyntaxNode,
): string | undefined {
if (func.type === nodeType.functionDefinition) {
return extractNameByNodeType(func, nodeType.identifier);
}
return undefined;
}
83 changes: 83 additions & 0 deletions src/control-flow/cfg-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
processStatementSequence,
processThrowStatement,
} from "./common-patterns.ts";
import {
extractNameByNodeType,
extractTaggedValueFromTreeSitterQuery,
} from "./function-utils.ts";
import {
type Context,
GenericCFGBuilder,
Expand Down Expand Up @@ -319,3 +323,82 @@ function processTryStatement(trySyntax: SyntaxNode, ctx: Context): BasicBlock {
});
});
}

const nodeType = {
// function‑declaration cases
functionDeclaration: "function_declaration",
generatorFunctionDeclaration: "generator_function_declaration",

// inline/function‑expression cases
generatorFunction: "generator_function",
arrowFunction: "arrow_function",
functionExpression: "function_expression",

// methods
methodDefinition: "method_definition",

// identifier lookups
identifier: "identifier",
propertyIdentifier: "property_identifier",

// Unnamed functions
anonymous: "<anonymous>",
};

const variableDeclaratorQueryAndTag = {
query: `
(lexical_declaration
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What of var definitions, and of definitions without either var, let or const?

(variable_declarator
name: (identifier) @var.name))
`,
tag: "var.name",
};

/**
* Extracts the name of a TypeScript function based on its syntax node type.
*
* @param {SyntaxNode} func - The syntax node representing the TypeScript function.
* @returns {string | undefined} The function name, or `undefined` if not found.
*/
export function extractTypeScriptFunctionName(
func: SyntaxNode,
): string | undefined {
switch (func.type) {
case nodeType.functionDeclaration:
case nodeType.generatorFunctionDeclaration:
return extractNameByNodeType(func, nodeType.identifier);

case nodeType.generatorFunction:
case nodeType.arrowFunction:
return (
extractTaggedValueFromTreeSitterQuery(
func,
variableDeclaratorQueryAndTag.query,
variableDeclaratorQueryAndTag.tag,
) || nodeType.anonymous
);

case nodeType.methodDefinition:
return extractNameByNodeType(func, nodeType.propertyIdentifier);

case nodeType.functionExpression: {
// first check for a direct name
const optionalIdentifier = extractNameByNodeType(
func,
nodeType.identifier,
);
if (optionalIdentifier) return optionalIdentifier;

// otherwise fall back to a variable‑assignment name
return (
extractTaggedValueFromTreeSitterQuery(
func,
variableDeclaratorQueryAndTag.query,
variableDeclaratorQueryAndTag.tag,
) || nodeType.anonymous
);
}
default:
return undefined;
}
}
Loading