mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-01 19:30:40 +08:00
Merge pull request #1898 from isExample/feat/ignore-language-framework
feat: support ignoring auto-generated files by language/framework
This commit is contained in:
@ -250,3 +250,15 @@ Where the `ignore_pr_authors` is a list of usernames that you want to ignore.
|
|||||||
|
|
||||||
!!! note
|
!!! 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.
|
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.
|
@ -2,6 +2,7 @@ import fnmatch
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
|
from pr_agent.log import get_logger
|
||||||
|
|
||||||
|
|
||||||
def filter_ignored(files, platform = 'github'):
|
def filter_ignored(files, platform = 'github'):
|
||||||
@ -17,7 +18,17 @@ def filter_ignored(files, platform = 'github'):
|
|||||||
glob_setting = get_settings().ignore.glob
|
glob_setting = get_settings().ignore.glob
|
||||||
if isinstance(glob_setting, str): # --ignore.glob=[.*utils.py], --ignore.glob=.*utils.py
|
if isinstance(glob_setting, str): # --ignore.glob=[.*utils.py], --ignore.glob=.*utils.py
|
||||||
glob_setting = glob_setting.strip('[]').split(",")
|
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', [])
|
||||||
|
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):
|
||||||
|
glob_patterns = [glob_patterns]
|
||||||
|
patterns += translate_globs_to_regexes(glob_patterns)
|
||||||
|
|
||||||
# compile all valid patterns
|
# compile all valid patterns
|
||||||
compiled_patterns = []
|
compiled_patterns = []
|
||||||
@ -66,3 +77,11 @@ def filter_ignored(files, platform = 'github'):
|
|||||||
print(f"Could not filter file list: {e}")
|
print(f"Could not filter file list: {e}")
|
||||||
|
|
||||||
return files
|
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
|
||||||
|
@ -14,6 +14,7 @@ global_settings = Dynaconf(
|
|||||||
settings_files=[join(current_dir, f) for f in [
|
settings_files=[join(current_dir, f) for f in [
|
||||||
"settings/configuration.toml",
|
"settings/configuration.toml",
|
||||||
"settings/ignore.toml",
|
"settings/ignore.toml",
|
||||||
|
"settings/generated_code_ignore.toml",
|
||||||
"settings/language_extensions.toml",
|
"settings/language_extensions.toml",
|
||||||
"settings/pr_reviewer_prompts.toml",
|
"settings/pr_reviewer_prompts.toml",
|
||||||
"settings/pr_questions_prompts.toml",
|
"settings/pr_questions_prompts.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_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_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_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
|
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
|
enable_ai_metadata = false # will enable adding ai metadata
|
||||||
|
42
pr_agent/settings/generated_code_ignore.toml
Normal file
42
pr_agent/settings/generated_code_ignore.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[generated_code]
|
||||||
|
|
||||||
|
# Protocol Buffers
|
||||||
|
protobuf = [
|
||||||
|
"**/*.pb.go",
|
||||||
|
"**/*.pb.cc",
|
||||||
|
"**/*_pb2.py",
|
||||||
|
"**/*.pb.swift",
|
||||||
|
"**/*.pb.rb",
|
||||||
|
"**/*.pb.php",
|
||||||
|
"**/*.pb.h"
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
]
|
@ -80,3 +80,53 @@ class TestIgnoreFilter:
|
|||||||
|
|
||||||
filtered_files = filter_ignored(files)
|
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]}."
|
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'})(),
|
||||||
|
type('', (object,), {'filename': 'file.generated.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]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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]}"
|
||||||
|
)
|
||||||
|
Reference in New Issue
Block a user