From 9de4012b320cefbd8cdc63162960409974e51ea4 Mon Sep 17 00:00:00 2001 From: tkucar Date: Fri, 14 Mar 2025 15:21:19 +0100 Subject: [PATCH 01/35] fix --- .../snapshot_event_handler/pr_tasks.py | 2 - src/codegen/sdk/core/expressions/name.py | 38 ++- .../sdk/core/interfaces/conditional_block.py | 12 + src/codegen/sdk/core/interfaces/editable.py | 8 + .../sdk/core/statements/if_block_statement.py | 7 + .../core/statements/try_catch_statement.py | 27 ++- .../sdk/python/statements/catch_statement.py | 1 + .../python/statements/try_catch_statement.py | 8 +- .../statements/try_catch_statement.py | 9 +- .../test_if_block_statement_properties.py | 53 ++++ .../test_try_catch_statement.py | 229 ++++++++++++++++++ .../test_match_statement.py | 139 +++++++++++ .../test_if_block_statement_properties.py | 190 +++++++++++++++ .../switch_statement/test_switch_statement.py | 188 ++++++++++++++ .../test_try_catch_statement.py | 215 ++++++++++++++++ 15 files changed, 1100 insertions(+), 26 deletions(-) diff --git a/codegen-examples/examples/snapshot_event_handler/pr_tasks.py b/codegen-examples/examples/snapshot_event_handler/pr_tasks.py index 153e303f0..992a120a9 100644 --- a/codegen-examples/examples/snapshot_event_handler/pr_tasks.py +++ b/codegen-examples/examples/snapshot_event_handler/pr_tasks.py @@ -1,8 +1,6 @@ import logging -from codegen.agents.code_agent import CodeAgent from codegen.extensions.github.types.pull_request import PullRequestLabeledEvent -from codegen.extensions.langchain.tools import GithubCreatePRCommentTool, GithubCreatePRReviewCommentTool, GithubViewPRTool from codegen.sdk.core.codebase import Codebase logging.basicConfig(level=logging.INFO, force=True) diff --git a/src/codegen/sdk/core/expressions/name.py b/src/codegen/sdk/core/expressions/name.py index df5ef6872..6f065467b 100644 --- a/src/codegen/sdk/core/expressions/name.py +++ b/src/codegen/sdk/core/expressions/name.py @@ -50,6 +50,36 @@ def rename_if_matching(self, old: str, new: str): if self.source == old: self.edit(new) + + def _resolve_conditionals(self,conditional_parent:ConditionalBlock,name:str,original_resolved): + search_limit = conditional_parent.start_byte_for_condition_block + if search_limit>=original_resolved.start_byte: + search_limit=original_resolved.start_byte-1 + if not conditional_parent.is_true_conditional(original_resolved): + #If it's a fake conditional we must skip any potential enveloping conditionals + def get_top_of_fake_chain(conditional,resolved,search_limit=0): + if skip_fake:= conditional.parent_of_type(ConditionalBlock): + if skip_fake.is_true_conditional(resolved): + return skip_fake.start_byte_for_condition_block + search_limit=skip_fake.start_byte_for_condition_block + return get_top_of_fake_chain(skip_fake,conditional,search_limit) + return search_limit + if search_limit:=get_top_of_fake_chain(conditional_parent,original_resolved): + search_limit=search_limit + else: + return + + original_conditional = conditional_parent + while next_resolved:= next(conditional_parent.resolve_name(name,start_byte=search_limit,strict=False),None): + yield next_resolved + next_conditional = next_resolved.parent_of_type(ConditionalBlock) + if not next_conditional or next_conditional == original_conditional: + return + search_limit = next_conditional.start_byte_for_condition_block + if next_conditional and not next_conditional.is_true_conditional(original_resolved): + pass + if search_limit>=next_resolved.start_byte: + search_limit=next_resolved.start_byte-1 @noapidoc @reader def resolve_name(self, name: str, start_byte: int | None = None, strict: bool = True) -> Generator["Symbol | Import | WildcardImport"]: @@ -60,14 +90,8 @@ def resolve_name(self, name: str, start_byte: int | None = None, strict: bool = return if hasattr(resolved_name, "parent") and (conditional_parent := resolved_name.parent_of_type(ConditionalBlock)): - top_of_conditional = conditional_parent.start_byte if self.parent_of_type(ConditionalBlock) == conditional_parent: # Use in the same block, should only depend on the inside of the block return - for other_conditional in conditional_parent.other_possible_blocks: - if cond_name := next(other_conditional.resolve_name(name, start_byte=other_conditional.end_byte_for_condition_block), None): - if cond_name.start_byte >= other_conditional.start_byte: - yield cond_name - top_of_conditional = min(top_of_conditional, other_conditional.start_byte) - yield from self.resolve_name(name, top_of_conditional, strict=False) + yield from self._resolve_conditionals(conditional_parent=conditional_parent,name=name,original_resolved=resolved_name) diff --git a/src/codegen/sdk/core/interfaces/conditional_block.py b/src/codegen/sdk/core/interfaces/conditional_block.py index a11990908..636c5c49a 100644 --- a/src/codegen/sdk/core/interfaces/conditional_block.py +++ b/src/codegen/sdk/core/interfaces/conditional_block.py @@ -2,6 +2,7 @@ from collections.abc import Sequence from codegen.sdk.core.statements.statement import Statement +from codegen.shared.decorators.docs import noapidoc class ConditionalBlock(Statement, ABC): @@ -15,3 +16,14 @@ def other_possible_blocks(self) -> Sequence["ConditionalBlock"]: @property def end_byte_for_condition_block(self) -> int: return self.end_byte + + @property + @noapidoc + def start_byte_for_condition_block(self) -> int: + """Returns the start byte for the specific condition block""" + return self.start_byte + + @noapidoc + def is_true_conditional(self,descendant) -> bool: + """Returns if this conditional is truly conditional, this is necessary as an override for things like finally statements that share a parent with try blocks""" + return True diff --git a/src/codegen/sdk/core/interfaces/editable.py b/src/codegen/sdk/core/interfaces/editable.py index 22ae37f51..7ce29362e 100644 --- a/src/codegen/sdk/core/interfaces/editable.py +++ b/src/codegen/sdk/core/interfaces/editable.py @@ -1106,6 +1106,14 @@ def parent_of_types(self, types: set[type[T]]) -> T | None: return self.parent.parent_of_types(types) return None + def is_child_of(self, instance: Editable) -> bool: + if not self.parent: + return False + if self.parent is instance: + return True + else: + return self.parent.is_child_of(instance=instance) + @reader def ancestors(self, type: type[T]) -> list[T]: """Find all ancestors of the node of the given type. Does not return itself""" diff --git a/src/codegen/sdk/core/statements/if_block_statement.py b/src/codegen/sdk/core/statements/if_block_statement.py index e3becc13c..d83fc5227 100644 --- a/src/codegen/sdk/core/statements/if_block_statement.py +++ b/src/codegen/sdk/core/statements/if_block_statement.py @@ -297,3 +297,10 @@ def end_byte_for_condition_block(self) -> int: if self.is_if_statement: return self.consequence_block.end_byte return self.end_byte + + @property + @noapidoc + def start_byte_for_condition_block(self) -> int: + if self.is_if_statement: + return self.consequence_block.start_byte + return self.start_byte diff --git a/src/codegen/sdk/core/statements/try_catch_statement.py b/src/codegen/sdk/core/statements/try_catch_statement.py index 177ddde68..7e9a2e1d9 100644 --- a/src/codegen/sdk/core/statements/try_catch_statement.py +++ b/src/codegen/sdk/core/statements/try_catch_statement.py @@ -1,13 +1,13 @@ from __future__ import annotations from abc import ABC -from typing import TYPE_CHECKING, Generic, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar, override from codegen.sdk.core.interfaces.conditional_block import ConditionalBlock from codegen.sdk.core.interfaces.has_block import HasBlock from codegen.sdk.core.statements.block_statement import BlockStatement from codegen.sdk.core.statements.statement import StatementType -from codegen.shared.decorators.docs import apidoc +from codegen.shared.decorators.docs import apidoc, noapidoc if TYPE_CHECKING: from codegen.sdk.core.detached_symbols.code_block import CodeBlock @@ -27,3 +27,26 @@ class TryCatchStatement(ConditionalBlock, BlockStatement[Parent], HasBlock, ABC, statement_type = StatementType.TRY_CATCH_STATEMENT finalizer: BlockStatement | None = None + + @noapidoc + @override + def is_true_conditional(self,descendant) -> bool: + if descendant.is_child_of(self.finalizer): + return False + return True + + @property + @noapidoc + def end_byte_for_condition_block(self) -> int: + if self.code_block: + return self.code_block.end_byte + else: + return self.end_byte + + @property + @noapidoc + def start_byte_for_condition_block(self) -> int: + if self.code_block: + return self.code_block.start_byte-1 + else: + return self.start_byte diff --git a/src/codegen/sdk/python/statements/catch_statement.py b/src/codegen/sdk/python/statements/catch_statement.py index 9ebee3f3f..8c3dc7d19 100644 --- a/src/codegen/sdk/python/statements/catch_statement.py +++ b/src/codegen/sdk/python/statements/catch_statement.py @@ -31,3 +31,4 @@ def __init__(self, ts_node: PyNode, file_node_id: NodeId, ctx: CodebaseContext, @property def other_possible_blocks(self) -> list[ConditionalBlock]: return [clause for clause in self.parent.except_clauses if clause != self] + [self.parent] + diff --git a/src/codegen/sdk/python/statements/try_catch_statement.py b/src/codegen/sdk/python/statements/try_catch_statement.py index b54051f96..f8e974fda 100644 --- a/src/codegen/sdk/python/statements/try_catch_statement.py +++ b/src/codegen/sdk/python/statements/try_catch_statement.py @@ -68,6 +68,7 @@ def _compute_dependencies(self, usage_type: UsageKind | None = None, dest: HasNa if self.finalizer: self.finalizer._compute_dependencies(usage_type, dest) + @property @noapidoc def descendant_symbols(self) -> list[Importable]: @@ -103,10 +104,3 @@ def nested_code_blocks(self) -> list[PyCodeBlock]: @property def other_possible_blocks(self) -> Sequence[ConditionalBlock]: return self.except_clauses - - @property - def end_byte_for_condition_block(self) -> int: - if self.code_block: - return self.code_block.end_byte - else: - return self.end_byte diff --git a/src/codegen/sdk/typescript/statements/try_catch_statement.py b/src/codegen/sdk/typescript/statements/try_catch_statement.py index 8f499da04..c8a04246d 100644 --- a/src/codegen/sdk/typescript/statements/try_catch_statement.py +++ b/src/codegen/sdk/typescript/statements/try_catch_statement.py @@ -36,7 +36,7 @@ class TSTryCatchStatement(TryCatchStatement["TSCodeBlock"], TSBlockStatement): def __init__(self, ts_node: TSNode, file_node_id: NodeId, ctx: CodebaseContext, parent: TSCodeBlock, pos: int | None = None) -> None: super().__init__(ts_node, file_node_id, ctx, parent, pos) if handler_node := self.ts_node.child_by_field_name("handler"): - self.catch = TSCatchStatement(handler_node, file_node_id, ctx, self.code_block) + self.catch = TSCatchStatement(handler_node, file_node_id, ctx, self) if finalizer_node := self.ts_node.child_by_field_name("finalizer"): self.finalizer = TSBlockStatement(finalizer_node, file_node_id, ctx, self.code_block) @@ -101,10 +101,3 @@ def other_possible_blocks(self) -> Sequence[ConditionalBlock]: return [self.catch] else: return [] - - @property - def end_byte_for_condition_block(self) -> int: - if self.code_block: - return self.code_block.end_byte - else: - return self.end_byte diff --git a/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py b/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py index 22f8af23f..ea9ac3c93 100644 --- a/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py +++ b/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py @@ -266,3 +266,56 @@ def test_if_else_reassigment_handling_nested_usage(tmpdir) -> None: second = file.symbols[1] assert len(first.usages) == 0 assert second.usages[0].match == pyspark_arg + + +def test_if_else_reassigment_inside_func_with_external_element(tmpdir) -> None: + content = """ + PYSPARK="0" + def foo(): + if True: + PYSPARK = True + else: + PYSPARK = False + print(PYSPARK) + + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + pyspark_arg = funct_call.args.children[0] + func = file.get_function("foo") + for assign in func.valid_symbol_names[:-1]: + assign.usages[0] == pyspark_arg + + + +def test_if_else_reassigment_handling_double_nested(tmpdir) -> None: + content = """ + if False: + PYSPARK = "TEST1" + elif True: + PYSPARK = "TEST2" + + if True: + PYSPARK = True + elif None: + if True: + PYSPARK = True + elif None: + if True: + PYSPARK = True + elif None: + PYSPARK = False + + print(PYSPARK) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + symbo = file.get_symbol("PYSPARK") + funct_call = file.function_calls[0] + pyspark_arg = funct_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg diff --git a/tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py b/tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py index 76bb5d0f4..cdc44b8c3 100644 --- a/tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py +++ b/tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py @@ -95,3 +95,232 @@ def test_try_except_reassigment_handling(tmpdir) -> None: for symb in file.symbols: usage = symb.usages[0] assert usage.match == pyspark_arg + + + +def test_try_except_reassigment_handling_function(tmpdir) -> None: + content = """ + try: + def process(): + print('try') + except ImportError: + def process(): + print('except') + finally: + def process(): + print('finally') + + process() + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + process = file.get_function("process") + funct_call = file.function_calls[3] # Skip the print calls + for idx,func in enumerate(file.functions): + if idx == 2: + usage = func.usages[0] + assert usage.match == funct_call + else: + assert not func.usages + + + +def test_try_except_reassigment_handling_inside_func(tmpdir) -> None: + content = """ + def get_result(): + result = None + try: + result = "success" + except Exception: + result = "error" + finally: + result = "done" + return result + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + get_result = file.get_function("get_result") + return_stmt = get_result.code_block.statements[-1] + result_var = return_stmt.value + for idx,symb in enumerate(file.symbols(True)): + if symb.name=='result': + if idx==4: + # The only usage is in the finally block + assert len(symb.usages) > 0 + assert any(usage.match == result_var for usage in symb.usages) + else: + assert len(symb.usages)==0 + + + +def test_try_except_reassigment_handling_nested(tmpdir) -> None: + content = """ + try: + RESULT = "outer try" + try: + RESULT = "inner try" + except Exception as e: + RESULT = "inner except" + except Exception as e: + RESULT = "outer except" + + print(RESULT) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + result_arg = funct_call.args.children[0] + for symb in file.symbols: + if symb.name == "RESULT": + usage = symb.usages[0] + assert usage.match == result_arg + + + +def test_try_except_reassigment_with_finally(tmpdir) -> None: + content = """ + try: + STATUS = "trying" + except Exception: + STATUS = "error" + finally: + STATUS = "done" + + print(STATUS) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + status_arg = funct_call.args.children[0] + for idx,symb in enumerate(file.symbols(True)): + if symb.name=='STATUS': + if idx==2: + # The only usage is in the finally block + assert len(symb.usages) > 0 + assert any(usage.match == status_arg for usage in symb.usages) + else: + assert len(symb.usages)==0 + + +def test_try_except_reassigment_with_finally_nested(tmpdir) -> None: + content = """ + try: + STATUS = "trying" + except Exception: + STATUS = "error" + try: + STATUS = "trying" + except Exception: + STATUS = "error" + finally: + STATUS = "done" + + print(STATUS) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + status_arg = funct_call.args.children[0] + for idx,symb in enumerate(file.symbols(True)): + if symb.name=='STATUS': + if idx==0 or idx==4: + # The only usage is in the finally block + assert len(symb.usages) > 0 + assert any(usage.match == status_arg for usage in symb.usages) + else: + assert len(symb.usages)==0 + +def test_try_except_reassigment_with_finally_nested_deeper(tmpdir) -> None: + content = """ + try: + STATUS = "trying" + except Exception: + STATUS = "error" + try: + STATUS = "trying_lvl2" + except Exception: + STATUS = "error_lvl2" + finally: + try: + STATUS = "trying_lvl3" + except Exception: + STATUS = "error_lvl3" + finally: + STATUS = "done_lvl3" + + print(STATUS) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + status_arg = funct_call.args.children[0] + for idx,symb in enumerate(file.symbols(True)): + if symb.name=='STATUS': + if idx==0 or idx==6: + # The only usage is in the finally block + assert len(symb.usages) > 0 + assert any(usage.match == status_arg for usage in symb.usages) + else: + assert len(symb.usages)==0 + + +def test_try_except_reassigment_with_finally_secondary_nested_deeper(tmpdir) -> None: + content = """ + try: + STATUS = "trying" + except Exception: + STATUS = "error" + try: + STATUS = "trying_lvl2" + except Exception: + STATUS = "error_lvl2" + finally: + try: + STATUS = "trying_lvl3" + except Exception: + STATUS = "error_lvl3" + + print(STATUS) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + status_arg = funct_call.args.children[0] + for idx,symb in enumerate(file.symbols(True)): + if symb.name=='STATUS': + assert len(symb.usages) > 0 + assert any(usage.match == status_arg for usage in symb.usages) + + + + +def test_try_except_multiple_reassigment(tmpdir) -> None: + content = """ + try: + VALUE = "first try" + except Exception: + VALUE = "first except" + + try: + VALUE = "second try" + except Exception: + VALUE = "second except" + + print(VALUE) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + value_arg = funct_call.args.children[0] + for symb in file.symbols: + if symb.name == "VALUE": + usage = symb.usages[0] + assert usage.match == value_arg diff --git a/tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py b/tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py index be972cffd..7be510fa9 100644 --- a/tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py +++ b/tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py @@ -77,3 +77,142 @@ def test_match_reassigment_handling(tmpdir) -> None: for symb in file.symbols[1:]: usage = symb.usages[0] assert usage.match == pyspark_arg + + + +def test_match_reassigment_handling_function(tmpdir) -> None: + content = """ +action = "create" +match action: + case "create": + def process(): + print("creating") + case "update": + def process(): + print("updating") + case _: + def process(): + print("unknown action") + +process() + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + process = file.get_function("process") + funct_call = file.function_calls[3] # Skip the print calls + for func in file.functions: + usage = func.usages[0] + assert usage.match == funct_call + + + +def test_match_reassigment_handling_inside_func(tmpdir) -> None: + content = """ +def get_message(status): + result = None + match status: + case "success": + result = "Operation successful" + case "error": + result = "An error occurred" + case _: + result = "Unknown status" + return result + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + get_message = file.get_function("get_message") + return_stmt = get_message.code_block.statements[-1] + result_var = return_stmt.value + for symb in file.symbols(True): + if symb.name == "result": + assert len(symb.usages) > 0 + assert any(usage.match == result_var for usage in symb.usages) + + + +def test_match_reassigment_handling_nested(tmpdir) -> None: + content = """ +outer = "first" +match outer: + case "first": + RESULT = "outer first" + inner = "second" + match inner: + case "second": + RESULT = "inner second" + case _: + RESULT = "inner default" + case _: + RESULT = "outer default" + +print(RESULT) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + result_arg = funct_call.args.children[0] + for symb in file.symbols: + if symb.name == "RESULT": + usage = symb.usages[0] + assert usage.match == result_arg + + + +def test_match_multiple_reassigment(tmpdir) -> None: + content = """ +first = "a" +match first: + case "a": + VALUE = "first a" + case _: + VALUE = "first default" + +second = "b" +match second: + case "b": + VALUE = "second b" + case _: + VALUE = "second default" + +print(VALUE) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + value_arg = funct_call.args.children[0] + for symb in file.symbols: + if symb.name == "VALUE": + usage = symb.usages[0] + assert usage.match == value_arg + + + +def test_match_complex_pattern_reassigment(tmpdir) -> None: + content = """ +data = {"type": "user", "name": "John", "age": 30} +match data: + case {"type": "user", "name": name, "age": age} if age > 18: + STATUS = "adult user" + case {"type": "user", "name": name}: + STATUS = "user with unknown age" + case {"type": "admin"}: + STATUS = "admin" + case _: + STATUS = "unknown" + +print(STATUS) + """ + + with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: + file = codebase.get_file("test.py") + funct_call = file.function_calls[0] + status_arg = funct_call.args.children[0] + for symb in file.symbols: + if symb.name == "STATUS": + usage = symb.usages[0] + assert usage.match == status_arg diff --git a/tests/unit/codegen/sdk/typescript/statements/if_block_statement/test_if_block_statement_properties.py b/tests/unit/codegen/sdk/typescript/statements/if_block_statement/test_if_block_statement_properties.py index 216741d61..2a09ff205 100644 --- a/tests/unit/codegen/sdk/typescript/statements/if_block_statement/test_if_block_statement_properties.py +++ b/tests/unit/codegen/sdk/typescript/statements/if_block_statement/test_if_block_statement_properties.py @@ -140,3 +140,193 @@ def test_get_alternative_if_blocks_from_codeblock(tmpdir) -> None: assert len(alt_blocks[2].alternative_blocks) == 0 assert len(alt_blocks[2].elif_statements) == 0 assert alt_blocks[2].else_statement is None + + +def test_if_else_reassignment_handling(tmpdir) -> None: + # language=typescript + content = """ +if (true) { + PYSPARK = true; +} else if (false) { + PYSPARK = false; +} else { + PYSPARK = null; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + +def test_if_else_reassignment_handling_function(tmpdir) -> None: + # language=typescript + content = """ +if (true) { + function foo() { + console.log('t'); + } +} else if (false) { + function foo() { + console.log('t'); + } +} else { + function foo() { + console.log('t'); + } +} +foo(); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + foo = file.get_function("foo") + func_call = file.function_calls[3] + for func in file.functions: + usage = func.usages[0] + assert usage.match == func_call + + +def test_if_else_reassignment_handling_inside_func(tmpdir) -> None: + # language=typescript + content = """ +function foo(a) { + a = 1; + if (xyz) { + b = 1; + } else { + b = 2; + } + f(a); // a resolves to 1 name + f(b); // b resolves to 2 possible names +} + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + foo = file.get_function("foo") + assert foo + assert len(foo.parameters[0].usages) == 0 + func_call_a = foo.function_calls[0].args[0] + func_call_b = foo.function_calls[1] + for symbol in file.symbols(True): + if symbol.name == "a": + assert len(symbol.usages) == 1 + symbol.usages[0].match == func_call_a + elif symbol.name == "b": + assert len(symbol.usages) == 1 + symbol.usages[0].match == func_call_b + + +def test_if_else_reassignment_handling_partial_if(tmpdir) -> None: + # language=typescript + content = """ +PYSPARK = "TEST"; +if (true) { + PYSPARK = true; +} else if (null) { + PYSPARK = false; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + +def test_if_else_reassignment_handling_solo_if(tmpdir) -> None: + # language=typescript + content = """ +PYSPARK = "TEST"; +if (true) { + PYSPARK = true; +} +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + +def test_if_else_reassignment_handling_double(tmpdir) -> None: + # language=typescript + content = """ +if (false) { + PYSPARK = "TEST1"; +} else if (true) { + PYSPARK = "TEST2"; +} + +if (true) { + PYSPARK = true; +} else if (null) { + PYSPARK = false; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + +def test_if_else_reassignment_handling_nested_usage(tmpdir) -> None: + # language=typescript + content = """ +if (true) { + PYSPARK = true; +} else if (null) { + PYSPARK = false; + console.log(PYSPARK); +} + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + first = file.symbols[0] + second = file.symbols[1] + assert len(first.usages) == 0 + assert second.usages[0].match == pyspark_arg + + +def test_if_else_reassignment_inside_func_with_external_element(tmpdir) -> None: + # language=typescript + content = """ +PYSPARK = "0"; +function foo() { + if (true) { + PYSPARK = true; + } else { + PYSPARK = false; + } + console.log(PYSPARK); +} + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + func = file.get_function("foo") + for assign in func.valid_symbol_names[:-1]: + assign.usages[0] == pyspark_arg diff --git a/tests/unit/codegen/sdk/typescript/statements/switch_statement/test_switch_statement.py b/tests/unit/codegen/sdk/typescript/statements/switch_statement/test_switch_statement.py index 6ac3a94af..0c02aa484 100644 --- a/tests/unit/codegen/sdk/typescript/statements/switch_statement/test_switch_statement.py +++ b/tests/unit/codegen/sdk/typescript/statements/switch_statement/test_switch_statement.py @@ -89,3 +89,191 @@ def test_switch_statement_dependencies(tmpdir) -> None: selectFruit = file.get_function("selectFruit") assert len(selectFruit.dependencies) == 1 assert selectFruit.dependencies[0] == file.get_global_var("fruit") + + +def test_switch_reassignment_handling(tmpdir) -> None: + # language=typescript + content = """ +const filter = 1; +switch (filter) { + case 1: + PYSPARK = true; + break; + case 2: + PYSPARK = false; + break; + default: + PYSPARK = null; + break; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols[1:]: # Skip the first symbol which is 'filter' + usage = symb.usages[0] + assert usage.match == pyspark_arg + + + +def test_switch_reassignment_handling_function(tmpdir) -> None: + # language=typescript + content = """ +const type = "handler"; +switch (type) { + case "handler": + function process(){ return "handler"; } + break; + case "processor": + function process(){ return "processor"; } + break; + default: + function process(){ return "default"; } + break; +} +process(); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + process = file.get_function("process") + func_call = file.function_calls[0] + for func in file.functions: + if func.name == "process": + usage = func.usages[0] + assert usage.match == func_call + + + +def test_switch_reassignment_handling_inside_func(tmpdir) -> None: + # language=typescript + content = """ +function getStatus(code) { + let status; + switch (code) { + case 200: + status = "OK"; + break; + case 404: + status = "Not Found"; + break; + default: + status = "Unknown"; + break; + } + return status; +} + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + get_status = file.get_function("getStatus") + return_stmt = get_status.code_block.statements[-1] + status_var = return_stmt.value + for symb in file.symbols(True): + if symb.name == "status": + assert len(symb.usages) > 0 + assert any(usage.match == status_var for usage in symb.usages) + + + +def test_switch_reassignment_handling_nested(tmpdir) -> None: + # language=typescript + content = """ +const outer = 1; +switch (outer) { + case 1: + RESULT = "outer 1"; + const inner = 2; + switch (inner) { + case 2: + RESULT = "inner 2"; + break; + default: + RESULT = "inner default"; + break; + } + break; + default: + RESULT = "outer default"; + break; +} +console.log(RESULT); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + result_arg = func_call.args.children[0] + for symb in file.symbols: + if symb.name == "RESULT": + usage = symb.usages[0] + assert usage.match == result_arg + + + +def test_switch_multiple_reassignment(tmpdir) -> None: + # language=typescript + content = """ +const first = 1; +switch (first) { + case 1: + VALUE = "first 1"; + break; + default: + VALUE = "first default"; + break; +} + +const second = 2; +switch (second) { + case 2: + VALUE = "second 2"; + break; + default: + VALUE = "second default"; + break; +} + +console.log(VALUE); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + value_arg = func_call.args.children[0] + for symb in file.symbols: + if symb.name == "VALUE": + usage = symb.usages[0] + assert usage.match == value_arg + + + +def test_switch_fallthrough_reassignment(tmpdir) -> None: + # language=typescript + content = """ +const code = 1; +switch (code) { + case 1: + STATUS = "Processing"; + // Fallthrough intentional + case 2: + STATUS = "Almost done"; + // Fallthrough intentional + case 3: + STATUS = "Complete"; + break; + default: + STATUS = "Unknown"; + break; +} +console.log(STATUS); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + status_arg = func_call.args.children[0] + for symb in file.symbols: + if symb.name == "STATUS": + usage = symb.usages[0] + assert usage.match == status_arg diff --git a/tests/unit/codegen/sdk/typescript/statements/try_catch_statement/test_try_catch_statement.py b/tests/unit/codegen/sdk/typescript/statements/try_catch_statement/test_try_catch_statement.py index b7ddfa236..b5b766621 100644 --- a/tests/unit/codegen/sdk/typescript/statements/try_catch_statement/test_try_catch_statement.py +++ b/tests/unit/codegen/sdk/typescript/statements/try_catch_statement/test_try_catch_statement.py @@ -77,3 +77,218 @@ def test_try_catch_statement_dependencies(tmpdir) -> None: example = file.get_function("example") assert len(example.dependencies) == 1 assert example.dependencies[0] == file.get_global_var("globalVar") + + +def test_try_catch_statement_dependencies_external(tmpdir) -> None: + # language=typescript + content = """ +let test: Record | undefined; +try { + test = JSON.parse(test); + if (Object.keys(test).length === 0) { + test = undefined; + } +} catch (e) { + console.error("Error parsing test", e); + test = undefined; +} +let use = test + + + + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("test") + func_call = file.function_calls[0] + assign_val = file.symbols[-1].value + for symb in file.symbols[:-1]: + assert any(usage.match == assign_val for usage in symb.usages) + + +def test_try_catch_reassignment_handling(tmpdir) -> None: + # language=typescript + content = """ +try { + PYSPARK = true; +} catch (error) { + PYSPARK = false; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + symbol = file.get_symbol("PYSPARK") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + + +def test_try_catch_reassignment_handling_function(tmpdir) -> None: + # language=typescript + content = """ +try { + function foo() { + console.log('try'); + } +} catch (error) { + function foo() { + console.log('catch'); + } +} +foo(); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + foo = file.get_function("foo") + func_call = file.function_calls[2] + for func in file.functions: + assert func.usages + usage = func.usages[0] + assert usage.match == func_call + + + +def test_try_catch_reassignment_handling_function_finally(tmpdir) -> None: + # language=typescript + content = """ +try { + function foo() { + console.log('try'); + } +} catch (error) { + function foo() { + console.log('catch'); + } +} finally { + function foo() { + console.log('finally'); + } +} +foo(); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + foo = file.get_function("foo") + func_call = file.function_calls[3] + for idx,func in enumerate(file.functions): + if idx == 2: + assert func.usages + usage = func.usages[0] + assert usage.match == func_call + else: + assert len(func.usages)==0 + + + +def test_try_catch_reassignment_handling_nested(tmpdir) -> None: + # language=typescript + content = """ +try { + PYSPARK = true; + try { + PYSPARK = "nested"; + } catch (innerError) { + PYSPARK = "inner catch"; + } +} catch (error) { + PYSPARK = false; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg + + + +def test_try_catch_reassignment_handling_inside_func(tmpdir) -> None: + # language=typescript + content = """ +function process() { + let result; + try { + result = "success"; + } catch (error) { + result = "error"; + } finally { + result = "done"; + } + return result; +} + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + process_func = file.get_function("process") + return_stmt = process_func.code_block.statements[-1] + result_var = return_stmt.value + for idx,symb in enumerate(file.symbols(True)): + if symb.name == "result": + if idx==4: + #Only finally triggers + assert len(symb.usages) > 0 + assert any(usage.match == result_var for usage in symb.usages) + else: + assert len(symb.usages)==0 + + + +def test_try_catch_reassignment_with_finally(tmpdir) -> None: + # language=typescript + content = """ +try { + PYSPARK = true; +} catch (error) { + PYSPARK = false; +} finally { + PYSPARK = "finally"; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for idx,symb in enumerate(file.symbols): + if idx==2: + usage = symb.usages[0] + assert usage.match == pyspark_arg + else: + assert not symb.usages + + + +def test_try_catch_multiple_reassignment(tmpdir) -> None: + # language=typescript + content = """ +try { + PYSPARK = true; +} catch (error) { + PYSPARK = false; +} + +try { + PYSPARK = "second try"; +} catch (error) { + PYSPARK = "second catch"; +} + +console.log(PYSPARK); + """ + with get_codebase_session(tmpdir=tmpdir, files={"test.ts": content}, programming_language=ProgrammingLanguage.TYPESCRIPT) as codebase: + file = codebase.get_file("test.ts") + func_call = file.function_calls[0] + pyspark_arg = func_call.args.children[0] + for symb in file.symbols: + usage = symb.usages[0] + assert usage.match == pyspark_arg From 85fad91d984b127045c62f2a70fd89ab3822ae89 Mon Sep 17 00:00:00 2001 From: tkucar Date: Fri, 14 Mar 2025 15:56:07 +0100 Subject: [PATCH 02/35] tests reorg, docstring --- src/codegen/sdk/core/expressions/name.py | 44 +++++++++++++++---- .../sdk/core/interfaces/conditional_block.py | 9 +++- src/codegen/sdk/core/interfaces/editable.py | 1 + .../assignment_statement/__init__.py | 0 .../python/statements/attribute/__init__.py | 0 .../statements/if_block_statement/__init__.py | 0 .../statements/import_statement/__init__.py | 0 .../test_assignment_statement_remove.py | 0 .../test_assignment_statement_rename.py | 0 ...signment_statement_set_assignment_value.py | 0 .../test_attribute_assignment_value.py | 0 .../test_attribute_get_usages.py | 0 .../test_attribute_properties.py | 0 .../{attribute => }/test_attribute_remove.py | 0 .../{attribute => }/test_attribute_rename.py | 0 .../test_attribute_set_type_annotation.py | 0 .../test_for_loop_statement.py | 0 .../test_if_block_reduce_block.py | 0 .../test_if_block_statement_properties.py | 0 .../test_import_statement.py | 0 .../test_match_statement.py | 0 .../test_try_catch_statement.py | 0 .../test_while_statement.py | 0 .../test_with_statement_properties.py | 0 .../statements/with_statement/__init__.py | 0 25 files changed, 43 insertions(+), 11 deletions(-) delete mode 100644 tests/unit/codegen/sdk/python/statements/assignment_statement/__init__.py delete mode 100644 tests/unit/codegen/sdk/python/statements/attribute/__init__.py delete mode 100644 tests/unit/codegen/sdk/python/statements/if_block_statement/__init__.py delete mode 100644 tests/unit/codegen/sdk/python/statements/import_statement/__init__.py rename tests/unit/codegen/sdk/python/statements/{assignment_statement => }/test_assignment_statement_remove.py (100%) rename tests/unit/codegen/sdk/python/statements/{assignment_statement => }/test_assignment_statement_rename.py (100%) rename tests/unit/codegen/sdk/python/statements/{assignment_statement => }/test_assignment_statement_set_assignment_value.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_assignment_value.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_get_usages.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_properties.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_remove.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_rename.py (100%) rename tests/unit/codegen/sdk/python/statements/{attribute => }/test_attribute_set_type_annotation.py (100%) rename tests/unit/codegen/sdk/python/statements/{for_loop_statement => }/test_for_loop_statement.py (100%) rename tests/unit/codegen/sdk/python/statements/{if_block_statement => }/test_if_block_reduce_block.py (100%) rename tests/unit/codegen/sdk/python/statements/{if_block_statement => }/test_if_block_statement_properties.py (100%) rename tests/unit/codegen/sdk/python/statements/{import_statement => }/test_import_statement.py (100%) rename tests/unit/codegen/sdk/python/statements/{try_catch_statement => }/test_match_statement.py (100%) rename tests/unit/codegen/sdk/python/statements/{match_statement => }/test_try_catch_statement.py (100%) rename tests/unit/codegen/sdk/python/statements/{while_statement => }/test_while_statement.py (100%) rename tests/unit/codegen/sdk/python/statements/{with_statement => }/test_with_statement_properties.py (100%) delete mode 100644 tests/unit/codegen/sdk/python/statements/with_statement/__init__.py diff --git a/src/codegen/sdk/core/expressions/name.py b/src/codegen/sdk/core/expressions/name.py index 6f065467b..13ff6a392 100644 --- a/src/codegen/sdk/core/expressions/name.py +++ b/src/codegen/sdk/core/expressions/name.py @@ -51,26 +51,50 @@ def rename_if_matching(self, old: str, new: str): self.edit(new) - def _resolve_conditionals(self,conditional_parent:ConditionalBlock,name:str,original_resolved): + @noapidoc + def _resolve_conditionals(self, conditional_parent: ConditionalBlock, name: str, original_resolved): + """Resolves name references within conditional blocks by traversing the conditional chain. + + This method handles name resolution within conditional blocks (like if/elif/else statements) by: + 1. Finding the appropriate search boundary based on the conditional block's position + 2. Handling "fake" conditionals by traversing up the conditional chain + 3. Yielding resolved names while respecting conditional block boundaries + + Args: + conditional_parent (ConditionalBlock): The parent conditional block containing the name reference + name (str): The name being resolved + original_resolved: The originally resolved symbol that triggered this resolution + + Yields: + Symbol | Import | WildcardImport: Resolved symbols found within the conditional blocks + + Notes: + - A "fake" conditional is one where is_true_conditional() returns False + - The search_limit ensures we don't resolve names that appear after our target + - The method stops when it either: + a) Reaches the top of the conditional chain + b) Returns to the original conditional block + c) Can't find any more resolutions + """ search_limit = conditional_parent.start_byte_for_condition_block - if search_limit>=original_resolved.start_byte: - search_limit=original_resolved.start_byte-1 + if search_limit >= original_resolved.start_byte: + search_limit = original_resolved.start_byte-1 if not conditional_parent.is_true_conditional(original_resolved): #If it's a fake conditional we must skip any potential enveloping conditionals def get_top_of_fake_chain(conditional,resolved,search_limit=0): - if skip_fake:= conditional.parent_of_type(ConditionalBlock): + if skip_fake := conditional.parent_of_type(ConditionalBlock): if skip_fake.is_true_conditional(resolved): return skip_fake.start_byte_for_condition_block search_limit=skip_fake.start_byte_for_condition_block return get_top_of_fake_chain(skip_fake,conditional,search_limit) return search_limit - if search_limit:=get_top_of_fake_chain(conditional_parent,original_resolved): - search_limit=search_limit + if search_limit := get_top_of_fake_chain(conditional_parent,original_resolved): + search_limit = search_limit else: return original_conditional = conditional_parent - while next_resolved:= next(conditional_parent.resolve_name(name,start_byte=search_limit,strict=False),None): + while next_resolved := next(conditional_parent.resolve_name(name,start_byte=search_limit,strict=False),None): yield next_resolved next_conditional = next_resolved.parent_of_type(ConditionalBlock) if not next_conditional or next_conditional == original_conditional: @@ -78,8 +102,10 @@ def get_top_of_fake_chain(conditional,resolved,search_limit=0): search_limit = next_conditional.start_byte_for_condition_block if next_conditional and not next_conditional.is_true_conditional(original_resolved): pass - if search_limit>=next_resolved.start_byte: - search_limit=next_resolved.start_byte-1 + if search_limit >= next_resolved.start_byte: + search_limit = next_resolved.start_byte - 1 + + @noapidoc @reader def resolve_name(self, name: str, start_byte: int | None = None, strict: bool = True) -> Generator["Symbol | Import | WildcardImport"]: diff --git a/src/codegen/sdk/core/interfaces/conditional_block.py b/src/codegen/sdk/core/interfaces/conditional_block.py index 636c5c49a..a84da4dce 100644 --- a/src/codegen/sdk/core/interfaces/conditional_block.py +++ b/src/codegen/sdk/core/interfaces/conditional_block.py @@ -6,7 +6,9 @@ class ConditionalBlock(Statement, ABC): - """An interface for any code block that might not be executed in the code, e.g if block/else block/try block/catch block ect.""" + """An interface for any code block that might not be executed in the code, + e.g if block/else block, try block/catch block ect. + """ @property @abstractmethod @@ -25,5 +27,8 @@ def start_byte_for_condition_block(self) -> int: @noapidoc def is_true_conditional(self,descendant) -> bool: - """Returns if this conditional is truly conditional, this is necessary as an override for things like finally statements that share a parent with try blocks""" + """Returns if this conditional is truly conditional, + this is necessary as an override for things like finally + statements that share a parent with try blocks + """ return True diff --git a/src/codegen/sdk/core/interfaces/editable.py b/src/codegen/sdk/core/interfaces/editable.py index 7ce29362e..86e08c844 100644 --- a/src/codegen/sdk/core/interfaces/editable.py +++ b/src/codegen/sdk/core/interfaces/editable.py @@ -1107,6 +1107,7 @@ def parent_of_types(self, types: set[type[T]]) -> T | None: return None def is_child_of(self, instance: Editable) -> bool: + """Checks if this node is a descendant of the given editable instance in the AST.""" if not self.parent: return False if self.parent is instance: diff --git a/tests/unit/codegen/sdk/python/statements/assignment_statement/__init__.py b/tests/unit/codegen/sdk/python/statements/assignment_statement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/codegen/sdk/python/statements/attribute/__init__.py b/tests/unit/codegen/sdk/python/statements/attribute/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/codegen/sdk/python/statements/if_block_statement/__init__.py b/tests/unit/codegen/sdk/python/statements/if_block_statement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/codegen/sdk/python/statements/import_statement/__init__.py b/tests/unit/codegen/sdk/python/statements/import_statement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_remove.py b/tests/unit/codegen/sdk/python/statements/test_assignment_statement_remove.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_remove.py rename to tests/unit/codegen/sdk/python/statements/test_assignment_statement_remove.py diff --git a/tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_rename.py b/tests/unit/codegen/sdk/python/statements/test_assignment_statement_rename.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_rename.py rename to tests/unit/codegen/sdk/python/statements/test_assignment_statement_rename.py diff --git a/tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_set_assignment_value.py b/tests/unit/codegen/sdk/python/statements/test_assignment_statement_set_assignment_value.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/assignment_statement/test_assignment_statement_set_assignment_value.py rename to tests/unit/codegen/sdk/python/statements/test_assignment_statement_set_assignment_value.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_assignment_value.py b/tests/unit/codegen/sdk/python/statements/test_attribute_assignment_value.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_assignment_value.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_assignment_value.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_get_usages.py b/tests/unit/codegen/sdk/python/statements/test_attribute_get_usages.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_get_usages.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_get_usages.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_properties.py b/tests/unit/codegen/sdk/python/statements/test_attribute_properties.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_properties.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_properties.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_remove.py b/tests/unit/codegen/sdk/python/statements/test_attribute_remove.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_remove.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_remove.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_rename.py b/tests/unit/codegen/sdk/python/statements/test_attribute_rename.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_rename.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_rename.py diff --git a/tests/unit/codegen/sdk/python/statements/attribute/test_attribute_set_type_annotation.py b/tests/unit/codegen/sdk/python/statements/test_attribute_set_type_annotation.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/attribute/test_attribute_set_type_annotation.py rename to tests/unit/codegen/sdk/python/statements/test_attribute_set_type_annotation.py diff --git a/tests/unit/codegen/sdk/python/statements/for_loop_statement/test_for_loop_statement.py b/tests/unit/codegen/sdk/python/statements/test_for_loop_statement.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/for_loop_statement/test_for_loop_statement.py rename to tests/unit/codegen/sdk/python/statements/test_for_loop_statement.py diff --git a/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_reduce_block.py b/tests/unit/codegen/sdk/python/statements/test_if_block_reduce_block.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_reduce_block.py rename to tests/unit/codegen/sdk/python/statements/test_if_block_reduce_block.py diff --git a/tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py b/tests/unit/codegen/sdk/python/statements/test_if_block_statement_properties.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/if_block_statement/test_if_block_statement_properties.py rename to tests/unit/codegen/sdk/python/statements/test_if_block_statement_properties.py diff --git a/tests/unit/codegen/sdk/python/statements/import_statement/test_import_statement.py b/tests/unit/codegen/sdk/python/statements/test_import_statement.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/import_statement/test_import_statement.py rename to tests/unit/codegen/sdk/python/statements/test_import_statement.py diff --git a/tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py b/tests/unit/codegen/sdk/python/statements/test_match_statement.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/try_catch_statement/test_match_statement.py rename to tests/unit/codegen/sdk/python/statements/test_match_statement.py diff --git a/tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py b/tests/unit/codegen/sdk/python/statements/test_try_catch_statement.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/match_statement/test_try_catch_statement.py rename to tests/unit/codegen/sdk/python/statements/test_try_catch_statement.py diff --git a/tests/unit/codegen/sdk/python/statements/while_statement/test_while_statement.py b/tests/unit/codegen/sdk/python/statements/test_while_statement.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/while_statement/test_while_statement.py rename to tests/unit/codegen/sdk/python/statements/test_while_statement.py diff --git a/tests/unit/codegen/sdk/python/statements/with_statement/test_with_statement_properties.py b/tests/unit/codegen/sdk/python/statements/test_with_statement_properties.py similarity index 100% rename from tests/unit/codegen/sdk/python/statements/with_statement/test_with_statement_properties.py rename to tests/unit/codegen/sdk/python/statements/test_with_statement_properties.py diff --git a/tests/unit/codegen/sdk/python/statements/with_statement/__init__.py b/tests/unit/codegen/sdk/python/statements/with_statement/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 4df2e83c0c4b81fd17eaa3bb80ae2867db104d54 Mon Sep 17 00:00:00 2001 From: Vishal Shenoy Date: Wed, 12 Mar 2025 15:27:25 -0700 Subject: [PATCH 03/35] Attribution tutorial fixed (#809) --- docs/mint.json | 759 ++++++++++++++++---------------- docs/tutorials/attributions.mdx | 4 +- 2 files changed, 381 insertions(+), 382 deletions(-) diff --git a/docs/mint.json b/docs/mint.json index 5a33f2590..0d4ea5d59 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -1,381 +1,380 @@ { - "$schema": "https://mintlify.com/schema.json", - "name": "Codegen", - "logo": { - "dark": "https://cdn.prod.website-files.com/67070304751b9b01bf6a161c/679bcf45a3e32761c42b324b_Codegen_Logomark_Dark.svg", - "light": "https://cdn.prod.website-files.com/67070304751b9b01bf6a161c/679bcf45bf55446746125835_Codegen_Logomark_Light.svg" - }, - "modeToggle": { - "default": "dark" - }, - "metadata": { - "og:site_name": "Codegen", - "og:title": "Codegen - Manipulate Code at Scale", - "og:description": "A scriptable interface to a powerful, multi-lingual language server built on top of Tree-sitter.", - "og:url": "https://docs.codegen.com", - "og:locale": "en_US", - "og:logo": "https://i.imgur.com/f4OVOqI.png", - "article:publisher": "Codegen, Inc.", - "twitter:site": "@codegen" - }, - "favicon": "/favicon.svg", - "colors": { - "primary": "#a277ff", - "light": "#a277ff", - "dark": "#a277ff", - "anchors": { - "from": "#61ffca", - "to": "#61ffca" - } - }, - "theme": "prism", - "background": { - "style": "gradient" - }, - "analytics": { - "posthog": { - "apiKey": "phc_GLxaINoQJnuyCyxDmTciQqzdKBYFVDkY7bRBO4bDdso" - } - }, - "feedback": { - "thumbsRating": true - }, - "topbarCtaButton": { - "name": "GitHub", - "url": "https://github.com/codegen-sh/codegen-sdk" - }, - "tabs": [ - { - "name": "API Reference", - "url": "/api-reference" - }, - { - "name": "CLI", - "url": "/cli" - }, - { - "name": "Blog", - "url": "/blog" - }, - { - "name": "Changelog", - "url": "/changelog" - } - ], - "navigation": [ - { - "group": "Introduction", - "pages": [ - "introduction/overview", - "introduction/getting-started", - "introduction/installation", - "introduction/ide-usage", - "introduction/work-with-ai", - "introduction/how-it-works", - "introduction/advanced-settings", - "introduction/guiding-principles", - "introduction/community", - "introduction/about", - "introduction/faq" - ] - }, - { - "group": "Tutorials", - "pages": [ - "tutorials/at-a-glance", - "tutorials/build-code-agent", - "tutorials/slack-bot", - "tutorials/github-review-bot", - "tutorials/deep-code-research", - "tutorials/training-data", - "tutorials/codebase-visualization", - "tutorials/migrating-apis", - "tutorials/organize-your-codebase", - "tutorials/promise-to-async-await", - "tutorials/modularity", - "tutorials/manage-feature-flags", - "tutorials/deleting-dead-code", - "tutorials/increase-type-coverage", - "tutorials/managing-typescript-exports", - "tutorials/converting-default-exports", - "tutorials/creating-documentation", - "tutorials/react-modernization", - "tutorials/unittest-to-pytest", - "tutorials/sqlalchemy-1.6-to-2.0", - "tutorials/fixing-import-loops-in-pytorch", - "tutorials/python2-to-python3", - "tutorials/flask-to-fastapi", - "tutorials/build-mcp", - "tutorials/neo4j-graph" - ] - }, - { - "group": "Building with Codegen", - "pages": [ - "building-with-codegen/at-a-glance", - "building-with-codegen/parsing-codebases", - "building-with-codegen/reusable-codemods", - "building-with-codegen/dot-codegen", - "building-with-codegen/function-decorator", - "building-with-codegen/language-support", - "building-with-codegen/commit-and-reset", - "building-with-codegen/git-operations", - "building-with-codegen/files-and-directories", - "building-with-codegen/the-editable-api", - "building-with-codegen/symbol-api", - "building-with-codegen/class-api", - "building-with-codegen/imports", - "building-with-codegen/exports", - "building-with-codegen/inheritable-behaviors", - "building-with-codegen/statements-and-code-blocks", - "building-with-codegen/dependencies-and-usages", - "building-with-codegen/function-calls-and-callsites", - "building-with-codegen/variable-assignments", - "building-with-codegen/local-variables", - "building-with-codegen/comments-and-docstrings", - "building-with-codegen/external-modules", - "building-with-codegen/type-annotations", - "building-with-codegen/moving-symbols", - "building-with-codegen/collections", - "building-with-codegen/traversing-the-call-graph", - "building-with-codegen/react-and-jsx", - "building-with-codegen/codebase-visualization", - "building-with-codegen/flagging-symbols", - "building-with-codegen/calling-out-to-llms", - "building-with-codegen/semantic-code-search", - "building-with-codegen/reducing-conditions" - ] - }, - { - "group": "CLI", - "pages": [ - "cli/about", - "cli/init", - "cli/notebook", - "cli/create", - "cli/run", - "cli/reset", - "cli/expert" - ] - }, - { - "group": "Changelog", - "pages": [ - "changelog/changelog" - ] - }, - { - "group": "Blog", - "pages": [ - "blog/posts", - "blog/devin", - "blog/act-via-code", - "blog/promise-to-async-await-twilio", - "blog/fixing-import-loops" - ] - }, - { - "group": "API Reference", - "pages": [ - "api-reference/index", - { - "group": "Core", - "icon": "code", - "pages": [ - "api-reference/core/Argument", - "api-reference/core/Assignment", - "api-reference/core/AssignmentStatement", - "api-reference/core/Attribute", - "api-reference/core/AwaitExpression", - "api-reference/core/BinaryExpression", - "api-reference/core/BlockStatement", - "api-reference/core/Boolean", - "api-reference/core/Callable", - "api-reference/core/CatchStatement", - "api-reference/core/ChainedAttribute", - "api-reference/core/Class", - "api-reference/core/CodeBlock", - "api-reference/core/CodeOwner", - "api-reference/core/Codebase", - "api-reference/core/Comment", - "api-reference/core/CommentGroup", - "api-reference/core/ComparisonExpression", - "api-reference/core/Decorator", - "api-reference/core/Dict", - "api-reference/core/Directory", - "api-reference/core/Editable", - "api-reference/core/Export", - "api-reference/core/ExportStatement", - "api-reference/core/Exportable", - "api-reference/core/Expression", - "api-reference/core/ExpressionGroup", - "api-reference/core/ExpressionStatement", - "api-reference/core/ExternalModule", - "api-reference/core/File", - "api-reference/core/FlagKwargs", - "api-reference/core/ForLoopStatement", - "api-reference/core/Function", - "api-reference/core/FunctionCall", - "api-reference/core/GenericType", - "api-reference/core/HasBlock", - "api-reference/core/HasName", - "api-reference/core/HasValue", - "api-reference/core/IfBlockStatement", - "api-reference/core/Import", - "api-reference/core/ImportStatement", - "api-reference/core/ImportType", - "api-reference/core/Importable", - "api-reference/core/Interface", - "api-reference/core/List", - "api-reference/core/MessageType", - "api-reference/core/MultiExpression", - "api-reference/core/MultiLineCollection", - "api-reference/core/Name", - "api-reference/core/NamedType", - "api-reference/core/NoneType", - "api-reference/core/Number", - "api-reference/core/Pair", - "api-reference/core/Parameter", - "api-reference/core/ParenthesizedExpression", - "api-reference/core/Placeholder", - "api-reference/core/PlaceholderType", - "api-reference/core/RaiseStatement", - "api-reference/core/ReturnStatement", - "api-reference/core/SourceFile", - "api-reference/core/Span", - "api-reference/core/Statement", - "api-reference/core/StatementType", - "api-reference/core/String", - "api-reference/core/StubPlaceholder", - "api-reference/core/SubscriptExpression", - "api-reference/core/SwitchCase", - "api-reference/core/SwitchStatement", - "api-reference/core/Symbol", - "api-reference/core/SymbolGroup", - "api-reference/core/SymbolStatement", - "api-reference/core/TernaryExpression", - "api-reference/core/TryCatchStatement", - "api-reference/core/Tuple", - "api-reference/core/TupleType", - "api-reference/core/Type", - "api-reference/core/TypeAlias", - "api-reference/core/TypePlaceholder", - "api-reference/core/Typeable", - "api-reference/core/UnaryExpression", - "api-reference/core/UnionType", - "api-reference/core/Unpack", - "api-reference/core/Unwrappable", - "api-reference/core/Usable", - "api-reference/core/Usage", - "api-reference/core/UsageKind", - "api-reference/core/UsageType", - "api-reference/core/Value", - "api-reference/core/WhileStatement", - "api-reference/core/WithStatement" - ] - }, - { - "group": "Python", - "icon": "python", - "pages": [ - "api-reference/python/PyAssignment", - "api-reference/python/PyAssignmentStatement", - "api-reference/python/PyAttribute", - "api-reference/python/PyBlockStatement", - "api-reference/python/PyBreakStatement", - "api-reference/python/PyCatchStatement", - "api-reference/python/PyChainedAttribute", - "api-reference/python/PyClass", - "api-reference/python/PyCodeBlock", - "api-reference/python/PyComment", - "api-reference/python/PyCommentGroup", - "api-reference/python/PyCommentType", - "api-reference/python/PyConditionalExpression", - "api-reference/python/PyDecorator", - "api-reference/python/PyFile", - "api-reference/python/PyForLoopStatement", - "api-reference/python/PyFunction", - "api-reference/python/PyGenericType", - "api-reference/python/PyHasBlock", - "api-reference/python/PyIfBlockStatement", - "api-reference/python/PyImport", - "api-reference/python/PyImportStatement", - "api-reference/python/PyMatchCase", - "api-reference/python/PyMatchStatement", - "api-reference/python/PyNamedType", - "api-reference/python/PyParameter", - "api-reference/python/PyPassStatement", - "api-reference/python/PyReturnTypePlaceholder", - "api-reference/python/PyString", - "api-reference/python/PySymbol", - "api-reference/python/PyTryCatchStatement", - "api-reference/python/PyUnionType", - "api-reference/python/PyWhileStatement" - ] - }, - { - "group": "Typescript", - "icon": "js", - "pages": [ - "api-reference/typescript/JSXElement", - "api-reference/typescript/JSXExpression", - "api-reference/typescript/JSXProp", - "api-reference/typescript/TSArrayType", - "api-reference/typescript/TSAssignment", - "api-reference/typescript/TSAssignmentStatement", - "api-reference/typescript/TSAttribute", - "api-reference/typescript/TSBlockStatement", - "api-reference/typescript/TSCatchStatement", - "api-reference/typescript/TSChainedAttribute", - "api-reference/typescript/TSClass", - "api-reference/typescript/TSCodeBlock", - "api-reference/typescript/TSComment", - "api-reference/typescript/TSCommentGroup", - "api-reference/typescript/TSCommentType", - "api-reference/typescript/TSConditionalType", - "api-reference/typescript/TSConfig", - "api-reference/typescript/TSDecorator", - "api-reference/typescript/TSDict", - "api-reference/typescript/TSEnum", - "api-reference/typescript/TSExport", - "api-reference/typescript/TSExpressionType", - "api-reference/typescript/TSFile", - "api-reference/typescript/TSForLoopStatement", - "api-reference/typescript/TSFunction", - "api-reference/typescript/TSFunctionType", - "api-reference/typescript/TSGenericType", - "api-reference/typescript/TSHasBlock", - "api-reference/typescript/TSIfBlockStatement", - "api-reference/typescript/TSImport", - "api-reference/typescript/TSImportStatement", - "api-reference/typescript/TSInterface", - "api-reference/typescript/TSLabeledStatement", - "api-reference/typescript/TSLookupType", - "api-reference/typescript/TSNamedType", - "api-reference/typescript/TSNamespace", - "api-reference/typescript/TSObjectType", - "api-reference/typescript/TSPair", - "api-reference/typescript/TSParameter", - "api-reference/typescript/TSQueryType", - "api-reference/typescript/TSReadonlyType", - "api-reference/typescript/TSReturnTypePlaceholder", - "api-reference/typescript/TSString", - "api-reference/typescript/TSSwitchCase", - "api-reference/typescript/TSSwitchStatement", - "api-reference/typescript/TSSymbol", - "api-reference/typescript/TSTernaryExpression", - "api-reference/typescript/TSTryCatchStatement", - "api-reference/typescript/TSTypeAlias", - "api-reference/typescript/TSUndefinedType", - "api-reference/typescript/TSUnionType", - "api-reference/typescript/TSWhileStatement" - ] - } - ] - } - ], - "footerSocials": { - "x": "https://x.com/codegen", - "linkedin": "https://linkedin.com/company/codegen-dot-com" - } -} \ No newline at end of file + "$schema": "https://mintlify.com/schema.json", + "name": "Codegen", + "logo": { + "dark": "https://cdn.prod.website-files.com/67070304751b9b01bf6a161c/679bcf45a3e32761c42b324b_Codegen_Logomark_Dark.svg", + "light": "https://cdn.prod.website-files.com/67070304751b9b01bf6a161c/679bcf45bf55446746125835_Codegen_Logomark_Light.svg" + }, + "modeToggle": { + "default": "dark" + }, + "metadata": { + "og:site_name": "Codegen", + "og:title": "Codegen - Manipulate Code at Scale", + "og:description": "A scriptable interface to a powerful, multi-lingual language server built on top of Tree-sitter.", + "og:url": "https://docs.codegen.com", + "og:locale": "en_US", + "og:logo": "https://i.imgur.com/f4OVOqI.png", + "article:publisher": "Codegen, Inc.", + "twitter:site": "@codegen" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#a277ff", + "light": "#a277ff", + "dark": "#a277ff", + "anchors": { + "from": "#61ffca", + "to": "#61ffca" + } + }, + "theme": "prism", + "background": { + "style": "gradient" + }, + "analytics": { + "posthog": { + "apiKey": "phc_GLxaINoQJnuyCyxDmTciQqzdKBYFVDkY7bRBO4bDdso" + } + }, + "feedback": { + "thumbsRating": true + }, + "topbarCtaButton": { + "name": "GitHub", + "url": "https://github.com/codegen-sh/codegen-sdk" + }, + "tabs": [ + { + "name": "API Reference", + "url": "/api-reference" + }, + { + "name": "CLI", + "url": "/cli" + }, + { + "name": "Blog", + "url": "/blog" + }, + { + "name": "Changelog", + "url": "/changelog" + } + ], + "navigation": [ + { + "group": "Introduction", + "pages": [ + "introduction/overview", + "introduction/getting-started", + "introduction/installation", + "introduction/ide-usage", + "introduction/work-with-ai", + "introduction/how-it-works", + "introduction/advanced-settings", + "introduction/guiding-principles", + "introduction/community", + "introduction/about", + "introduction/faq" + ] + }, + { + "group": "Tutorials", + "pages": [ + "tutorials/at-a-glance", + "tutorials/build-code-agent", + "tutorials/slack-bot", + "tutorials/github-review-bot", + "tutorials/deep-code-research", + "tutorials/training-data", + "tutorials/codebase-visualization", + "tutorials/migrating-apis", + "tutorials/organize-your-codebase", + "tutorials/promise-to-async-await", + "tutorials/modularity", + "tutorials/manage-feature-flags", + "tutorials/deleting-dead-code", + "tutorials/increase-type-coverage", + "tutorials/managing-typescript-exports", + "tutorials/converting-default-exports", + "tutorials/creating-documentation", + "tutorials/react-modernization", + "tutorials/unittest-to-pytest", + "tutorials/sqlalchemy-1.6-to-2.0", + "tutorials/fixing-import-loops-in-pytorch", + "tutorials/python2-to-python3", + "tutorials/flask-to-fastapi", + "tutorials/build-mcp", + "tutorials/neo4j-graph", + "tutorials/attributions" + ] + }, + { + "group": "Building with Codegen", + "pages": [ + "building-with-codegen/at-a-glance", + "building-with-codegen/parsing-codebases", + "building-with-codegen/reusable-codemods", + "building-with-codegen/dot-codegen", + "building-with-codegen/function-decorator", + "building-with-codegen/language-support", + "building-with-codegen/commit-and-reset", + "building-with-codegen/git-operations", + "building-with-codegen/files-and-directories", + "building-with-codegen/the-editable-api", + "building-with-codegen/symbol-api", + "building-with-codegen/class-api", + "building-with-codegen/imports", + "building-with-codegen/exports", + "building-with-codegen/inheritable-behaviors", + "building-with-codegen/statements-and-code-blocks", + "building-with-codegen/dependencies-and-usages", + "building-with-codegen/function-calls-and-callsites", + "building-with-codegen/variable-assignments", + "building-with-codegen/local-variables", + "building-with-codegen/comments-and-docstrings", + "building-with-codegen/external-modules", + "building-with-codegen/type-annotations", + "building-with-codegen/moving-symbols", + "building-with-codegen/collections", + "building-with-codegen/traversing-the-call-graph", + "building-with-codegen/react-and-jsx", + "building-with-codegen/codebase-visualization", + "building-with-codegen/flagging-symbols", + "building-with-codegen/calling-out-to-llms", + "building-with-codegen/semantic-code-search", + "building-with-codegen/reducing-conditions" + ] + }, + { + "group": "CLI", + "pages": [ + "cli/about", + "cli/init", + "cli/notebook", + "cli/create", + "cli/run", + "cli/reset", + "cli/expert" + ] + }, + { + "group": "Changelog", + "pages": ["changelog/changelog"] + }, + { + "group": "Blog", + "pages": [ + "blog/posts", + "blog/devin", + "blog/act-via-code", + "blog/promise-to-async-await-twilio", + "blog/fixing-import-loops" + ] + }, + { + "group": "API Reference", + "pages": [ + "api-reference/index", + { + "group": "Core", + "icon": "code", + "pages": [ + "api-reference/core/Argument", + "api-reference/core/Assignment", + "api-reference/core/AssignmentStatement", + "api-reference/core/Attribute", + "api-reference/core/AwaitExpression", + "api-reference/core/BinaryExpression", + "api-reference/core/BlockStatement", + "api-reference/core/Boolean", + "api-reference/core/Callable", + "api-reference/core/CatchStatement", + "api-reference/core/ChainedAttribute", + "api-reference/core/Class", + "api-reference/core/CodeBlock", + "api-reference/core/CodeOwner", + "api-reference/core/Codebase", + "api-reference/core/Comment", + "api-reference/core/CommentGroup", + "api-reference/core/ComparisonExpression", + "api-reference/core/Decorator", + "api-reference/core/Dict", + "api-reference/core/Directory", + "api-reference/core/Editable", + "api-reference/core/Export", + "api-reference/core/ExportStatement", + "api-reference/core/Exportable", + "api-reference/core/Expression", + "api-reference/core/ExpressionGroup", + "api-reference/core/ExpressionStatement", + "api-reference/core/ExternalModule", + "api-reference/core/File", + "api-reference/core/FlagKwargs", + "api-reference/core/ForLoopStatement", + "api-reference/core/Function", + "api-reference/core/FunctionCall", + "api-reference/core/GenericType", + "api-reference/core/HasBlock", + "api-reference/core/HasName", + "api-reference/core/HasValue", + "api-reference/core/IfBlockStatement", + "api-reference/core/Import", + "api-reference/core/ImportStatement", + "api-reference/core/ImportType", + "api-reference/core/Importable", + "api-reference/core/Interface", + "api-reference/core/List", + "api-reference/core/MessageType", + "api-reference/core/MultiExpression", + "api-reference/core/MultiLineCollection", + "api-reference/core/Name", + "api-reference/core/NamedType", + "api-reference/core/NoneType", + "api-reference/core/Number", + "api-reference/core/Pair", + "api-reference/core/Parameter", + "api-reference/core/ParenthesizedExpression", + "api-reference/core/Placeholder", + "api-reference/core/PlaceholderType", + "api-reference/core/RaiseStatement", + "api-reference/core/ReturnStatement", + "api-reference/core/SourceFile", + "api-reference/core/Span", + "api-reference/core/Statement", + "api-reference/core/StatementType", + "api-reference/core/String", + "api-reference/core/StubPlaceholder", + "api-reference/core/SubscriptExpression", + "api-reference/core/SwitchCase", + "api-reference/core/SwitchStatement", + "api-reference/core/Symbol", + "api-reference/core/SymbolGroup", + "api-reference/core/SymbolStatement", + "api-reference/core/TernaryExpression", + "api-reference/core/TryCatchStatement", + "api-reference/core/Tuple", + "api-reference/core/TupleType", + "api-reference/core/Type", + "api-reference/core/TypeAlias", + "api-reference/core/TypePlaceholder", + "api-reference/core/Typeable", + "api-reference/core/UnaryExpression", + "api-reference/core/UnionType", + "api-reference/core/Unpack", + "api-reference/core/Unwrappable", + "api-reference/core/Usable", + "api-reference/core/Usage", + "api-reference/core/UsageKind", + "api-reference/core/UsageType", + "api-reference/core/Value", + "api-reference/core/WhileStatement", + "api-reference/core/WithStatement" + ] + }, + { + "group": "Python", + "icon": "python", + "pages": [ + "api-reference/python/PyAssignment", + "api-reference/python/PyAssignmentStatement", + "api-reference/python/PyAttribute", + "api-reference/python/PyBlockStatement", + "api-reference/python/PyBreakStatement", + "api-reference/python/PyCatchStatement", + "api-reference/python/PyChainedAttribute", + "api-reference/python/PyClass", + "api-reference/python/PyCodeBlock", + "api-reference/python/PyComment", + "api-reference/python/PyCommentGroup", + "api-reference/python/PyCommentType", + "api-reference/python/PyConditionalExpression", + "api-reference/python/PyDecorator", + "api-reference/python/PyFile", + "api-reference/python/PyForLoopStatement", + "api-reference/python/PyFunction", + "api-reference/python/PyGenericType", + "api-reference/python/PyHasBlock", + "api-reference/python/PyIfBlockStatement", + "api-reference/python/PyImport", + "api-reference/python/PyImportStatement", + "api-reference/python/PyMatchCase", + "api-reference/python/PyMatchStatement", + "api-reference/python/PyNamedType", + "api-reference/python/PyParameter", + "api-reference/python/PyPassStatement", + "api-reference/python/PyReturnTypePlaceholder", + "api-reference/python/PyString", + "api-reference/python/PySymbol", + "api-reference/python/PyTryCatchStatement", + "api-reference/python/PyUnionType", + "api-reference/python/PyWhileStatement" + ] + }, + { + "group": "Typescript", + "icon": "js", + "pages": [ + "api-reference/typescript/JSXElement", + "api-reference/typescript/JSXExpression", + "api-reference/typescript/JSXProp", + "api-reference/typescript/TSArrayType", + "api-reference/typescript/TSAssignment", + "api-reference/typescript/TSAssignmentStatement", + "api-reference/typescript/TSAttribute", + "api-reference/typescript/TSBlockStatement", + "api-reference/typescript/TSCatchStatement", + "api-reference/typescript/TSChainedAttribute", + "api-reference/typescript/TSClass", + "api-reference/typescript/TSCodeBlock", + "api-reference/typescript/TSComment", + "api-reference/typescript/TSCommentGroup", + "api-reference/typescript/TSCommentType", + "api-reference/typescript/TSConditionalType", + "api-reference/typescript/TSConfig", + "api-reference/typescript/TSDecorator", + "api-reference/typescript/TSDict", + "api-reference/typescript/TSEnum", + "api-reference/typescript/TSExport", + "api-reference/typescript/TSExpressionType", + "api-reference/typescript/TSFile", + "api-reference/typescript/TSForLoopStatement", + "api-reference/typescript/TSFunction", + "api-reference/typescript/TSFunctionType", + "api-reference/typescript/TSGenericType", + "api-reference/typescript/TSHasBlock", + "api-reference/typescript/TSIfBlockStatement", + "api-reference/typescript/TSImport", + "api-reference/typescript/TSImportStatement", + "api-reference/typescript/TSInterface", + "api-reference/typescript/TSLabeledStatement", + "api-reference/typescript/TSLookupType", + "api-reference/typescript/TSNamedType", + "api-reference/typescript/TSNamespace", + "api-reference/typescript/TSObjectType", + "api-reference/typescript/TSPair", + "api-reference/typescript/TSParameter", + "api-reference/typescript/TSQueryType", + "api-reference/typescript/TSReadonlyType", + "api-reference/typescript/TSReturnTypePlaceholder", + "api-reference/typescript/TSString", + "api-reference/typescript/TSSwitchCase", + "api-reference/typescript/TSSwitchStatement", + "api-reference/typescript/TSSymbol", + "api-reference/typescript/TSTernaryExpression", + "api-reference/typescript/TSTryCatchStatement", + "api-reference/typescript/TSTypeAlias", + "api-reference/typescript/TSUndefinedType", + "api-reference/typescript/TSUnionType", + "api-reference/typescript/TSWhileStatement" + ] + } + ] + } + ], + "footerSocials": { + "x": "https://x.com/codegen", + "linkedin": "https://linkedin.com/company/codegen-dot-com" + } +} diff --git a/docs/tutorials/attributions.mdx b/docs/tutorials/attributions.mdx index cf86538f9..5e4e8284e 100644 --- a/docs/tutorials/attributions.mdx +++ b/docs/tutorials/attributions.mdx @@ -1,6 +1,6 @@ --- -title: "Code statistics and attributions" -sidebarTitle: "Code statistics and attributions" +title: "Code Attributions" +sidebarTitle: "Code Attributions" description: "Learn how to analyze code statistics and attributions using Codegen" icon: "network-wired" iconType: "solid" From 52be3b9a6ff051ad4d822d01a6dce75cfdb00e1a Mon Sep 17 00:00:00 2001 From: Vishal Shenoy Date: Wed, 12 Mar 2025 15:28:13 -0700 Subject: [PATCH 04/35] AI impact dashboard (#808) --- .../examples/ai_impact_analysis/README.md | 124 + .../ai_impact_analysis/dashboard/README.md | 86 + .../dashboard/backend/api.py | 54 + .../dashboard/frontend/app/favicon.ico | Bin 0 -> 15406 bytes .../dashboard/frontend/app/globals.css | 76 + .../dashboard/frontend/app/layout.tsx | 34 + .../dashboard/frontend/app/page.tsx | 9 + .../dashboard/frontend/components.json | 21 + .../components/contribution-timeline.tsx | 83 + .../components/contributors-breakdown.tsx | 131 + .../frontend/components/dashboard-header.tsx | 18 + .../components/high-impact-symbols.tsx | 58 + .../frontend/components/loading-screen.tsx | 15 + .../components/repo-analysis-dashboard.tsx | 113 + .../frontend/components/summary-cards.tsx | 77 + .../frontend/components/theme-provider.tsx | 11 + .../frontend/components/top-ai-files.tsx | 48 + .../frontend/components/ui/button.tsx | 56 + .../dashboard/frontend/components/ui/card.tsx | 86 + .../frontend/components/ui/chart.tsx | 365 ++ .../frontend/components/ui/input-otp.tsx | 71 + .../frontend/components/ui/input.tsx | 22 + .../frontend/components/ui/progress.tsx | 28 + .../frontend/components/ui/scroll-area.tsx | 48 + .../frontend/components/ui/skeleton.tsx | 15 + .../frontend/components/ui/use-mobile.tsx | 21 + .../frontend/components/ui/use-toast.ts | 191 + .../dashboard/frontend/hooks/use-mobile.tsx | 21 + .../dashboard/frontend/hooks/use-toast.ts | 191 + .../dashboard/frontend/lib/types.ts | 27 + .../dashboard/frontend/lib/utils.ts | 6 + .../dashboard/frontend/next.config.mjs | 48 + .../dashboard/frontend/package-lock.json | 4640 +++++++++++++++++ .../dashboard/frontend/package.json | 70 + .../dashboard/frontend/postcss.config.mjs | 8 + .../dashboard/frontend/styles/globals.css | 94 + .../dashboard/frontend/tailwind.config.js | 77 + .../dashboard/frontend/tsconfig.json | 27 + 38 files changed, 7070 insertions(+) create mode 100644 codegen-examples/examples/ai_impact_analysis/README.md create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/README.md create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/backend/api.py create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/favicon.ico create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/globals.css create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/layout.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/page.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components.json create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contribution-timeline.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contributors-breakdown.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/dashboard-header.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/high-impact-symbols.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/loading-screen.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/repo-analysis-dashboard.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/summary-cards.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/theme-provider.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/top-ai-files.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/button.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/card.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/chart.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/input-otp.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/input.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/progress.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/scroll-area.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/skeleton.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/use-mobile.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/use-toast.ts create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/hooks/use-mobile.tsx create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/hooks/use-toast.ts create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/lib/types.ts create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/lib/utils.ts create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/next.config.mjs create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/package-lock.json create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/package.json create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/postcss.config.mjs create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/styles/globals.css create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/tailwind.config.js create mode 100644 codegen-examples/examples/ai_impact_analysis/dashboard/frontend/tsconfig.json diff --git a/codegen-examples/examples/ai_impact_analysis/README.md b/codegen-examples/examples/ai_impact_analysis/README.md new file mode 100644 index 000000000..e34e1a8af --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/README.md @@ -0,0 +1,124 @@ +# AI Impact Analysis + +This script analyzes a codebase to measure and report the impact of AI-generated code contributions. It provides detailed insights about AI vs human contributions, helping teams understand the role of AI in their development process. + +## Features + +- **Repository Analysis**: Automatically detects and analyzes git repositories: + + - Uses current directory if it's a git repo + + - Searches parent directories for a git repo + + - Falls back to cloning a specified repository if needed + + ```python + # Basic repository setup + repo_path = os.getcwd() + repo_config = RepoConfig.from_repo_path(repo_path) + repo_operator = RepoOperator(repo_config=repo_config) + project = ProjectConfig.from_repo_operator(repo_operator=repo_operator, programming_language=ProgrammingLanguage.PYTHON) + codebase = Codebase(projects=[project]) + ``` + +- **Comprehensive Statistics**: + + - Total number of commits and AI vs human contribution percentages + - Files with significant AI contribution (>50%) + - AI-touched symbols and their impact + - Detailed contributor breakdown (human and AI contributors) + + ```python + # Run the analysis + ai_authors = ["github-actions[bot]", "dependabot[bot]"] + results = analyze_ai_impact(codebase, ai_authors) + + # Access statistics + stats = results["stats"] + print(f"Total commits: {stats['total_commits']}") + print(f"AI commits: {stats['ai_commits']} ({stats['ai_percentage']:.1f}%)") + print(f"Files with >50% AI: {stats['ai_file_count']} of {stats['total_file_count']}") + + # View contributors + for author, count in results["contributors"]: + is_ai = any(ai_name in author for ai_name in ai_authors) + print(f"{'šŸ¤–' if is_ai else 'šŸ‘¤'} {author}: {count} commits") + ``` + +- **High-Impact Code Detection**: + + - Identifies AI-written code that is heavily used by other parts of the codebase + - Shows dependency relationships for AI-contributed code + + ```python + # Access high-impact AI symbols + for symbol in results["high_impact_symbols"]: + print(f"Symbol: {symbol['name']} ({symbol['filepath']})") + print(f"Used by {symbol['usage_count']} other symbols") + print(f"Last edited by: {symbol['last_editor']}") + + # View top AI-contributed files + for file_path, percentage in stats["top_ai_files"]: + print(f"{file_path}: {percentage:.1f}% AI contribution") + ``` + +- **Detailed Attribution**: + + - Maps symbols to git history + - Tracks last editor and complete editor history for each symbol + - Flags AI-authored symbols + + ```python + # Get attribution information for a specific symbol + symbol = codebase.get_symbol("path/to/file.py:MyClass.my_method") + + # Access attribution data + print(f"Last editor: {symbol.last_editor}") + print(f"Editor history: {symbol.editor_history}") + print(f"AI authored: {symbol.is_ai_authored}") + + # Find all AI-authored symbols + ai_symbols = [s for s in codebase.get_symbols() if s.is_ai_authored] + for symbol in ai_symbols: + print(f"AI symbol: {symbol.name}") + ``` + +## Output + +The script generates: + +1. Console output with summary statistics +1. Detailed analysis in `ai_impact_analysis.json` +1. Attribution information added to codebase symbols + +## Usage + +```bash +python run.py +``` + +The script will automatically: + +1. Initialize and analyze the codebase +1. Process git history +1. Generate attribution information +1. Output detailed statistics + +You can also visualize the AI impact analysis results using a dashboard. For setup and usage instructions, please see the documentation in the `/dashboard` subdirectory. + +## Symbol Attribution + +After running the analysis, symbols in the codebase will have the following attribution information: + +- `symbol.last_editor`: The last person who edited the symbol +- `symbol.editor_history`: List of all editors who have touched the symbol +- `symbol.is_ai_authored`: Boolean indicating if the symbol was authored by AI + +## Learn More + +- [Attributions](https://docs.codegen.com/tutorials/attributions) +- [Codegen Documentation](https://docs.codegen.com) + +## Contributing + +Feel free to submit issues and enhancement requests! diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/README.md b/codegen-examples/examples/ai_impact_analysis/dashboard/README.md new file mode 100644 index 000000000..cde758b55 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/README.md @@ -0,0 +1,86 @@ +# AI Impact Analysis Dashboard + +A web dashboard for visualizing AI-generated code contributions in your codebase. This dashboard provides detailed insights about AI vs human contributions, helping understand the role of AI in a codebase development process. + +## Setup + +### Backend + +1. Install dependencies: + +```bash +uv venv +source .venv/bin/activate +uv pip install modal codegen fastapi +``` + +2. Deploy or serve the Modal endpoint: + +```bash +modal serve backend/api.py +``` + +```bash +modal deploy backend/api.py +``` + +### Frontend + +1. Install dependencies: + +```bash +cd frontend +npm install +``` + +2. Update the API endpoint: + Edit the fetch URL on line 29 in `components/repo-analysis-dashboard.tsx` to point to your Modal endpoint: + +```bash + fetch(`[your-modal-deployment-url]/analyze?repo_full_name=${repoFullName}`, { + method: 'POST', + }) +``` + +3. Start the development server: + +```bash +npm run dev +``` + +## Usage + +1. Visit the dashboard in your browser (default: http://localhost:3000) +1. Enter a GitHub repository name (format: username/repo) +1. Click "Analyze Repo" to generate insights + +The dashboard will display: + +- Summary statistics of AI contributions +- Monthly contribution timeline +- Top files with AI contributions +- High-impact AI-authored symbols +- Contributor breakdown visualization + +## Architecture + +- **Backend**: Modal-deployed FastAPI service that: + + - Clones and analyzes repositories + - Processes git history + - Calculates AI impact metrics + - Returns structured analysis data + +- **Frontend**: Next.js application with: + + - Interactive charts + - Visualized AI impact metrics + +## Learn More + +- [AI Impact Analysis Documentation](https://docs.codegen.com/tutorials/attributions) +- [Codegen Documentation](https://docs.codegen.com) + +## Contributing + +Feel free to submit issues and enhancement requests! diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/backend/api.py b/codegen-examples/examples/ai_impact_analysis/dashboard/backend/api.py new file mode 100644 index 000000000..ddb08115d --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/backend/api.py @@ -0,0 +1,54 @@ +from codegen import Codebase +from codegen.extensions.attribution.main import ( + add_attribution_to_symbols, + analyze_ai_impact, +) +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +import modal + +image = modal.Image.debian_slim().apt_install("git").pip_install("codegen", "fastapi", "intervaltree", "pygit2", "requests") + +app = modal.App(name="ai-impact-analysis", image=image) + +fastapi_app = FastAPI() + +fastapi_app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@fastapi_app.post("/analyze") +async def analyze(repo_full_name: str): + codebase = Codebase.from_repo(repo_full_name=repo_full_name, language="python", full_history=True) + + print("šŸ¤– Analyzing AI impact on codebase...") + + ai_authors = [ + "renovate[bot]", + "dependabot[bot]", + "github-actions[bot]", + "devin-ai-integration[bot]", + ] + + results = analyze_ai_impact(codebase, ai_authors) + + print("\nšŸ·ļø Adding attribution information to symbols...") + add_attribution_to_symbols(codebase, ai_authors) + print("āœ… Attribution information added to symbols") + + return results + + +@app.function(image=image) +@modal.asgi_app() +def fastapi_modal_app(): + return fastapi_app + + +if __name__ == "__main__": + app.deploy("ai-impact-analysis") diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/favicon.ico b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..fd85877464ce8ec6468b611d781ca11399bb1a5e GIT binary patch literal 15406 zcmeI2d5jgs7Ka-_P!JtdgrMLIg5tgqx1h3!im2}yBAS>O)WpOs`j3hlV~k6D8sqZN zC+-Vw(YQtI}As}oFPVE&0`vgJI*x3F&YCsU&iEfWQqV(;9V8Os37=;a4$kDDRs9MV9 z@|G~D>WpL{-!NDVOQ5NmGO!;B%fQHTB5G1a!ER^xF3R{4;k+omuu^91M#E2${nLaS zK_9pl=vlvzJ_c5S-8qX8q50KmqJ)r@n!}+ke>5d}) zr{o>5wV;y+4>EQ3ZcWxPpz zi^%SJ;zn+YbOTVl9x0a+ZWrl?5dS#RFCrWq>Gvc)T5$>Sdn23A2#2<7+yC-B5Z;N> zHSUJR(-lWI817PNr$y=N^93ML+eSbW$fv2UJhl$IO-iNG3xtv%U_}@!muK6yzGv(= zE|<#BMEMlVfxbR_mmd4xpmF#(^l{k~q0^*ngmIwt>7OX{pXSN&e@EM>_bh0G5?s)o zKB&H^UV;v}oLG-~`l0gSijEr75v?uZI#;wGl&iYqYgfAVcFk=B;hI>K4=RrreY>MV zt@o=P`k4jRy&>g4;HX1;?~X7WzO>S36j48AYM*_=DqD{_I}A5<5Z?wq0m+R}^BACv zexUW$7S*$jsoR9;bS63)5_3S$w$9}7h1hQfuYfJ@p~!vi!d83VLeSnC?!Ue|jb3(9 z9)g6t!BcqNFt%ACw%WTtik0vIap(SO%27JV&XyNZY-;RGh^_%R>81t!Ibe&<2dVvE zxuplC+wv@m#2(A@1kjm6Yp;;chsV9(S?9w?!Iq^cMp(8wPe^Zz@+bCap2tC}ZOHRF zm~55X3*9V-?YHQ5maRh2vn|TU7erFfnRAeBujluY)?xV@pr7e5)>&~cFz2ASiN$Hd@IiBMo+HyQ=&p9?e?vFMT~6schrLn#N+h-Rqto|;&e68~Pj#TY z#7^M3nbY+|hYwdeY>n!_4tsrn$S+&2LD8{R5!ZatSdNR%`bWjfx3=Wp$Nwup~j&R+3r`I2jO_v z_pZJfZFvyI-L{hF1x41cvRYbNcB0&G9Cg_$`MGD&^{YI}-HsJ%sa;|0S^n)NL?WiG=qS#H43oLtum3oIPJo8T#!19e@y3UoUxD)L?5M>-csUWOft zuBRU5;pcAfJQ&}JhN5qeed|$YQDx$vzMI|+y>GyMuip zajq}YmukwAAE57k-NED)okx#^)VFLkb=PW3{qp@VSOV3`axzSTzoiYT7sf)C@463Z z0iWo8V|=asSZ#jl>;=cdSKt%fC+PeCLRjWALVr)S_19ugeLdj>_#S++ii8uPH>j=p z=KUFLS+;iV+5>9wr<%M}ZhZO4b|m&JKh?g(`oiAW^qPAI!F0GWLVMT*Nd1qC%0NF59tVvXmuQ?m08OqsjgEB2 zd-%IXE1^%c_Kkql`hae8P+#e;D(;Konv=62b*F(&YtB%Z13pn1YNK;Lpx+W60-q@V zi{Sjd4t;;nd-IJslEQa-AE9$Bpx64NZ%%t2pz4rki48G*t`O@{=#i1Do z4rw||x_%Qtoe+(4@0~SiThYurSvRuU@H}>!GkR}Z!(7PfH~rpL={L&R*yNRk7^cQw ztS@wjc1K2GnwuKO!{Mb2J0+=YnRg(hYtFiUheGWXjm6BpiFBP4A93dHRO+rWeqY&{ z(izjdujK!R*!Qz`^2( z|Hg!>Lp~>VFm%iz;-leJ@QM0D?;-r3MMu2@OQrIYq-U)Ins?7ZV*N8^q3;VOp#9Dz>R;^cypm*()H%QpU z`0ENu-yU8AUmMiFcY$vo)VXIWXg|@I{0P*h6Cm|}2l4^E_N^J<8-Mae=iJ?0KHEC% zw5~n|zOkS&taT}CKhyh=U+P!yT#fwS1)JZ&CGuV83g>yt_T4;VKLoVrYRtMsdX0xh zlc%d8W$5gpcVxeFJ?lMg+m*7(Hwc~p^|wn@zUnQa4Na7z{xJQea4tCiFDP$&$gjSc z3EBr-qBf|WUGtXdv!jfma6fzx_keSq^5ysGDW?H$0N?x~DZF>C`S)GQ(YM$Lg+70} zqNhB~#ake2ebc$j`yNQs`us3xZ5Rfrb+jwK<;_EV+F!J((8EKc7P-`3i$+(5>rEr8eS^=EG#jnhzT9 zQ^9!-tyD*Mq~n{`o8_Q$MO<_y_;+{sP-`9dc@XG3$+SW1=cHQ8?XLXzs&AORVG%5b TvEcn(qr3cCU(fm;uzTSD5Uj=O literal 0 HcmV?d00001 diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/globals.css b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/globals.css new file mode 100644 index 000000000..1535f872d --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/globals.css @@ -0,0 +1,76 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/layout.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/layout.tsx new file mode 100644 index 000000000..264632940 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import type React from "react"; +import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "AI Code Impact Analysis", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + ); +} + +import "./globals.css"; diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/page.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/page.tsx new file mode 100644 index 000000000..5b048bbdf --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/app/page.tsx @@ -0,0 +1,9 @@ +import { RepoAnalysisDashboard } from "@/components/repo-analysis-dashboard"; + +export default function Home() { + return ( +
+ +
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components.json b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components.json new file mode 100644 index 000000000..7f48f98e8 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contribution-timeline.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contribution-timeline.tsx new file mode 100644 index 000000000..e6017ba9c --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contribution-timeline.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import type { Timeline } from "@/lib/types"; +import { + Bar, + BarChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +interface ContributionTimelineProps { + timeline: Timeline[]; +} + +export function ContributionTimeline({ timeline }: ContributionTimelineProps) { + return ( + + + AI Contribution Timeline + Monthly AI contributions over time + + + + + + `${value}`} + /> + { + if (active && payload && payload.length) { + return ( +
+
+
+ + Date + + + {payload[0].payload.date} + +
+
+ + Commits + + + {payload[0].value} + +
+
+
+ ); + } + return null; + }} + /> + +
+
+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contributors-breakdown.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contributors-breakdown.tsx new file mode 100644 index 000000000..fb54b7fec --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/contributors-breakdown.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Cell, + Legend, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, +} from "recharts"; + +interface ContributorsBreakdownProps { + contributors: [string, number][]; +} + +export function ContributorsBreakdown({ + contributors, +}: ContributorsBreakdownProps) { + // Take top 5 contributors for the chart + const topContributors = contributors.slice(0, 5); + const otherContributors = contributors.slice(5); + const otherCount = otherContributors.reduce( + (sum, [_, count]) => sum + count, + 0, + ); + + const chartData = [ + ...topContributors.map(([name, count]) => ({ + name: name.split(" ")[0], // Just use first name for chart + fullName: name, + count, + })), + otherContributors.length > 0 + ? { name: "Others", fullName: "Other Contributors", count: otherCount } + : null, + ].filter(Boolean); + + const COLORS = [ + "#3b82f6", + "#10b981", + "#f59e0b", + "#ef4444", + "#8b5cf6", + "#6b7280", + ]; + + return ( + + + Contributors Breakdown + Top contributors by commit count + + +
+
+ + + + {chartData.map((entry, index) => ( + + ))} + + [ + value, + props.payload.fullName, + ]} + contentStyle={{ + backgroundColor: "white", + borderColor: "#e2e8f0", + borderRadius: "0.375rem", + }} + /> + + + +
+
+ +
+ {contributors.slice(0, 10).map(([name, count], index) => ( +
+
+
+ + {name.split(" ")[0]} + +
+
{count}
+
+ ))} + {contributors.length > 10 && ( +
+ +{contributors.length - 10} more contributors +
+ )} +
+ +
+
+ + + ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/dashboard-header.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/dashboard-header.tsx new file mode 100644 index 000000000..3fa854491 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/dashboard-header.tsx @@ -0,0 +1,18 @@ +import { Code2 } from "lucide-react"; + +export function DashboardHeader() { + return ( +
+
+
+

+ AI Code Impact Analysis +

+
+

+ Analyze AI-generated code contributions in your repository +

+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/high-impact-symbols.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/high-impact-symbols.tsx new file mode 100644 index 000000000..db1fe51aa --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/high-impact-symbols.tsx @@ -0,0 +1,58 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import type { HighImpactSymbol } from "@/lib/types"; + +interface HighImpactSymbolsProps { + symbols: HighImpactSymbol[]; +} + +export function HighImpactSymbols({ symbols }: HighImpactSymbolsProps) { + return ( + + + High-Impact AI Symbols + + AI-written code with significant usage + + + + +
+ {symbols.length > 0 ? ( + symbols.map((symbol) => ( +
+
+
{symbol.name}
+
+ Used by {symbol.usage_count} symbols +
+
+
+ {symbol.filepath} +
+
+ Last edited by:{" "} + {symbol.last_editor} +
+
+ )) + ) : ( +
+ No high-impact AI symbols found +
+ )} +
+
+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/loading-screen.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/loading-screen.tsx new file mode 100644 index 000000000..089cdf833 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/loading-screen.tsx @@ -0,0 +1,15 @@ +import { Loader2 } from "lucide-react"; + +export function LoadingScreen() { + return ( +
+
+ +

+ Analyzing Repository +

+

This may take a few seconds...

+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/repo-analysis-dashboard.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/repo-analysis-dashboard.tsx new file mode 100644 index 000000000..4cf6ddb71 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/repo-analysis-dashboard.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { ContributionTimeline } from "@/components/contribution-timeline"; +import { ContributorsBreakdown } from "@/components/contributors-breakdown"; +import { DashboardHeader } from "@/components/dashboard-header"; +import { HighImpactSymbols } from "@/components/high-impact-symbols"; +import { LoadingScreen } from "@/components/loading-screen"; +import { SummaryCards } from "@/components/summary-cards"; +import { TopAIFiles } from "@/components/top-ai-files"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import type { AnalysisData } from "@/lib/types"; +import { GitBranch, Loader2 } from "lucide-react"; +import { useState } from "react"; + +export function RepoAnalysisDashboard() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [repoUrl, setRepoUrl] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (repoUrl.trim()) { + setLoading(true); + const match = repoUrl.match(/(?:github\.com\/)?([^/\s]+\/[^/\s]+)/); + if (match) { + const repoFullName = match[1]; + fetch( + `[your-modal-deployment-url]/analyze?repo_full_name=${repoFullName}`, + { + method: "POST", + }, + ) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((analysisData: AnalysisData) => { + setData(analysisData); + setLoading(false); + }) + .catch((error) => { + console.error("Error analyzing repository:", error); + setLoading(false); + }); + } + } + }; + + return ( +
+ {loading && } + + + + + +
+
+ +
+
+ + setRepoUrl(e.target.value)} + disabled={loading} + /> +
+ +
+
+
+
+
+ + {data && ( +
+ + +
+ + +
+ +
+ + +
+
+ )} +

+

+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/summary-cards.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/summary-cards.tsx new file mode 100644 index 000000000..dea866379 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/summary-cards.tsx @@ -0,0 +1,77 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { AnalysisData } from "@/lib/types"; +import { BarChart3, FileCode, GitCommit, Percent } from "lucide-react"; + +interface SummaryCardsProps { + data: AnalysisData; +} + +export function SummaryCards({ data }: SummaryCardsProps) { + const { stats, ai_symbol_count, total_symbol_count } = data; + + return ( +
+ + + AI Commits + + + +
+ {stats.ai_commits} / {stats.total_commits} +
+

+ {stats.ai_percentage.toFixed(1)}% of total commits +

+
+
+ + + + AI Files + + + +
+ {stats.ai_file_count} / {stats.total_file_count} +
+

+ {((stats.ai_file_count / stats.total_file_count) * 100).toFixed(1)}% + of files have >50% AI contribution +

+
+
+ + + + AI Symbols + + + +
+ {ai_symbol_count} / {total_symbol_count} +
+

+ {((ai_symbol_count / total_symbol_count) * 100).toFixed(1)}% of code + symbols +

+
+
+ + + + High Impact + + + +
+ {data.high_impact_symbols.length} +
+

+ AI-written symbols with high usage +

+
+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/theme-provider.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/theme-provider.tsx new file mode 100644 index 000000000..020003cf9 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/theme-provider.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from "next-themes"; +import * as React from "react"; + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children}; +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/top-ai-files.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/top-ai-files.tsx new file mode 100644 index 000000000..67a5472a8 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/top-ai-files.tsx @@ -0,0 +1,48 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Progress } from "@/components/ui/progress"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +interface TopAIFilesProps { + files: [string, number][]; +} + +export function TopAIFiles({ files }: TopAIFilesProps) { + return ( + + + Top AI-Contributed Files + + Files with highest AI contribution percentage + + + + +
+ {files.map(([filepath, percentage]) => ( +
+
+
+ {filepath.split("/").pop()} +
+
+ {percentage.toFixed(1)}% +
+
+ +
+ {filepath} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/button.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/button.tsx new file mode 100644 index 000000000..91b784a28 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/button.tsx @@ -0,0 +1,56 @@ +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/card.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/card.tsx new file mode 100644 index 000000000..bb368bd00 --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/chart.tsx b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/chart.tsx new file mode 100644 index 000000000..aa3d5f99a --- /dev/null +++ b/codegen-examples/examples/ai_impact_analysis/dashboard/frontend/components/ui/chart.tsx @@ -0,0 +1,365 @@ +"use client"; + +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; + +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + +
+ + + {children} + +
+
+ ); +}); +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color, + ); + + if (!colorConfig.length) { + return null; + } + + return ( +