From 1a00e61239df0c9350811cdf24529b7862f266b9 Mon Sep 17 00:00:00 2001 From: isExample Date: Wed, 25 Jun 2025 23:17:42 +0900 Subject: [PATCH 1/8] feat: add configuration property 'ignore_language_framework' --- pr_agent/settings/configuration.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index f028c9f0..f3bd765f 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -56,6 +56,7 @@ ignore_pr_source_branches = [] # a list of regular expressions of source branche ignore_pr_labels = [] # labels to ignore from PR agent when an PR is created ignore_pr_authors = [] # authors to ignore from PR agent when an PR is created ignore_repositories = [] # a list of regular expressions of repository full names (e.g. "org/repo") to ignore from PR agent processing +ignore_language_framework = [] # a list of code-generation languages or frameworks (e.g. 'protobuf', 'go_gen') whose auto-generated source files will be excluded from analysis # is_auto_command = false # will be auto-set to true if the command is triggered by an automation enable_ai_metadata = false # will enable adding ai metadata From c7241ca093c2dfb974b2edd071c1a7993054394f Mon Sep 17 00:00:00 2001 From: isExample Date: Wed, 25 Jun 2025 23:39:14 +0900 Subject: [PATCH 2/8] feat: support ignore_language_framework via generated_code_ignore.toml - [generated_code] table defines default glob patterns for code-generation tools - merge generated_code globs into ignore logic --- pr_agent/algo/file_filter.py | 7 +++++++ pr_agent/config_loader.py | 1 + pr_agent/settings/generated_code_ignore.toml | 14 ++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 pr_agent/settings/generated_code_ignore.toml diff --git a/pr_agent/algo/file_filter.py b/pr_agent/algo/file_filter.py index 79bb4d8e..93a7e74c 100644 --- a/pr_agent/algo/file_filter.py +++ b/pr_agent/algo/file_filter.py @@ -19,6 +19,13 @@ def filter_ignored(files, platform = 'github'): glob_setting = glob_setting.strip('[]').split(",") patterns += [fnmatch.translate(glob) for glob in glob_setting] + code_generators = get_settings().config.get('ignore_language_framework', []) + for cg in code_generators: + glob_patterns = get_settings().generated_code.get(cg, []) + if isinstance(glob_patterns, str): + glob_patterns = [glob_patterns] + patterns += [fnmatch.translate(glob) for glob in glob_patterns] + # compile all valid patterns compiled_patterns = [] for r in patterns: diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index f525d893..f19f1067 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -14,6 +14,7 @@ global_settings = Dynaconf( settings_files=[join(current_dir, f) for f in [ "settings/configuration.toml", "settings/ignore.toml", + "settings/generated_code_ignore.toml", "settings/language_extensions.toml", "settings/pr_reviewer_prompts.toml", "settings/pr_questions_prompts.toml", diff --git a/pr_agent/settings/generated_code_ignore.toml b/pr_agent/settings/generated_code_ignore.toml new file mode 100644 index 00000000..012d9127 --- /dev/null +++ b/pr_agent/settings/generated_code_ignore.toml @@ -0,0 +1,14 @@ +[generated_code] + +# Protocol Buffers generated code. +protobuf = [ + "**/*.pb.go", + "**/*.pb.cc", + "**/*_pb2.py" +] + +# Go generator output files. +go_gen = [ + "**/*_gen.go", + "**/*generated.go" +] From 8e210f8ea07fb45ce265d18cfccfd2127f8306bb Mon Sep 17 00:00:00 2001 From: isExample Date: Thu, 26 Jun 2025 13:29:59 +0900 Subject: [PATCH 3/8] chore: update generated code sources --- pr_agent/settings/generated_code_ignore.toml | 34 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/pr_agent/settings/generated_code_ignore.toml b/pr_agent/settings/generated_code_ignore.toml index 012d9127..52f3bfff 100644 --- a/pr_agent/settings/generated_code_ignore.toml +++ b/pr_agent/settings/generated_code_ignore.toml @@ -1,13 +1,41 @@ [generated_code] -# Protocol Buffers generated code. +# Protocol Buffers protobuf = [ "**/*.pb.go", "**/*.pb.cc", - "**/*_pb2.py" + "**/*_pb2.py", + "**/*.pb.swift", + "**/*.pb.rb", + "**/*.pb.php", + "**/*.pb.h" ] -# Go generator output files. +# OpenAPI / Swagger stubs +openapi = [ + "**/__generated__/**", + "**/openapi_client/**", + "**/openapi_server/**" +] +swagger = [ + "**/swagger.json", + "**/swagger.yaml" +] + +# GraphQL codegen +graphql = [ + "**/*.graphql.ts", + "**/*.generated.ts", + "**/*.graphql.js" +] + +# RPC / gRPC Generators +grpc_python = ["**/*_grpc.py"] +grpc_java = ["**/*Grpc.java"] +grpc_csharp = ["**/*Grpc.cs"] +grpc_typescript = ["**/*_grpc.ts", "**/*_grpc.js"] + +# Go code generators go_gen = [ "**/*_gen.go", "**/*generated.go" From e4f477dae00358895cfdee49fa3fb7ca882339e5 Mon Sep 17 00:00:00 2001 From: isExample Date: Thu, 26 Jun 2025 13:32:27 +0900 Subject: [PATCH 4/8] test: add filtering test for auto-generated files --- tests/unittest/test_file_filter.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/unittest/test_file_filter.py b/tests/unittest/test_file_filter.py index b35e351f..064290f3 100644 --- a/tests/unittest/test_file_filter.py +++ b/tests/unittest/test_file_filter.py @@ -80,3 +80,27 @@ class TestIgnoreFilter: filtered_files = filter_ignored(files) assert filtered_files == expected, f"Expected {[file.filename for file in expected]}, but got {[file.filename for file in filtered_files]}." + + def test_language_framework_ignores(self, monkeypatch): + """ + Test files are ignored based on language/framework mapping (e.g., protobuf). + """ + monkeypatch.setattr(global_settings.config, 'ignore_language_framework', ['protobuf', 'go_gen']) + + files = [ + type('', (object,), {'filename': 'main.go'})(), + type('', (object,), {'filename': 'dir1/service.pb.go'})(), + type('', (object,), {'filename': 'dir1/dir/data_pb2.py'})(), + type('', (object,), {'filename': 'file.py'})(), + type('', (object,), {'filename': 'dir2/file_gen.go'})() + ] + expected = [ + files[0], + files[3] + ] + + filtered = filter_ignored(files) + assert filtered == expected, ( + f"Expected {[f.filename for f in expected]}, " + f"but got {[f.filename for f in filtered]}" + ) From 2d1afc634ed9f70997b8b4f47c59ffd2644dae04 Mon Sep 17 00:00:00 2001 From: isExample Date: Thu, 26 Jun 2025 13:43:18 +0900 Subject: [PATCH 5/8] docs: ignore_language_framework property --- docs/docs/usage-guide/additional_configurations.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/usage-guide/additional_configurations.md b/docs/docs/usage-guide/additional_configurations.md index 8d205865..98ed8472 100644 --- a/docs/docs/usage-guide/additional_configurations.md +++ b/docs/docs/usage-guide/additional_configurations.md @@ -250,3 +250,15 @@ Where the `ignore_pr_authors` is a list of usernames that you want to ignore. !!! note There is one specific case where bots will receive an automatic response - when they generated a PR with a _failed test_. In that case, the [`ci_feedback`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) tool will be invoked. + +### Ignoring Generated Files by Language/Framework + +To automatically exclude files generated by specific languages or frameworks, you can add the following to your `configuration.toml` file: + +``` +[config] +ignore_language_framework = ['protobuf', ...] +``` + +You can view the list of auto-generated file patterns in `generated_code_ignore.toml`. +Files matching these glob patterns will be automatically excluded from PR Agent analysis. \ No newline at end of file From 87a245bf9c402103e9c25418d66ba889e01bf0bf Mon Sep 17 00:00:00 2001 From: isExample Date: Thu, 26 Jun 2025 15:26:12 +0900 Subject: [PATCH 6/8] fix: support root-level matching for '**/' globs - generate an additional regex to match root-level files alongside '**/' patterns. - ensure files in the repo root are correctly excluded from analysis. --- pr_agent/algo/file_filter.py | 12 ++++++++++-- tests/unittest/test_file_filter.py | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pr_agent/algo/file_filter.py b/pr_agent/algo/file_filter.py index 93a7e74c..0a0b3480 100644 --- a/pr_agent/algo/file_filter.py +++ b/pr_agent/algo/file_filter.py @@ -17,14 +17,14 @@ def filter_ignored(files, platform = 'github'): glob_setting = get_settings().ignore.glob if isinstance(glob_setting, str): # --ignore.glob=[.*utils.py], --ignore.glob=.*utils.py glob_setting = glob_setting.strip('[]').split(",") - patterns += [fnmatch.translate(glob) for glob in glob_setting] + patterns += translate_globs_to_regexes(glob_setting) code_generators = get_settings().config.get('ignore_language_framework', []) for cg in code_generators: glob_patterns = get_settings().generated_code.get(cg, []) if isinstance(glob_patterns, str): glob_patterns = [glob_patterns] - patterns += [fnmatch.translate(glob) for glob in glob_patterns] + patterns += translate_globs_to_regexes(glob_patterns) # compile all valid patterns compiled_patterns = [] @@ -73,3 +73,11 @@ def filter_ignored(files, platform = 'github'): print(f"Could not filter file list: {e}") return files + +def translate_globs_to_regexes(globs: list): + regexes = [] + for pattern in globs: + regexes.append(fnmatch.translate(pattern)) + if pattern.startswith("**/"): # cover root-level files + regexes.append(fnmatch.translate(pattern[3:])) + return regexes diff --git a/tests/unittest/test_file_filter.py b/tests/unittest/test_file_filter.py index 064290f3..272a20b4 100644 --- a/tests/unittest/test_file_filter.py +++ b/tests/unittest/test_file_filter.py @@ -92,7 +92,8 @@ class TestIgnoreFilter: type('', (object,), {'filename': 'dir1/service.pb.go'})(), type('', (object,), {'filename': 'dir1/dir/data_pb2.py'})(), type('', (object,), {'filename': 'file.py'})(), - type('', (object,), {'filename': 'dir2/file_gen.go'})() + type('', (object,), {'filename': 'dir2/file_gen.go'})(), + type('', (object,), {'filename': 'file.generated.go'})() ] expected = [ files[0], From bd9ddc8b86d6c08008e984f96f8e28553c8d5483 Mon Sep 17 00:00:00 2001 From: isExample Date: Sun, 29 Jun 2025 02:06:40 +0900 Subject: [PATCH 7/8] fix: avoid crash when ignore_language_framework is not a list --- pr_agent/algo/file_filter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pr_agent/algo/file_filter.py b/pr_agent/algo/file_filter.py index 0a0b3480..109aede9 100644 --- a/pr_agent/algo/file_filter.py +++ b/pr_agent/algo/file_filter.py @@ -2,6 +2,7 @@ import fnmatch import re from pr_agent.config_loader import get_settings +from pr_agent.log import get_logger def filter_ignored(files, platform = 'github'): @@ -20,6 +21,9 @@ def filter_ignored(files, platform = 'github'): patterns += translate_globs_to_regexes(glob_setting) code_generators = get_settings().config.get('ignore_language_framework', []) + if isinstance(code_generators, str): + get_logger().warning("'ignore_language_framework' should be a list. Skipping language framework filtering.") + code_generators = [] for cg in code_generators: glob_patterns = get_settings().generated_code.get(cg, []) if isinstance(glob_patterns, str): From 7d50625bd67f6d4705e8ac49d0e7a014eb7af6b0 Mon Sep 17 00:00:00 2001 From: isExample Date: Sun, 29 Jun 2025 02:18:29 +0900 Subject: [PATCH 8/8] test: skip filtering when ignore_language_framework is misconfigured --- tests/unittest/test_file_filter.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unittest/test_file_filter.py b/tests/unittest/test_file_filter.py index 272a20b4..e461618c 100644 --- a/tests/unittest/test_file_filter.py +++ b/tests/unittest/test_file_filter.py @@ -105,3 +105,28 @@ class TestIgnoreFilter: f"Expected {[f.filename for f in expected]}, " f"but got {[f.filename for f in filtered]}" ) + + def test_skip_invalid_ignore_language_framework(self, monkeypatch): + """ + Test skipping of generated code filtering when ignore_language_framework is not a list + """ + monkeypatch.setattr(global_settings.config, 'ignore_language_framework', 'protobuf') + + files = [ + type('', (object,), {'filename': 'main.go'})(), + type('', (object,), {'filename': 'file.py'})(), + type('', (object,), {'filename': 'dir1/service.pb.go'})(), + type('', (object,), {'filename': 'file_pb2.py'})() + ] + expected = [ + files[0], + files[1], + files[2], + files[3] + ] + + filtered = filter_ignored(files) + assert filtered == expected, ( + f"Expected {[f.filename for f in expected]}, " + f"but got {[f.filename for f in filtered]}" + )