From c84d84ace285f569acc9aaa051bde6d75fd3bb23 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 2 Oct 2024 08:56:33 +0300 Subject: [PATCH] static analysis --- pr_agent/static_analysis/queries/Credits.md | 5 + .../queries/tree-sitter-c-tags.scm | 9 + .../queries/tree-sitter-c_sharp-tags.scm | 46 ++ .../queries/tree-sitter-cpp-tags.scm | 15 + .../queries/tree-sitter-elisp-tags.scm | 8 + .../queries/tree-sitter-elixir-tags.scm | 54 ++ .../queries/tree-sitter-elm-tags.scm | 19 + .../queries/tree-sitter-go-tags.scm | 30 + .../queries/tree-sitter-java-tags.scm | 20 + .../queries/tree-sitter-javascript-tags.scm | 88 +++ .../queries/tree-sitter-ocaml-tags.scm | 115 ++++ .../queries/tree-sitter-php-tags.scm | 26 + .../queries/tree-sitter-python-tags.scm | 12 + .../queries/tree-sitter-ql-tags.scm | 26 + .../queries/tree-sitter-ruby-tags.scm | 64 ++ .../queries/tree-sitter-rust-tags.scm | 60 ++ .../queries/tree-sitter-typescript-tags.scm | 41 ++ pr_agent/static_analysis/src/file_summary.py | 153 +++++ .../tests/example_files/AES.cpp | 428 +++++++++++++ .../tests/example_files/calc.java | 586 ++++++++++++++++++ .../tests/example_files/match.ts | 138 +++++ .../tests/test_file_summary.py | 43 ++ requirements.txt | 6 + 23 files changed, 1992 insertions(+) create mode 100644 pr_agent/static_analysis/queries/Credits.md create mode 100644 pr_agent/static_analysis/queries/tree-sitter-c-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-c_sharp-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-cpp-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-elisp-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-elixir-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-elm-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-go-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-java-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-javascript-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-ocaml-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-php-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-python-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-ql-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-ruby-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-rust-tags.scm create mode 100644 pr_agent/static_analysis/queries/tree-sitter-typescript-tags.scm create mode 100644 pr_agent/static_analysis/src/file_summary.py create mode 100644 pr_agent/static_analysis/tests/example_files/AES.cpp create mode 100644 pr_agent/static_analysis/tests/example_files/calc.java create mode 100644 pr_agent/static_analysis/tests/example_files/match.ts create mode 100644 pr_agent/static_analysis/tests/test_file_summary.py diff --git a/pr_agent/static_analysis/queries/Credits.md b/pr_agent/static_analysis/queries/Credits.md new file mode 100644 index 00000000..69802230 --- /dev/null +++ b/pr_agent/static_analysis/queries/Credits.md @@ -0,0 +1,5 @@ + +# Credits + +These queries, and some of the static analysis logic, were adopted from the excellent [Aider](https://github.com/paul-gauthier/aider) project. + diff --git a/pr_agent/static_analysis/queries/tree-sitter-c-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-c-tags.scm new file mode 100644 index 00000000..1035aa22 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-c-tags.scm @@ -0,0 +1,9 @@ +(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class + +(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class + +(function_declarator declarator: (identifier) @name.definition.function) @definition.function + +(type_definition declarator: (type_identifier) @name.definition.type) @definition.type + +(enum_specifier name: (type_identifier) @name.definition.type) @definition.type diff --git a/pr_agent/static_analysis/queries/tree-sitter-c_sharp-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-c_sharp-tags.scm new file mode 100644 index 00000000..58e9199a --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-c_sharp-tags.scm @@ -0,0 +1,46 @@ +(class_declaration + name: (identifier) @name.definition.class + ) @definition.class + +(class_declaration + bases: (base_list (_) @name.reference.class) + ) @reference.class + +(interface_declaration + name: (identifier) @name.definition.interface + ) @definition.interface + +(interface_declaration + bases: (base_list (_) @name.reference.interface) + ) @reference.interface + +(method_declaration + name: (identifier) @name.definition.method + ) @definition.method + +(object_creation_expression + type: (identifier) @name.reference.class + ) @reference.class + +(type_parameter_constraints_clause + target: (identifier) @name.reference.class + ) @reference.class + +(type_constraint + type: (identifier) @name.reference.class + ) @reference.class + +(variable_declaration + type: (identifier) @name.reference.class + ) @reference.class + +(invocation_expression + function: + (member_access_expression + name: (identifier) @name.reference.send + ) +) @reference.send + +(namespace_declaration + name: (identifier) @name.definition.module +) @definition.module diff --git a/pr_agent/static_analysis/queries/tree-sitter-cpp-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-cpp-tags.scm new file mode 100644 index 00000000..7a7ad0b9 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-cpp-tags.scm @@ -0,0 +1,15 @@ +(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class + +(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class + +(function_declarator declarator: (identifier) @name.definition.function) @definition.function + +(function_declarator declarator: (field_identifier) @name.definition.function) @definition.function + +(function_declarator declarator: (qualified_identifier scope: (namespace_identifier) @scope name: (identifier) @name.definition.method)) @definition.method + +(type_definition declarator: (type_identifier) @name.definition.type) @definition.type + +(enum_specifier name: (type_identifier) @name.definition.type) @definition.type + +(class_specifier name: (type_identifier) @name.definition.class) @definition.class diff --git a/pr_agent/static_analysis/queries/tree-sitter-elisp-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-elisp-tags.scm new file mode 100644 index 00000000..743c8d8a --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-elisp-tags.scm @@ -0,0 +1,8 @@ +;; defun/defsubst +(function_definition name: (symbol) @name.definition.function) @definition.function + +;; Treat macros as function definitions for the sake of TAGS. +(macro_definition name: (symbol) @name.definition.function) @definition.function + +;; Match function calls +(list (symbol) @name.reference.function) @reference.function diff --git a/pr_agent/static_analysis/queries/tree-sitter-elixir-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-elixir-tags.scm new file mode 100644 index 00000000..9eb39d95 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-elixir-tags.scm @@ -0,0 +1,54 @@ +; Definitions + +; * modules and protocols +(call + target: (identifier) @ignore + (arguments (alias) @name.definition.module) + (#match? @ignore "^(defmodule|defprotocol)$")) @definition.module + +; * functions/macros +(call + target: (identifier) @ignore + (arguments + [ + ; zero-arity functions with no parentheses + (identifier) @name.definition.function + ; regular function clause + (call target: (identifier) @name.definition.function) + ; function clause with a guard clause + (binary_operator + left: (call target: (identifier) @name.definition.function) + operator: "when") + ]) + (#match? @ignore "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @definition.function + +; References + +; ignore calls to kernel/special-forms keywords +(call + target: (identifier) @ignore + (#match? @ignore "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defmodule|defprotocol|defimpl|defstruct|defexception|defoverridable|alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$")) + +; ignore module attributes +(unary_operator + operator: "@" + operand: (call + target: (identifier) @ignore)) + +; * function call +(call + target: [ + ; local + (identifier) @name.reference.call + ; remote + (dot + right: (identifier) @name.reference.call) + ]) @reference.call + +; * pipe into function call +(binary_operator + operator: "|>" + right: (identifier) @name.reference.call) @reference.call + +; * modules +(alias) @name.reference.module @reference.module diff --git a/pr_agent/static_analysis/queries/tree-sitter-elm-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-elm-tags.scm new file mode 100644 index 00000000..8b1589e9 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-elm-tags.scm @@ -0,0 +1,19 @@ +(value_declaration (function_declaration_left (lower_case_identifier) @name.definition.function)) @definition.function + +(function_call_expr (value_expr (value_qid) @name.reference.function)) @reference.function +(exposed_value (lower_case_identifier) @name.reference.function) @reference.function +(type_annotation ((lower_case_identifier) @name.reference.function) (colon)) @reference.function + +(type_declaration ((upper_case_identifier) @name.definition.type)) @definition.type + +(type_ref (upper_case_qid (upper_case_identifier) @name.reference.type)) @reference.type +(exposed_type (upper_case_identifier) @name.reference.type) @reference.type + +(type_declaration (union_variant (upper_case_identifier) @name.definition.union)) @definition.union + +(value_expr (upper_case_qid (upper_case_identifier) @name.reference.union)) @reference.union + + +(module_declaration + (upper_case_qid (upper_case_identifier)) @name.definition.module +) @definition.module diff --git a/pr_agent/static_analysis/queries/tree-sitter-go-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-go-tags.scm new file mode 100644 index 00000000..a32d03aa --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-go-tags.scm @@ -0,0 +1,30 @@ +( + (comment)* @doc + . + (function_declaration + name: (identifier) @name.definition.function) @definition.function + (#strip! @doc "^//\\s*") + (#set-adjacent! @doc @definition.function) +) + +( + (comment)* @doc + . + (method_declaration + name: (field_identifier) @name.definition.method) @definition.method + (#strip! @doc "^//\\s*") + (#set-adjacent! @doc @definition.method) +) + +(call_expression + function: [ + (identifier) @name.reference.call + (parenthesized_expression (identifier) @name.reference.call) + (selector_expression field: (field_identifier) @name.reference.call) + (parenthesized_expression (selector_expression field: (field_identifier) @name.reference.call)) + ]) @reference.call + +(type_spec + name: (type_identifier) @name.definition.type) @definition.type + +(type_identifier) @name.reference.type @reference.type diff --git a/pr_agent/static_analysis/queries/tree-sitter-java-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-java-tags.scm new file mode 100644 index 00000000..3b7290d4 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-java-tags.scm @@ -0,0 +1,20 @@ +(class_declaration + name: (identifier) @name.definition.class) @definition.class + +(method_declaration + name: (identifier) @name.definition.method) @definition.method + +(method_invocation + name: (identifier) @name.reference.call + arguments: (argument_list) @reference.call) + +(interface_declaration + name: (identifier) @name.definition.interface) @definition.interface + +(type_list + (type_identifier) @name.reference.implementation) @reference.implementation + +(object_creation_expression + type: (type_identifier) @name.reference.class) @reference.class + +(superclass (type_identifier) @name.reference.class) @reference.class diff --git a/pr_agent/static_analysis/queries/tree-sitter-javascript-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-javascript-tags.scm new file mode 100644 index 00000000..3bc55c5c --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-javascript-tags.scm @@ -0,0 +1,88 @@ +( + (comment)* @doc + . + (method_definition + name: (property_identifier) @name.definition.method) @definition.method + (#not-eq? @name.definition.method "constructor") + (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") + (#select-adjacent! @doc @definition.method) +) + +( + (comment)* @doc + . + [ + (class + name: (_) @name.definition.class) + (class_declaration + name: (_) @name.definition.class) + ] @definition.class + (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") + (#select-adjacent! @doc @definition.class) +) + +( + (comment)* @doc + . + [ + (function + name: (identifier) @name.definition.function) + (function_declaration + name: (identifier) @name.definition.function) + (generator_function + name: (identifier) @name.definition.function) + (generator_function_declaration + name: (identifier) @name.definition.function) + ] @definition.function + (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") + (#select-adjacent! @doc @definition.function) +) + +( + (comment)* @doc + . + (lexical_declaration + (variable_declarator + name: (identifier) @name.definition.function + value: [(arrow_function) (function)]) @definition.function) + (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") + (#select-adjacent! @doc @definition.function) +) + +( + (comment)* @doc + . + (variable_declaration + (variable_declarator + name: (identifier) @name.definition.function + value: [(arrow_function) (function)]) @definition.function) + (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") + (#select-adjacent! @doc @definition.function) +) + +(assignment_expression + left: [ + (identifier) @name.definition.function + (member_expression + property: (property_identifier) @name.definition.function) + ] + right: [(arrow_function) (function)] +) @definition.function + +(pair + key: (property_identifier) @name.definition.function + value: [(arrow_function) (function)]) @definition.function + +( + (call_expression + function: (identifier) @name.reference.call) @reference.call + (#not-match? @name.reference.call "^(require)$") +) + +(call_expression + function: (member_expression + property: (property_identifier) @name.reference.call) + arguments: (_) @reference.call) + +(new_expression + constructor: (_) @name.reference.class) @reference.class diff --git a/pr_agent/static_analysis/queries/tree-sitter-ocaml-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-ocaml-tags.scm new file mode 100644 index 00000000..52d5a857 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-ocaml-tags.scm @@ -0,0 +1,115 @@ +; Modules +;-------- + +( + (comment)? @doc . + (module_definition (module_binding (module_name) @name.definition.module) @definition.module) + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +(module_path (module_name) @name.reference.module) @reference.module + +; Module types +;-------------- + +( + (comment)? @doc . + (module_type_definition (module_type_name) @name.definition.interface) @definition.interface + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +(module_type_path (module_type_name) @name.reference.implementation) @reference.implementation + +; Functions +;---------- + +( + (comment)? @doc . + (value_definition + [ + (let_binding + pattern: (value_name) @name.definition.function + (parameter)) + (let_binding + pattern: (value_name) @name.definition.function + body: [(fun_expression) (function_expression)]) + ] @definition.function + ) + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +( + (comment)? @doc . + (external (value_name) @name.definition.function) @definition.function + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +(application_expression + function: (value_path (value_name) @name.reference.call)) @reference.call + +(infix_expression + left: (value_path (value_name) @name.reference.call) + operator: (concat_operator) @reference.call + (#eq? @reference.call "@@")) + +(infix_expression + operator: (rel_operator) @reference.call + right: (value_path (value_name) @name.reference.call) + (#eq? @reference.call "|>")) + +; Operator +;--------- + +( + (comment)? @doc . + (value_definition + (let_binding + pattern: (parenthesized_operator (_) @name.definition.function)) @definition.function) + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +[ + (prefix_operator) + (sign_operator) + (pow_operator) + (mult_operator) + (add_operator) + (concat_operator) + (rel_operator) + (and_operator) + (or_operator) + (assign_operator) + (hash_operator) + (indexing_operator) + (let_operator) + (let_and_operator) + (match_operator) +] @name.reference.call @reference.call + +; Classes +;-------- + +( + (comment)? @doc . + [ + (class_definition (class_binding (class_name) @name.definition.class) @definition.class) + (class_type_definition (class_type_binding (class_type_name) @name.definition.class) @definition.class) + ] + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +[ + (class_path (class_name) @name.reference.class) + (class_type_path (class_type_name) @name.reference.class) +] @reference.class + +; Methods +;-------- + +( + (comment)? @doc . + (method_definition (method_name) @name.definition.method) @definition.method + (#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$") +) + +(method_invocation (method_name) @name.reference.call) @reference.call diff --git a/pr_agent/static_analysis/queries/tree-sitter-php-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-php-tags.scm new file mode 100644 index 00000000..61c86fcb --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-php-tags.scm @@ -0,0 +1,26 @@ +(class_declaration + name: (name) @name.definition.class) @definition.class + +(function_definition + name: (name) @name.definition.function) @definition.function + +(method_declaration + name: (name) @name.definition.function) @definition.function + +(object_creation_expression + [ + (qualified_name (name) @name.reference.class) + (variable_name (name) @name.reference.class) + ]) @reference.class + +(function_call_expression + function: [ + (qualified_name (name) @name.reference.call) + (variable_name (name)) @name.reference.call + ]) @reference.call + +(scoped_call_expression + name: (name) @name.reference.call) @reference.call + +(member_call_expression + name: (name) @name.reference.call) @reference.call diff --git a/pr_agent/static_analysis/queries/tree-sitter-python-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-python-tags.scm new file mode 100644 index 00000000..3be5bed9 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-python-tags.scm @@ -0,0 +1,12 @@ +(class_definition + name: (identifier) @name.definition.class) @definition.class + +(function_definition + name: (identifier) @name.definition.function) @definition.function + +(call + function: [ + (identifier) @name.reference.call + (attribute + attribute: (identifier) @name.reference.call) + ]) @reference.call diff --git a/pr_agent/static_analysis/queries/tree-sitter-ql-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-ql-tags.scm new file mode 100644 index 00000000..3164aa25 --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-ql-tags.scm @@ -0,0 +1,26 @@ +(classlessPredicate + name: (predicateName) @name.definition.function) @definition.function + +(memberPredicate + name: (predicateName) @name.definition.method) @definition.method + +(aritylessPredicateExpr + name: (literalId) @name.reference.call) @reference.call + +(module + name: (moduleName) @name.definition.module) @definition.module + +(dataclass + name: (className) @name.definition.class) @definition.class + +(datatype + name: (className) @name.definition.class) @definition.class + +(datatypeBranch + name: (className) @name.definition.class) @definition.class + +(qualifiedRhs + name: (predicateName) @name.reference.call) @reference.call + +(typeExpr + name: (className) @name.reference.type) @reference.type diff --git a/pr_agent/static_analysis/queries/tree-sitter-ruby-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-ruby-tags.scm new file mode 100644 index 00000000..79e71d2d --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-ruby-tags.scm @@ -0,0 +1,64 @@ +; Method definitions + +( + (comment)* @doc + . + [ + (method + name: (_) @name.definition.method) @definition.method + (singleton_method + name: (_) @name.definition.method) @definition.method + ] + (#strip! @doc "^#\\s*") + (#select-adjacent! @doc @definition.method) +) + +(alias + name: (_) @name.definition.method) @definition.method + +(setter + (identifier) @ignore) + +; Class definitions + +( + (comment)* @doc + . + [ + (class + name: [ + (constant) @name.definition.class + (scope_resolution + name: (_) @name.definition.class) + ]) @definition.class + (singleton_class + value: [ + (constant) @name.definition.class + (scope_resolution + name: (_) @name.definition.class) + ]) @definition.class + ] + (#strip! @doc "^#\\s*") + (#select-adjacent! @doc @definition.class) +) + +; Module definitions + +( + (module + name: [ + (constant) @name.definition.module + (scope_resolution + name: (_) @name.definition.module) + ]) @definition.module +) + +; Calls + +(call method: (identifier) @name.reference.call) @reference.call + +( + [(identifier) (constant)] @name.reference.call @reference.call + (#is-not? local) + (#not-match? @name.reference.call "^(lambda|load|require|require_relative|__FILE__|__LINE__)$") +) diff --git a/pr_agent/static_analysis/queries/tree-sitter-rust-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-rust-tags.scm new file mode 100644 index 00000000..dadfa7ac --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-rust-tags.scm @@ -0,0 +1,60 @@ +; ADT definitions + +(struct_item + name: (type_identifier) @name.definition.class) @definition.class + +(enum_item + name: (type_identifier) @name.definition.class) @definition.class + +(union_item + name: (type_identifier) @name.definition.class) @definition.class + +; type aliases + +(type_item + name: (type_identifier) @name.definition.class) @definition.class + +; method definitions + +(declaration_list + (function_item + name: (identifier) @name.definition.method)) @definition.method + +; function definitions + +(function_item + name: (identifier) @name.definition.function) @definition.function + +; trait definitions +(trait_item + name: (type_identifier) @name.definition.interface) @definition.interface + +; module definitions +(mod_item + name: (identifier) @name.definition.module) @definition.module + +; macro definitions + +(macro_definition + name: (identifier) @name.definition.macro) @definition.macro + +; references + +(call_expression + function: (identifier) @name.reference.call) @reference.call + +(call_expression + function: (field_expression + field: (field_identifier) @name.reference.call)) @reference.call + +(macro_invocation + macro: (identifier) @name.reference.call) @reference.call + +; implementations + +(impl_item + trait: (type_identifier) @name.reference.implementation) @reference.implementation + +(impl_item + type: (type_identifier) @name.reference.implementation + !trait) @reference.implementation diff --git a/pr_agent/static_analysis/queries/tree-sitter-typescript-tags.scm b/pr_agent/static_analysis/queries/tree-sitter-typescript-tags.scm new file mode 100644 index 00000000..8a73dccc --- /dev/null +++ b/pr_agent/static_analysis/queries/tree-sitter-typescript-tags.scm @@ -0,0 +1,41 @@ +(function_signature + name: (identifier) @name.definition.function) @definition.function + +(method_signature + name: (property_identifier) @name.definition.method) @definition.method + +(abstract_method_signature + name: (property_identifier) @name.definition.method) @definition.method + +(abstract_class_declaration + name: (type_identifier) @name.definition.class) @definition.class + +(module + name: (identifier) @name.definition.module) @definition.module + +(interface_declaration + name: (type_identifier) @name.definition.interface) @definition.interface + +(type_annotation + (type_identifier) @name.reference.type) @reference.type + +(new_expression + constructor: (identifier) @name.reference.class) @reference.class + +(function_declaration + name: (identifier) @name.definition.function) @definition.function + +(method_definition + name: (property_identifier) @name.definition.method) @definition.method + +(class_declaration + name: (type_identifier) @name.definition.class) @definition.class + +(interface_declaration + name: (type_identifier) @name.definition.class) @definition.class + +(type_alias_declaration + name: (type_identifier) @name.definition.type) @definition.type + +(enum_declaration + name: (identifier) @name.definition.enum) @definition.enum diff --git a/pr_agent/static_analysis/src/file_summary.py b/pr_agent/static_analysis/src/file_summary.py new file mode 100644 index 00000000..e771e9db --- /dev/null +++ b/pr_agent/static_analysis/src/file_summary.py @@ -0,0 +1,153 @@ +import os +from pathlib import Path + +from grep_ast import TreeContext +from grep_ast.parsers import PARSERS +# from pygments.lexers import guess_lexer_for_filename +# from pygments.token import Token +from tree_sitter_languages import get_language, get_parser + + +class FileSummary: + """ + This class is used to summarize the content of a file using tree-sitter queries. + Supported languages: C, C++, C#, elisp, elixir, go, java, javascript, ocaml, php, python, ql, ruby, rust, typescript + """ + def __init__(self, fname_full_path: str, project_base_path, parent_context=True, child_context=False, header_max=0): + self.fname_full_path = fname_full_path + self.project_base_path = project_base_path + self.fname_rel = os.path.relpath(fname_full_path, project_base_path) + self.main_queries_path = Path(__file__).parent.parent / 'queries' + if not os.path.exists(fname_full_path): + print(f"File {fname_full_path} does not exist") + with open(fname_full_path, "r") as f: + code = f.read() + if not code.endswith("\n"): + code += "\n" + self.code = code + self.parent_context = parent_context + self.child_context = child_context + self.header_max = header_max + + def summarize(self): + query_results = self.get_query_results() + summary_str = self.query_processing(query_results) + return summary_str + + def render_tree_sitter(self, lines_of_interest: list): + code = self.code + fname_rel = self.fname_rel + context = TreeContext( + fname_rel, + code, + color=False, + line_number=True, # number the lines (1-indexed) + parent_context=self.parent_context, + child_context=self.child_context, + last_line=False, + margin=0, + mark_lois=False, + loi_pad=0, + header_max=self.header_max, # max number of lines to show in a function header + show_top_of_file_parent_scope=False, + ) + + context.lines_of_interest = set() + context.add_lines_of_interest(lines_of_interest) + context.add_context() + res = context.format() + return res + + def query_processing(self, query_results: list): + if not query_results: + return "" + + output = "" + def_lines = [q['line'] for q in query_results if q['kind'] == "def"] + output += "\n" + output += query_results[0]['fname'] + ":\n" + output += self.render_tree_sitter(def_lines) + return output + + def get_queries_scheme(self, lang) -> str: + # Load the relevant queries + try: + path = os.path.join(self.main_queries_path, f"tree-sitter-{lang}-tags.scm") + with open(path, "r") as f: + return f.read() + + except KeyError: + return "" + + def filename_to_lang(self, filename): + file_extension = os.path.splitext(filename)[1] + lang = PARSERS.get(file_extension) + return lang + + def get_query_results(self): + fname_rel = self.fname_rel + code = self.code + lang = self.filename_to_lang(fname_rel) + if not lang: + return + + try: + language = get_language(lang) + parser = get_parser(lang) + except Exception as err: + print(f"Skipping file {fname_rel}: {err}") + return + + query_scheme_str = self.get_queries_scheme(lang) + tree = parser.parse(bytes(code, "utf-8")) + + # Run the queries + query = language.query(query_scheme_str) + captures = list(query.captures(tree.root_node)) + + # Parse the results into a list of "def" and "ref" tags + visited_set = set() + results = [] + for node, tag in captures: + if tag.startswith("name.definition."): + kind = "def" + elif tag.startswith("name.reference."): + kind = "ref" + else: + continue + + visited_set.add(kind) + result = dict( + fname=fname_rel, + name=node.text.decode("utf-8"), + kind=kind, + line=node.start_point[0], + ) + results.append(result) + + if "ref" in visited_set: + return results + if "def" not in visited_set: + return results + + ## currently we are interested only in defs + # # We saw defs, without any refs + # # Some files only provide defs (cpp, for example) + # # Use pygments to backfill refs + # try: + # lexer = guess_lexer_for_filename(fname, code) + # except Exception: + # return + # + # tokens = list(lexer.get_tokens(code)) + # tokens = [token[1] for token in tokens if token[0] in Token.Name] + # + # for token in tokens: + # result = dict( + # fname=fname, + # name=token, + # kind="ref", + # line=-1, + # ) + # results.append(result) + return results diff --git a/pr_agent/static_analysis/tests/example_files/AES.cpp b/pr_agent/static_analysis/tests/example_files/AES.cpp new file mode 100644 index 00000000..82c1844c --- /dev/null +++ b/pr_agent/static_analysis/tests/example_files/AES.cpp @@ -0,0 +1,428 @@ +// Taken from 'https://github.com/dolphin-emu/dolphin' +// Copyright 2017 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "Common/Assert.h" +#include "Common/CPUDetect.h" +#include "Common/Crypto/AES.h" + +#ifdef _MSC_VER +#include +#else +#if defined(_M_X86_64) +#include +#elif defined(_M_ARM_64) +#include +#include +#endif +#endif + +#ifdef _MSC_VER +#define ATTRIBUTE_TARGET(x) +#else +#define ATTRIBUTE_TARGET(x) [[gnu::target(x)]] +#endif + +namespace Common::AES +{ +// For x64 and arm64, it's very unlikely a user's cpu does not support the accelerated version, +// fallback is just in case. +template +class ContextGeneric final : public Context +{ +public: + ContextGeneric(const u8* key) + { + mbedtls_aes_init(&ctx); + if constexpr (AesMode == Mode::Encrypt) + ASSERT(!mbedtls_aes_setkey_enc(&ctx, key, 128)); + else + ASSERT(!mbedtls_aes_setkey_dec(&ctx, key, 128)); + } + + virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, + size_t len) const override + { + std::array iv_tmp{}; + if (iv) + std::memcpy(&iv_tmp[0], iv, BLOCK_SIZE); + + constexpr int mode = (AesMode == Mode::Encrypt) ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT; + if (mbedtls_aes_crypt_cbc(const_cast(&ctx), mode, len, &iv_tmp[0], buf_in, + buf_out)) + return false; + + if (iv_out) + std::memcpy(iv_out, &iv_tmp[0], BLOCK_SIZE); + return true; + } + +private: + mbedtls_aes_context ctx{}; +}; + +#if defined(_M_X86_64) + +// Note that (for instructions with same data width) the actual instructions emitted vary depending +// on compiler and flags. The naming is somewhat confusing, because VAES cpuid flag was added after +// VAES(VEX.128): +// clang-format off +// instructions | cpuid flag | #define +// AES(128) | AES | - +// VAES(VEX.128) | AES & AVX | __AVX__ +// VAES(VEX.256) | VAES | - +// VAES(EVEX.128) | VAES & AVX512VL | __AVX512VL__ +// VAES(EVEX.256) | VAES & AVX512VL | __AVX512VL__ +// VAES(EVEX.512) | VAES & AVX512F | __AVX512F__ +// clang-format on +template +class ContextAESNI final : public Context +{ + static inline __m128i Aes128KeygenAssistFinish(__m128i key, __m128i kga) + { + __m128i tmp = _mm_shuffle_epi32(kga, _MM_SHUFFLE(3, 3, 3, 3)); + tmp = _mm_xor_si128(tmp, key); + + key = _mm_slli_si128(key, 4); + tmp = _mm_xor_si128(tmp, key); + key = _mm_slli_si128(key, 4); + tmp = _mm_xor_si128(tmp, key); + key = _mm_slli_si128(key, 4); + tmp = _mm_xor_si128(tmp, key); + return tmp; + } + + template + ATTRIBUTE_TARGET("aes") + inline constexpr void StoreRoundKey(__m128i rk) + { + if constexpr (AesMode == Mode::Encrypt) + round_keys[RoundIdx] = rk; + else + { + constexpr size_t idx = NUM_ROUND_KEYS - RoundIdx - 1; + if constexpr (idx == 0 || idx == NUM_ROUND_KEYS - 1) + round_keys[idx] = rk; + else + round_keys[idx] = _mm_aesimc_si128(rk); + } + } + + template + ATTRIBUTE_TARGET("aes") + inline constexpr __m128i Aes128Keygen(__m128i rk) + { + rk = Aes128KeygenAssistFinish(rk, _mm_aeskeygenassist_si128(rk, Rcon)); + StoreRoundKey(rk); + return rk; + } + +public: + ContextAESNI(const u8* key) + { + __m128i rk = _mm_loadu_si128((const __m128i*)key); + StoreRoundKey<0>(rk); + rk = Aes128Keygen<1, 0x01>(rk); + rk = Aes128Keygen<2, 0x02>(rk); + rk = Aes128Keygen<3, 0x04>(rk); + rk = Aes128Keygen<4, 0x08>(rk); + rk = Aes128Keygen<5, 0x10>(rk); + rk = Aes128Keygen<6, 0x20>(rk); + rk = Aes128Keygen<7, 0x40>(rk); + rk = Aes128Keygen<8, 0x80>(rk); + rk = Aes128Keygen<9, 0x1b>(rk); + Aes128Keygen<10, 0x36>(rk); + } + + ATTRIBUTE_TARGET("aes") + inline void CryptBlock(__m128i* iv, const u8* buf_in, u8* buf_out) const + { + __m128i block = _mm_loadu_si128((const __m128i*)buf_in); + + if constexpr (AesMode == Mode::Encrypt) + { + block = _mm_xor_si128(_mm_xor_si128(block, *iv), round_keys[0]); + + for (size_t i = 1; i < Nr; ++i) + block = _mm_aesenc_si128(block, round_keys[i]); + block = _mm_aesenclast_si128(block, round_keys[Nr]); + + *iv = block; + } + else + { + __m128i iv_next = block; + + block = _mm_xor_si128(block, round_keys[0]); + + for (size_t i = 1; i < Nr; ++i) + block = _mm_aesdec_si128(block, round_keys[i]); + block = _mm_aesdeclast_si128(block, round_keys[Nr]); + + block = _mm_xor_si128(block, *iv); + *iv = iv_next; + } + + _mm_storeu_si128((__m128i*)buf_out, block); + } + + // Takes advantage of instruction pipelining to parallelize. + template + ATTRIBUTE_TARGET("aes") + inline void DecryptPipelined(__m128i* iv, const u8* buf_in, u8* buf_out) const + { + constexpr size_t Depth = NumBlocks; + + __m128i block[Depth]; + for (size_t d = 0; d < Depth; d++) + block[d] = _mm_loadu_si128(&((const __m128i*)buf_in)[d]); + + __m128i iv_next[1 + Depth]; + iv_next[0] = *iv; + for (size_t d = 0; d < Depth; d++) + iv_next[1 + d] = block[d]; + + for (size_t d = 0; d < Depth; d++) + block[d] = _mm_xor_si128(block[d], round_keys[0]); + + // The main speedup is here + for (size_t i = 1; i < Nr; ++i) + for (size_t d = 0; d < Depth; d++) + block[d] = _mm_aesdec_si128(block[d], round_keys[i]); + for (size_t d = 0; d < Depth; d++) + block[d] = _mm_aesdeclast_si128(block[d], round_keys[Nr]); + + for (size_t d = 0; d < Depth; d++) + block[d] = _mm_xor_si128(block[d], iv_next[d]); + *iv = iv_next[1 + Depth - 1]; + + for (size_t d = 0; d < Depth; d++) + _mm_storeu_si128(&((__m128i*)buf_out)[d], block[d]); + } + + virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, + size_t len) const override + { + if (len % BLOCK_SIZE) + return false; + + __m128i iv_block = iv ? _mm_loadu_si128((const __m128i*)iv) : _mm_setzero_si128(); + + if constexpr (AesMode == Mode::Decrypt) + { + // On amd zen2...(benchmark, not real-world): + // With AES(128) instructions, BLOCK_DEPTH results in following speedup vs. non-pipelined: 4: + // 18%, 8: 22%, 9: 26%, 10-15: 31%. 16: 8% (register exhaustion). With VAES(VEX.128), 10 gives + // 36% speedup vs. its corresponding baseline. VAES(VEX.128) is ~4% faster than AES(128). The + // result is similar on zen3. + // Zen3 in general is 20% faster than zen2 in aes, and VAES(VEX.256) is 35% faster than + // zen3/VAES(VEX.128). + // It seems like VAES(VEX.256) should be faster? + constexpr size_t BLOCK_DEPTH = 10; + constexpr size_t CHUNK_LEN = BLOCK_DEPTH * BLOCK_SIZE; + while (len >= CHUNK_LEN) + { + DecryptPipelined(&iv_block, buf_in, buf_out); + buf_in += CHUNK_LEN; + buf_out += CHUNK_LEN; + len -= CHUNK_LEN; + } + } + + len /= BLOCK_SIZE; + while (len--) + { + CryptBlock(&iv_block, buf_in, buf_out); + buf_in += BLOCK_SIZE; + buf_out += BLOCK_SIZE; + } + + if (iv_out) + _mm_storeu_si128((__m128i*)iv_out, iv_block); + + return true; + } + +private: + std::array<__m128i, NUM_ROUND_KEYS> round_keys; +}; + +#endif + +#if defined(_M_ARM_64) + +template +class ContextNeon final : public Context +{ +public: + template + inline constexpr void StoreRoundKey(const u32* rk) + { + const uint8x16_t rk_block = vreinterpretq_u8_u32(vld1q_u32(rk)); + if constexpr (AesMode == Mode::Encrypt) + round_keys[RoundIdx] = rk_block; + else + { + constexpr size_t idx = NUM_ROUND_KEYS - RoundIdx - 1; + if constexpr (idx == 0 || idx == NUM_ROUND_KEYS - 1) + round_keys[idx] = rk_block; + else + round_keys[idx] = vaesimcq_u8(rk_block); + } + } + + ContextNeon(const u8* key) + { + constexpr u8 rcon[]{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + std::array rk{}; + + // This uses a nice trick I've seen in wolfssl (not sure original author), + // which uses vaeseq_u8 to assist keygen. + // vaeseq_u8: op1 = SubBytes(ShiftRows(AddRoundKey(op1, op2))) + // given RotWord == ShiftRows for row 1 (rol(x,8)) + // Probably not super fast (moves to/from vector regs constantly), but it is nice and simple. + + std::memcpy(&rk[0], key, KEY_SIZE); + StoreRoundKey<0>(&rk[0]); + for (size_t i = 0; i < rk.size() - Nk; i += Nk) + { + const uint8x16_t enc = vaeseq_u8(vreinterpretq_u8_u32(vmovq_n_u32(rk[i + 3])), vmovq_n_u8(0)); + const u32 temp = vgetq_lane_u32(vreinterpretq_u32_u8(enc), 0); + rk[i + 4] = rk[i + 0] ^ std::rotr(temp, 8) ^ rcon[i / Nk]; + rk[i + 5] = rk[i + 4] ^ rk[i + 1]; + rk[i + 6] = rk[i + 5] ^ rk[i + 2]; + rk[i + 7] = rk[i + 6] ^ rk[i + 3]; + // clang-format off + // Not great + const size_t rki = 1 + i / Nk; + switch (rki) + { + case 1: StoreRoundKey< 1>(&rk[Nk * rki]); break; + case 2: StoreRoundKey< 2>(&rk[Nk * rki]); break; + case 3: StoreRoundKey< 3>(&rk[Nk * rki]); break; + case 4: StoreRoundKey< 4>(&rk[Nk * rki]); break; + case 5: StoreRoundKey< 5>(&rk[Nk * rki]); break; + case 6: StoreRoundKey< 6>(&rk[Nk * rki]); break; + case 7: StoreRoundKey< 7>(&rk[Nk * rki]); break; + case 8: StoreRoundKey< 8>(&rk[Nk * rki]); break; + case 9: StoreRoundKey< 9>(&rk[Nk * rki]); break; + case 10: StoreRoundKey<10>(&rk[Nk * rki]); break; + } + // clang-format on + } + } + + inline void CryptBlock(uint8x16_t* iv, const u8* buf_in, u8* buf_out) const + { + uint8x16_t block = vld1q_u8(buf_in); + + if constexpr (AesMode == Mode::Encrypt) + { + block = veorq_u8(block, *iv); + + for (size_t i = 0; i < Nr - 1; ++i) + block = vaesmcq_u8(vaeseq_u8(block, round_keys[i])); + block = vaeseq_u8(block, round_keys[Nr - 1]); + block = veorq_u8(block, round_keys[Nr]); + + *iv = block; + } + else + { + uint8x16_t iv_next = block; + + for (size_t i = 0; i < Nr - 1; ++i) + block = vaesimcq_u8(vaesdq_u8(block, round_keys[i])); + block = vaesdq_u8(block, round_keys[Nr - 1]); + block = veorq_u8(block, round_keys[Nr]); + + block = veorq_u8(block, *iv); + *iv = iv_next; + } + + vst1q_u8(buf_out, block); + } + + virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, + size_t len) const override + { + if (len % BLOCK_SIZE) + return false; + + uint8x16_t iv_block = iv ? vld1q_u8(iv) : vmovq_n_u8(0); + + len /= BLOCK_SIZE; + while (len--) + { + CryptBlock(&iv_block, buf_in, buf_out); + buf_in += BLOCK_SIZE; + buf_out += BLOCK_SIZE; + } + + if (iv_out) + vst1q_u8(iv_out, iv_block); + + return true; + } + +private: + std::array round_keys; +}; + +#endif + +template +std::unique_ptr CreateContext(const u8* key) +{ + if (cpu_info.bAES) + { +#if defined(_M_X86_64) +#if defined(__AVX__) + // If compiler enables AVX, the intrinsics will generate VAES(VEX.128) instructions. + // In the future we may want to compile the code twice and explicitly override the compiler + // flags. There doesn't seem to be much performance difference between AES(128) and + // VAES(VEX.128) at the moment, though. + if (cpu_info.bAVX) +#endif + return std::make_unique>(key); +#elif defined(_M_ARM_64) + return std::make_unique>(key); +#endif + } + return std::make_unique>(key); +} + +std::unique_ptr CreateContextEncrypt(const u8* key) +{ + return CreateContext(key); +} + +std::unique_ptr CreateContextDecrypt(const u8* key) +{ + return CreateContext(key); +} + +// OFB encryption and decryption are the exact same. We don't encrypt though. +void CryptOFB(const u8* key, const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, size_t size) +{ + mbedtls_aes_context aes_ctx; + size_t iv_offset = 0; + + std::array iv_tmp{}; + if (iv) + std::memcpy(&iv_tmp[0], iv, 16); + + ASSERT(!mbedtls_aes_setkey_enc(&aes_ctx, key, 128)); + mbedtls_aes_crypt_ofb(&aes_ctx, size, &iv_offset, &iv_tmp[0], buf_in, buf_out); + + if (iv_out) + std::memcpy(iv_out, &iv_tmp[0], 16); +} + +} // namespace Common::AES diff --git a/pr_agent/static_analysis/tests/example_files/calc.java b/pr_agent/static_analysis/tests/example_files/calc.java new file mode 100644 index 00000000..d02f228b --- /dev/null +++ b/pr_agent/static_analysis/tests/example_files/calc.java @@ -0,0 +1,586 @@ +package com.houarizegai.calculator.ui; + +import com.houarizegai.calculator.theme.properties.Theme; +import com.houarizegai.calculator.theme.ThemeLoader; + +import java.awt.Cursor; +import java.awt.Font; +import java.awt.event.ItemEvent; +import java.util.Map; +import java.util.regex.Pattern; +import java.awt.Color; +import javax.swing.*; + +import static com.houarizegai.calculator.util.ColorUtil.hex2Color; + +public class CalculatorUI { + + private static final String FONT_NAME = "Comic Sans MS"; + private static final String DOUBLE_OR_NUMBER_REGEX = "([-]?\\d+[.]\\d*)|(\\d+)|(-\\d+)"; + private static final String APPLICATION_TITLE = "Calculator"; + private static final int WINDOW_WIDTH = 410; + private static final int WINDOW_HEIGHT = 600; + private static final int BUTTON_WIDTH = 80; + private static final int BUTTON_HEIGHT = 70; + private static final int MARGIN_X = 20; + private static final int MARGIN_Y = 60; + + private final JFrame window; + private JComboBox comboCalculatorType; + private JComboBox comboTheme; + private JTextField inputScreen; + private JButton btnC; + private JButton btnBack; + private JButton btnMod; + private JButton btnDiv; + private JButton btnMul; + private JButton btnSub; + private JButton btnAdd; + private JButton btn0; + private JButton btn1; + private JButton btn2; + private JButton btn3; + private JButton btn4; + private JButton btn5; + private JButton btn6; + private JButton btn7; + private JButton btn8; + private JButton btn9; + private JButton btnPoint; + private JButton btnEqual; + private JButton btnRoot; + private JButton btnPower; + private JButton btnLog; + + private char selectedOperator = ' '; + private boolean go = true; // For calculate with Opt != (=) + private boolean addToDisplay = true; // Connect numbers in display + private double typedValue = 0; + + private final Map themesMap; + + public CalculatorUI() { + themesMap = ThemeLoader.loadThemes(); + + window = new JFrame(APPLICATION_TITLE); + window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); + window.setLocationRelativeTo(null); + + int[] columns = {MARGIN_X, MARGIN_X + 90, MARGIN_X + 90 * 2, MARGIN_X + 90 * 3, MARGIN_X + 90 * 4}; + int[] rows = {MARGIN_Y, MARGIN_Y + 100, MARGIN_Y + 100 + 80, MARGIN_Y + 100 + 80 * 2, MARGIN_Y + 100 + 80 * 3, MARGIN_Y + 100 + 80 * 4}; + + initInputScreen(columns, rows); + initButtons(columns, rows); + initCalculatorTypeSelector(); + + initThemeSelector(); + + window.setLayout(null); + window.setResizable(false); + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + window.setVisible(true); + } + + public double calculate(double firstNumber, double secondNumber, char operator) { + switch (operator) { + case '+': + return firstNumber + secondNumber; + case '-': + return firstNumber - secondNumber; + case '*': + return firstNumber * secondNumber; + case '/': + return firstNumber / secondNumber; + case '%': + return firstNumber % secondNumber; + case '^': + return Math.pow(firstNumber, secondNumber); + default: + return secondNumber; + } + } + + private void initThemeSelector() { + comboTheme = createComboBox(themesMap.keySet().toArray(new String[0]), 230, 30, "Theme"); + comboTheme.addItemListener(event -> { + if (event.getStateChange() != ItemEvent.SELECTED) + return; + + String selectedTheme = (String) event.getItem(); + applyTheme(themesMap.get(selectedTheme)); + }); + + if (themesMap.entrySet().iterator().hasNext()) { + applyTheme(themesMap.entrySet().iterator().next().getValue()); + } + } + + private void initInputScreen(int[] columns, int[] rows) { + inputScreen = new JTextField("0"); + inputScreen.setBounds(columns[0], rows[0], 350, 70); + inputScreen.setEditable(false); + inputScreen.setBackground(Color.WHITE); + inputScreen.setFont(new Font(FONT_NAME, Font.PLAIN, 33)); + window.add(inputScreen); + } + + private void initCalculatorTypeSelector() { + comboCalculatorType = createComboBox(new String[]{"Standard", "Scientific"}, 20, 30, "Calculator type"); + comboCalculatorType.addItemListener(event -> { + if (event.getStateChange() != ItemEvent.SELECTED) + return; + + String selectedItem = (String) event.getItem(); + switch (selectedItem) { + case "Standard": + window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); + btnRoot.setVisible(false); + btnPower.setVisible(false); + btnLog.setVisible(false); + break; + case "Scientific": + window.setSize(WINDOW_WIDTH + 80, WINDOW_HEIGHT); + btnRoot.setVisible(true); + btnPower.setVisible(true); + btnLog.setVisible(true); + break; + } + }); + } + + private void initButtons(int[] columns, int[] rows) { + btnC = createButton("C", columns[0], rows[1]); + btnC.addActionListener(event -> { + inputScreen.setText("0"); + selectedOperator = ' '; + typedValue = 0; + }); + + btnBack = createButton("<-", columns[1], rows[1]); + btnBack.addActionListener(event -> { + String str = inputScreen.getText(); + StringBuilder str2 = new StringBuilder(); + for (int i = 0; i < (str.length() - 1); i++) { + str2.append(str.charAt(i)); + } + if (str2.toString().equals("")) { + inputScreen.setText("0"); + } else { + inputScreen.setText(str2.toString()); + } + }); + + btnMod = createButton("%", columns[2], rows[1]); + btnMod.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()) || !go) + return; + + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '%'; + go = false; + addToDisplay = false; + }); + + btnDiv = createButton("/", columns[3], rows[1]); + btnDiv.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '/'; + go = false; + addToDisplay = false; + } else { + selectedOperator = '/'; + } + }); + + btn7 = createButton("7", columns[0], rows[2]); + btn7.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("7"); + } else { + inputScreen.setText(inputScreen.getText() + "7"); + } + } else { + inputScreen.setText("7"); + addToDisplay = true; + } + go = true; + }); + + btn8 = createButton("8", columns[1], rows[2]); + btn8.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("8"); + } else { + inputScreen.setText(inputScreen.getText() + "8"); + } + } else { + inputScreen.setText("8"); + addToDisplay = true; + } + go = true; + }); + + btn9 = createButton("9", columns[2], rows[2]); + btn9.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("9"); + } else { + inputScreen.setText(inputScreen.getText() + "9"); + } + } else { + inputScreen.setText("9"); + addToDisplay = true; + } + go = true; + }); + + btnMul = createButton("*", columns[3], rows[2]); + btnMul.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '*'; + go = false; + addToDisplay = false; + } else { + selectedOperator = '*'; + } + }); + + btn4 = createButton("4", columns[0], rows[3]); + btn4.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("4"); + } else { + inputScreen.setText(inputScreen.getText() + "4"); + } + } else { + inputScreen.setText("4"); + addToDisplay = true; + } + go = true; + }); + + btn5 = createButton("5", columns[1], rows[3]); + btn5.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("5"); + } else { + inputScreen.setText(inputScreen.getText() + "5"); + } + } else { + inputScreen.setText("5"); + addToDisplay = true; + } + go = true; + }); + + btn6 = createButton("6", columns[2], rows[3]); + btn6.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("6"); + } else { + inputScreen.setText(inputScreen.getText() + "6"); + } + } else { + inputScreen.setText("6"); + addToDisplay = true; + } + go = true; + }); + + btnSub = createButton("-", columns[3], rows[3]); + btnSub.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + + selectedOperator = '-'; + go = false; + addToDisplay = false; + } else { + selectedOperator = '-'; + } + }); + + btn1 = createButton("1", columns[0], rows[4]); + btn1.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("1"); + } else { + inputScreen.setText(inputScreen.getText() + "1"); + } + } else { + inputScreen.setText("1"); + addToDisplay = true; + } + go = true; + }); + + btn2 = createButton("2", columns[1], rows[4]); + btn2.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("2"); + } else { + inputScreen.setText(inputScreen.getText() + "2"); + } + } else { + inputScreen.setText("2"); + addToDisplay = true; + } + go = true; + }); + + btn3 = createButton("3", columns[2], rows[4]); + btn3.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("3"); + } else { + inputScreen.setText(inputScreen.getText() + "3"); + } + } else { + inputScreen.setText("3"); + addToDisplay = true; + } + go = true; + }); + + btnAdd = createButton("+", columns[3], rows[4]); + btnAdd.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '+'; + go = false; + addToDisplay = false; + } else { + selectedOperator = '+'; + } + }); + + btnPoint = createButton(".", columns[0], rows[5]); + btnPoint.addActionListener(event -> { + if (addToDisplay) { + if (!inputScreen.getText().contains(".")) { + inputScreen.setText(inputScreen.getText() + "."); + } + } else { + inputScreen.setText("0."); + addToDisplay = true; + } + go = true; + }); + + btn0 = createButton("0", columns[1], rows[5]); + btn0.addActionListener(event -> { + if (addToDisplay) { + if (Pattern.matches("[0]*", inputScreen.getText())) { + inputScreen.setText("0"); + } else { + inputScreen.setText(inputScreen.getText() + "0"); + } + } else { + inputScreen.setText("0"); + addToDisplay = true; + } + go = true; + }); + + btnEqual = createButton("=", columns[2], rows[5]); + btnEqual.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '='; + addToDisplay = false; + } + }); + btnEqual.setSize(2 * BUTTON_WIDTH + 10, BUTTON_HEIGHT); + + btnRoot = createButton("√", columns[4], rows[1]); + btnRoot.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = Math.sqrt(Double.parseDouble(inputScreen.getText())); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '√'; + addToDisplay = false; + } + }); + btnRoot.setVisible(false); + + btnPower = createButton("pow", columns[4], rows[2]); + btnPower.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = '^'; + go = false; + addToDisplay = false; + } else { + selectedOperator = '^'; + } + }); + btnPower.setFont(new Font("Comic Sans MS", Font.PLAIN, 24)); + btnPower.setVisible(false); + + btnLog = createButton("ln", columns[4], rows[3]); + btnLog.addActionListener(event -> { + if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText())) + return; + + if (go) { + typedValue = Math.log(Double.parseDouble(inputScreen.getText())); + if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) { + inputScreen.setText(String.valueOf((int) typedValue)); + } else { + inputScreen.setText(String.valueOf(typedValue)); + } + selectedOperator = 'l'; + addToDisplay = false; + } + }); + btnLog.setVisible(false); + } + + private JComboBox createComboBox(String[] items, int x, int y, String toolTip) { + JComboBox combo = new JComboBox<>(items); + combo.setBounds(x, y, 140, 25); + combo.setToolTipText(toolTip); + combo.setCursor(new Cursor(Cursor.HAND_CURSOR)); + window.add(combo); + + return combo; + } + + private JButton createButton(String label, int x, int y) { + JButton btn = new JButton(label); + btn.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT); + btn.setFont(new Font("Comic Sans MS", Font.PLAIN, 28)); + btn.setCursor(new Cursor(Cursor.HAND_CURSOR)); + btn.setFocusable(false); + window.add(btn); + + return btn; + } + + private void applyTheme(Theme theme) { + window.getContentPane().setBackground(hex2Color(theme.getApplicationBackground())); + + comboCalculatorType.setForeground(hex2Color(theme.getTextColor())); + comboTheme.setForeground(hex2Color(theme.getTextColor())); + inputScreen.setForeground(hex2Color(theme.getTextColor())); + btn0.setForeground(hex2Color(theme.getTextColor())); + btn1.setForeground(hex2Color(theme.getTextColor())); + btn2.setForeground(hex2Color(theme.getTextColor())); + btn3.setForeground(hex2Color(theme.getTextColor())); + btn4.setForeground(hex2Color(theme.getTextColor())); + btn5.setForeground(hex2Color(theme.getTextColor())); + btn6.setForeground(hex2Color(theme.getTextColor())); + btn7.setForeground(hex2Color(theme.getTextColor())); + btn8.setForeground(hex2Color(theme.getTextColor())); + btn9.setForeground(hex2Color(theme.getTextColor())); + btnPoint.setForeground(hex2Color(theme.getTextColor())); + btnC.setForeground(hex2Color(theme.getTextColor())); + btnBack.setForeground(hex2Color(theme.getTextColor())); + btnMod.setForeground(hex2Color(theme.getTextColor())); + btnDiv.setForeground(hex2Color(theme.getTextColor())); + btnMul.setForeground(hex2Color(theme.getTextColor())); + btnSub.setForeground(hex2Color(theme.getTextColor())); + btnAdd.setForeground(hex2Color(theme.getTextColor())); + btnRoot.setForeground(hex2Color(theme.getTextColor())); + btnLog.setForeground(hex2Color(theme.getTextColor())); + btnPower.setForeground(hex2Color(theme.getTextColor())); + btnEqual.setForeground(hex2Color(theme.getBtnEqualTextColor())); + + comboCalculatorType.setBackground(hex2Color(theme.getApplicationBackground())); + comboTheme.setBackground(hex2Color(theme.getApplicationBackground())); + inputScreen.setBackground(hex2Color(theme.getApplicationBackground())); + btn0.setBackground(hex2Color(theme.getNumbersBackground())); + btn1.setBackground(hex2Color(theme.getNumbersBackground())); + btn2.setBackground(hex2Color(theme.getNumbersBackground())); + btn3.setBackground(hex2Color(theme.getNumbersBackground())); + btn4.setBackground(hex2Color(theme.getNumbersBackground())); + btn5.setBackground(hex2Color(theme.getNumbersBackground())); + btn6.setBackground(hex2Color(theme.getNumbersBackground())); + btn7.setBackground(hex2Color(theme.getNumbersBackground())); + btn8.setBackground(hex2Color(theme.getNumbersBackground())); + btn9.setBackground(hex2Color(theme.getNumbersBackground())); + btnPoint.setBackground(hex2Color(theme.getNumbersBackground())); + btnC.setBackground(hex2Color(theme.getOperatorBackground())); + btnBack.setBackground(hex2Color(theme.getOperatorBackground())); + btnMod.setBackground(hex2Color(theme.getOperatorBackground())); + btnDiv.setBackground(hex2Color(theme.getOperatorBackground())); + btnMul.setBackground(hex2Color(theme.getOperatorBackground())); + btnSub.setBackground(hex2Color(theme.getOperatorBackground())); + btnAdd.setBackground(hex2Color(theme.getOperatorBackground())); + btnRoot.setBackground(hex2Color(theme.getOperatorBackground())); + btnLog.setBackground(hex2Color(theme.getOperatorBackground())); + btnPower.setBackground(hex2Color(theme.getOperatorBackground())); + btnEqual.setBackground(hex2Color(theme.getBtnEqualBackground())); + } +} \ No newline at end of file diff --git a/pr_agent/static_analysis/tests/example_files/match.ts b/pr_agent/static_analysis/tests/example_files/match.ts new file mode 100644 index 00000000..1368cfe0 --- /dev/null +++ b/pr_agent/static_analysis/tests/example_files/match.ts @@ -0,0 +1,138 @@ +// @ts-ignore +import { Pattern } from './types/Pattern'; // @ts-ignore +import { Match } from './types/Match'; // @ts-ignore +import * as symbols from './internals/symbols'; // @ts-ignore +import { matchPattern } from './internals/helpers'; // @ts-ignore + +type MatchState = + | { matched: true; value: output } + | { matched: false; value: undefined }; + +const unmatched: MatchState = { + matched: false, + value: undefined, +}; + +/** + * `match` creates a **pattern matching expression**. + * * Use `.with(pattern, handler)` to pattern match on the input. + * * Use `.exhaustive()` or `.otherwise(() => defaultValue)` to end the expression and get the result. + * + * [Read the documentation for `match` on GitHub](https://github.com/gvergnaud/ts-pattern#match) + * + * @example + * declare let input: "A" | "B"; + * + * return match(input) + * .with("A", () => "It's an A!") + * .with("B", () => "It's a B!") + * .exhaustive(); + * + */ +export function match( + value: input +): Match { + return new MatchExpression(value, unmatched) as any; +} + +/** + * This class represents a match expression. It follows the + * builder pattern, we chain methods to add features to the expression + * until we call `.exhaustive`, `.otherwise` or the unsafe `.run` + * method to execute it. + * + * The types of this class aren't public, the public type definition + * can be found in src/types/Match.ts. + */ +class MatchExpression { + constructor(private input: input, private state: MatchState) {} + + with(...args: any[]): MatchExpression { + if (this.state.matched) return this; + + const handler: (selection: unknown, value: input) => output = + args[args.length - 1]; + + const patterns: Pattern[] = [args[0]]; + let predicate: ((value: input) => unknown) | undefined = undefined; + + if (args.length === 3 && typeof args[1] === 'function') { + // case with guard as second argument + patterns.push(args[0]); + predicate = args[1]; + } else if (args.length > 2) { + // case with several patterns + patterns.push(...args.slice(1, args.length - 1)); + } + + let hasSelections = false; + let selected: Record = {}; + const select = (key: string, value: unknown) => { + hasSelections = true; + selected[key] = value; + }; + + const matched = + patterns.some((pattern) => matchPattern(pattern, this.input, select)) && + (predicate ? Boolean(predicate(this.input)) : true); + + const selections = hasSelections + ? symbols.anonymousSelectKey in selected + ? selected[symbols.anonymousSelectKey] + : selected + : this.input; + + const state = matched + ? { + matched: true as const, + value: handler(selections, this.input), + } + : unmatched; + + return new MatchExpression(this.input, state); + } + + when( + predicate: (value: input) => unknown, + handler: (selection: input, value: input) => output + ): MatchExpression { + if (this.state.matched) return this; + + const matched = Boolean(predicate(this.input)); + + return new MatchExpression( + this.input, + matched + ? { matched: true, value: handler(this.input, this.input) } + : unmatched + ); + } + + otherwise(handler: (value: input) => output): output { + if (this.state.matched) return this.state.value; + return handler(this.input); + } + + exhaustive(): output { + return this.run(); + } + + run(): output { + if (this.state.matched) return this.state.value; + + let displayedValue; + try { + displayedValue = JSON.stringify(this.input); + } catch (e) { + displayedValue = this.input; + } + + throw new Error( + `Pattern matching error: no pattern matches value ${displayedValue}` + ); + } + + returnType() { + return this; + } +} diff --git a/pr_agent/static_analysis/tests/test_file_summary.py b/pr_agent/static_analysis/tests/test_file_summary.py new file mode 100644 index 00000000..c5c84931 --- /dev/null +++ b/pr_agent/static_analysis/tests/test_file_summary.py @@ -0,0 +1,43 @@ +import os +from pathlib import Path + +from pr_agent.static_analysis.src.file_summary import FileSummary + + +class TestFileSummary: + def setup_method(self): + self.base_path = Path(__file__).parent + self.project_base_path = Path(__file__).parent.parent.parent.parent + + def test_file_summary_cpp(self): + fname = os.path.join(self.base_path, 'example_files/AES.cpp') + if not os.path.exists(fname): + print(f"File {fname} does not exist") + return False + fname_summary = FileSummary(fname, self.project_base_path, parent_context=False, child_context=False, + header_max=0) + output = fname_summary.summarize() + expected_output = '\npr_agent/static_analysis/tests/example_files/AES.cpp:\n...⋮...\n 32│namespace Common::AES\n 33│{\n...⋮...\n 36│template \n 37│class ContextGeneric final : public Context\n 38│{\n 39│public:\n 40│ ContextGeneric(const u8* key)\n...⋮...\n 49│ virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,\n...⋮...\n 70│#if defined(_M_X86_64)\n 71│\n...⋮...\n 84│template \n 85│class ContextAESNI final : public Context\n 86│{\n 87│ static inline __m128i Aes128KeygenAssistFinish(__m128i key, __m128i kga)\n...⋮...\n103│ inline constexpr void StoreRoundKey(__m128i rk)\n...⋮...\n119│ inline constexpr __m128i Aes128Keygen(__m128i rk)\n...⋮...\n127│ ContextAESNI(const u8* key)\n...⋮...\n144│ inline void CryptBlock(__m128i* iv, const u8* buf_in, u8* buf_out) const\n...⋮...\n178│ inline void DecryptPipelined(__m128i* iv, const u8* buf_in, u8* buf_out) const\n...⋮...\n209│ virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,\n...⋮...\n259│#if defined(_M_ARM_64)\n260│\n261│template \n262│class ContextNeon final : public Context\n263│{\n264│public:\n265│ template \n266│ inline constexpr void StoreRoundKey(const u32* rk)\n...⋮...\n281│ ContextNeon(const u8* key)\n...⋮...\n322│ inline void CryptBlock(uint8x16_t* iv, const u8* buf_in, u8* buf_out) const\n...⋮...\n353│ virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,\n...⋮...\n381│template \n382│std::unique_ptr CreateContext(const u8* key)\n...⋮...\n402│std::unique_ptr CreateContextEncrypt(const u8* key)\n...⋮...\n407│std::unique_ptr CreateContextDecrypt(const u8* key)\n...⋮...\n413│void CryptOFB(const u8* key, const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, size_t size)\n...⋮...\n' + assert output == expected_output + + def test_file_typescript(self): + fname = os.path.join(self.base_path, 'example_files/match.ts') + if not os.path.exists(fname): + print(f"File {fname} does not exist") + return False + fname_summary = FileSummary(fname, self.project_base_path, parent_context=False, child_context=False, + header_max=0) + output = fname_summary.summarize() + expected_output = '\npr_agent/static_analysis/tests/example_files/match.ts:\n...⋮...\n 6│type MatchState =\n...⋮...\n 31│export function match(\n...⋮...\n 46│class MatchExpression {\n 47│ constructor(private input: input, private state: MatchState) {}\n 48│\n 49│ with(...args: any[]): MatchExpression {\n...⋮...\n 94│ when(\n...⋮...\n110│ otherwise(handler: (value: input) => output): output {\n...⋮...\n115│ exhaustive(): output {\n...⋮...\n119│ run(): output {\n...⋮...\n134│ returnType() {\n...⋮...\n' + assert output == expected_output + + def test_file_java(self): + fname = os.path.join(self.base_path, 'example_files/calc.java') + if not os.path.exists(fname): + print(f"File {fname} does not exist") + return False + fname_summary = FileSummary(fname, self.project_base_path, parent_context=False, child_context=False, + header_max=0) + output = fname_summary.summarize() + expected_output = '\npr_agent/static_analysis/tests/example_files/calc.java:\n...⋮...\n 16│public class CalculatorUI {\n 17│\n...⋮...\n 84│ public double calculate(double firstNumber, double secondNumber, char operator) {\n...⋮...\n103│ private void initThemeSelector() {\n...⋮...\n118│ private void initInputScreen(int[] columns, int[] rows) {\n...⋮...\n127│ private void initCalculatorTypeSelector() {\n...⋮...\n151│ private void initButtons(int[] columns, int[] rows) {\n...⋮...\n510│ private JComboBox createComboBox(String[] items, int x, int y, String toolTip) {\n...⋮...\n520│ private JButton createButton(String label, int x, int y) {\n...⋮...\n531│ private void applyTheme(Theme theme) {\n...⋮...\n' + assert output == expected_output diff --git a/requirements.txt b/requirements.txt index 05f13ad7..343557ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,6 +34,12 @@ langchain-openai==0.2.0 langchain-pinecone==0.2.0 langchain-chroma==0.1.4 chromadb==0.5.7 +# +tree_sitter==0.21.3 +tree_sitter_languages==1.10.2 +grep_ast==0.3.3 +#Pygments==2.18.0 + # Uncomment the following lines to enable the 'similar issue' tool # pinecone-client # pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main