mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-06 13:50:44 +08:00
code suggestions
This commit is contained in:
@ -53,8 +53,7 @@ class GitProvider(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_code_suggestion(self, body: str, relevant_file: str,
|
def publish_code_suggestions(self, code_suggestions: list):
|
||||||
relevant_lines_start: int, relevant_lines_end: int):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@ -146,24 +146,32 @@ class GithubProvider(GitProvider):
|
|||||||
def publish_inline_comments(self, comments: list[dict]):
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
self.pr.create_review(commit=self.last_commit_id, comments=comments)
|
self.pr.create_review(commit=self.last_commit_id, comments=comments)
|
||||||
|
|
||||||
def publish_code_suggestion(self, body: str,
|
def publish_code_suggestions(self, code_suggestions: list):
|
||||||
relevant_file: str,
|
"""
|
||||||
relevant_lines_start: int,
|
Publishes code suggestions as comments on the PR.
|
||||||
relevant_lines_end: int):
|
In practice current APU enables to send only one code suggestion per comment. Might change in the future.
|
||||||
if not relevant_lines_start or relevant_lines_start == -1:
|
"""
|
||||||
if settings.config.verbosity_level >= 2:
|
post_parameters_list = []
|
||||||
logging.exception(f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
|
import github.PullRequestComment
|
||||||
return False
|
for suggestion in code_suggestions:
|
||||||
|
body = suggestion['body']
|
||||||
|
relevant_file = suggestion['relevant_file']
|
||||||
|
relevant_lines_start = suggestion['relevant_lines_start']
|
||||||
|
relevant_lines_end = suggestion['relevant_lines_end']
|
||||||
|
|
||||||
if relevant_lines_end < relevant_lines_start:
|
if not relevant_lines_start or relevant_lines_start == -1:
|
||||||
if settings.config.verbosity_level >= 2:
|
if settings.config.verbosity_level >= 2:
|
||||||
logging.exception(f"Failed to publish code suggestion, "
|
logging.exception(
|
||||||
f"relevant_lines_end is {relevant_lines_end} and "
|
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
|
||||||
f"relevant_lines_start is {relevant_lines_start}")
|
continue
|
||||||
return False
|
|
||||||
|
if relevant_lines_end < relevant_lines_start:
|
||||||
|
if settings.config.verbosity_level >= 2:
|
||||||
|
logging.exception(f"Failed to publish code suggestion, "
|
||||||
|
f"relevant_lines_end is {relevant_lines_end} and "
|
||||||
|
f"relevant_lines_start is {relevant_lines_start}")
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
|
||||||
import github.PullRequestComment
|
|
||||||
if relevant_lines_end > relevant_lines_start:
|
if relevant_lines_end > relevant_lines_start:
|
||||||
post_parameters = {
|
post_parameters = {
|
||||||
"body": body,
|
"body": body,
|
||||||
@ -181,17 +189,19 @@ class GithubProvider(GitProvider):
|
|||||||
"line": relevant_lines_start,
|
"line": relevant_lines_start,
|
||||||
"side": "RIGHT",
|
"side": "RIGHT",
|
||||||
}
|
}
|
||||||
headers, data = self.pr._requester.requestJsonAndCheck(
|
|
||||||
"POST", f"{self.pr.url}/comments", input=post_parameters
|
try:
|
||||||
)
|
headers, data = self.pr._requester.requestJsonAndCheck(
|
||||||
github.PullRequestComment.PullRequestComment(
|
"POST", f"{self.pr.url}/comments", input=post_parameters
|
||||||
self.pr._requester, headers, data, completed=True
|
)
|
||||||
)
|
github.PullRequestComment.PullRequestComment(
|
||||||
return True
|
self.pr._requester, headers, data, completed=True
|
||||||
except Exception as e:
|
)
|
||||||
if settings.config.verbosity_level >= 2:
|
return True
|
||||||
logging.error(f"Failed to publish code suggestion, error: {e}")
|
except Exception as e:
|
||||||
return False
|
if settings.config.verbosity_level >= 2:
|
||||||
|
logging.error(f"Failed to publish code suggestion, error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def remove_initial_comment(self):
|
def remove_initial_comment(self):
|
||||||
try:
|
try:
|
||||||
@ -314,7 +324,6 @@ class GithubProvider(GitProvider):
|
|||||||
for p in pr_types:
|
for p in pr_types:
|
||||||
color = label_color_map.get(p, "d1bcf9") # default to "Other" color
|
color = label_color_map.get(p, "d1bcf9") # default to "Other" color
|
||||||
post_parameters.append({"name": p, "color": color})
|
post_parameters.append({"name": p, "color": color})
|
||||||
post_parameters.append({"name": p, "color": colors[ind]})
|
|
||||||
headers, data = self.pr._requester.requestJsonAndCheck(
|
headers, data = self.pr._requester.requestJsonAndCheck(
|
||||||
"PUT", f"{self.pr.issue_url}/labels", input=post_parameters
|
"PUT", f"{self.pr.issue_url}/labels", input=post_parameters
|
||||||
)
|
)
|
||||||
|
@ -135,26 +135,29 @@ class GitLabProvider(GitProvider):
|
|||||||
self.mr.discussions.create({'body': body,
|
self.mr.discussions.create({'body': body,
|
||||||
'position': pos_obj})
|
'position': pos_obj})
|
||||||
|
|
||||||
def publish_code_suggestion(self, body: str,
|
def publish_code_suggestions(self, code_suggestions: list):
|
||||||
relevant_file: str,
|
for suggestion in code_suggestions:
|
||||||
relevant_lines_start: int,
|
body = suggestion['body']
|
||||||
relevant_lines_end: int):
|
relevant_file = suggestion['relevant_file']
|
||||||
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
relevant_lines_start = suggestion['relevant_lines_start']
|
||||||
target_file = None
|
relevant_lines_end = suggestion['relevant_lines_end']
|
||||||
for file in self.diff_files:
|
|
||||||
if file.filename == relevant_file:
|
|
||||||
if file.filename == relevant_file:
|
|
||||||
target_file = file
|
|
||||||
break
|
|
||||||
range = relevant_lines_end - relevant_lines_start + 1
|
|
||||||
body = body.replace('```suggestion', f'```suggestion:-0+{range}')
|
|
||||||
|
|
||||||
lines = target_file.head_file.splitlines()
|
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
||||||
relevant_line_in_file = lines[relevant_lines_start - 1]
|
target_file = None
|
||||||
edit_type, found, source_line_no, target_file, target_line_no = self.find_in_file(target_file,
|
for file in self.diff_files:
|
||||||
relevant_line_in_file)
|
if file.filename == relevant_file:
|
||||||
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
if file.filename == relevant_file:
|
||||||
target_file, target_line_no)
|
target_file = file
|
||||||
|
break
|
||||||
|
range = relevant_lines_end - relevant_lines_start + 1
|
||||||
|
body = body.replace('```suggestion', f'```suggestion:-0+{range}')
|
||||||
|
|
||||||
|
lines = target_file.head_file.splitlines()
|
||||||
|
relevant_line_in_file = lines[relevant_lines_start - 1]
|
||||||
|
edit_type, found, source_line_no, target_file, target_line_no = self.find_in_file(target_file,
|
||||||
|
relevant_line_in_file)
|
||||||
|
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
|
target_file, target_line_no)
|
||||||
|
|
||||||
def search_line(self, relevant_file, relevant_line_in_file):
|
def search_line(self, relevant_file, relevant_line_in_file):
|
||||||
target_file = None
|
target_file = None
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
commands_text = "> /review [-i]: Request a review of your Pull Request. For an incremental review, which only " \
|
commands_text = "> **/review [-i]**: Request a review of your Pull Request. For an incremental review, which only " \
|
||||||
"considers changes since the last review, include the '-i' option.\n" \
|
"considers changes since the last review, include the '-i' option.\n" \
|
||||||
"> /describe: Modify the PR title and description based on the contents of the PR.\n" \
|
"> **/describe**: Modify the PR title and description based on the contents of the PR.\n" \
|
||||||
"> /improve: Suggest improvements to the code in the PR. " \
|
"> **/improve**: Suggest improvements to the code in the PR. " \
|
||||||
"These will be provided as pull request comments, ready to commit.\n" \
|
"These will be provided as pull request comments, ready to commit.\n" \
|
||||||
"> /ask \\<QUESTION\\>: Pose a question about the PR.\n"
|
"> **/ask \\<QUESTION\\>**: Pose a question about the PR.\n"
|
||||||
|
|
||||||
|
|
||||||
def bot_help_text(user: str):
|
def bot_help_text(user: str):
|
||||||
|
@ -79,7 +79,6 @@ class PRCodeSuggestions:
|
|||||||
|
|
||||||
def _prepare_pr_code_suggestions(self) -> str:
|
def _prepare_pr_code_suggestions(self) -> str:
|
||||||
review = self.prediction.strip()
|
review = self.prediction.strip()
|
||||||
data = None
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(review)
|
data = json.loads(review)
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
@ -89,6 +88,7 @@ class PRCodeSuggestions:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def push_inline_code_suggestions(self, data):
|
def push_inline_code_suggestions(self, data):
|
||||||
|
code_suggestions = []
|
||||||
for d in data['Code suggestions']:
|
for d in data['Code suggestions']:
|
||||||
if settings.config.verbosity_level >= 2:
|
if settings.config.verbosity_level >= 2:
|
||||||
logging.info(f"suggestion: {d}")
|
logging.info(f"suggestion: {d}")
|
||||||
@ -100,27 +100,33 @@ class PRCodeSuggestions:
|
|||||||
new_code_snippet = d['improved code']
|
new_code_snippet = d['improved code']
|
||||||
|
|
||||||
if new_code_snippet:
|
if new_code_snippet:
|
||||||
try: # dedent code snippet
|
new_code_snippet = self.dedent_code(relevant_file, relevant_lines_start, new_code_snippet)
|
||||||
self.diff_files = self.git_provider.diff_files if self.git_provider.diff_files \
|
|
||||||
else self.git_provider.get_diff_files()
|
|
||||||
original_initial_line = None
|
|
||||||
for file in self.diff_files:
|
|
||||||
if file.filename.strip() == relevant_file:
|
|
||||||
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
|
|
||||||
break
|
|
||||||
if original_initial_line:
|
|
||||||
suggested_initial_line = new_code_snippet.splitlines()[0]
|
|
||||||
original_initial_spaces = len(original_initial_line) - len(original_initial_line.lstrip())
|
|
||||||
suggested_initial_spaces = len(suggested_initial_line) - len(suggested_initial_line.lstrip())
|
|
||||||
delta_spaces = original_initial_spaces - suggested_initial_spaces
|
|
||||||
if delta_spaces > 0:
|
|
||||||
new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n')
|
|
||||||
except Exception as e:
|
|
||||||
if settings.config.verbosity_level >= 2:
|
|
||||||
logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}")
|
|
||||||
|
|
||||||
body = f"**Suggestion:** {content}\n```suggestion\n" + new_code_snippet + "\n```"
|
body = f"**Suggestion:** {content}\n```suggestion\n" + new_code_snippet + "\n```"
|
||||||
self.git_provider.publish_code_suggestion(body=body,
|
code_suggestions.append({'body': body,'relevant_file': relevant_file,
|
||||||
relevant_file=relevant_file,
|
'relevant_lines_start': relevant_lines_start,
|
||||||
relevant_lines_start=relevant_lines_start,
|
'relevant_lines_end': relevant_lines_end})
|
||||||
relevant_lines_end=relevant_lines_end)
|
|
||||||
|
self.git_provider.publish_code_suggestions(code_suggestions)
|
||||||
|
|
||||||
|
def dedent_code(self, relevant_file, relevant_lines_start, new_code_snippet):
|
||||||
|
try: # dedent code snippet
|
||||||
|
self.diff_files = self.git_provider.diff_files if self.git_provider.diff_files \
|
||||||
|
else self.git_provider.get_diff_files()
|
||||||
|
original_initial_line = None
|
||||||
|
for file in self.diff_files:
|
||||||
|
if file.filename.strip() == relevant_file:
|
||||||
|
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
|
||||||
|
break
|
||||||
|
if original_initial_line:
|
||||||
|
suggested_initial_line = new_code_snippet.splitlines()[0]
|
||||||
|
original_initial_spaces = len(original_initial_line) - len(original_initial_line.lstrip())
|
||||||
|
suggested_initial_spaces = len(suggested_initial_line) - len(suggested_initial_line.lstrip())
|
||||||
|
delta_spaces = original_initial_spaces - suggested_initial_spaces
|
||||||
|
if delta_spaces > 0:
|
||||||
|
new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n')
|
||||||
|
except Exception as e:
|
||||||
|
if settings.config.verbosity_level >= 2:
|
||||||
|
logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}")
|
||||||
|
|
||||||
|
return new_code_snippet
|
||||||
|
Reference in New Issue
Block a user