Skip to content

Commit 1471dce

Browse files
committed
fix(#32): relative line number for GitHub was incorrect
1 parent c529079 commit 1471dce

File tree

4 files changed

+149
-7
lines changed

4 files changed

+149
-7
lines changed

src/lgtm_ai/git_parser/parser.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ def parse_diff_patch(metadata: DiffFileMetadata, diff_text: str) -> DiffResult:
3333

3434
old_line_num = 0
3535
new_line_num = 0
36-
rel_position = 1
36+
rel_position = -1 # We just don't count the first hunk, but we do count the rest
3737

3838
try:
3939
for line in lines:
4040
hunk_match = re.match(r"^@@ -(\d+),?\d* \+(\d+),?\d* @@", line)
41+
rel_position += 1
4142
if hunk_match:
4243
old_line_num = int(hunk_match.group(1))
4344
new_line_num = int(hunk_match.group(2))
44-
rel_position = 1 # Reset position for each hunk
4545
continue
4646

4747
if line.startswith("+") and not line.startswith("+++"):
@@ -69,7 +69,6 @@ def parse_diff_patch(metadata: DiffFileMetadata, diff_text: str) -> DiffResult:
6969
else:
7070
old_line_num += 1
7171
new_line_num += 1
72-
rel_position += 1
7372
except (ValueError, TypeError, KeyError) as err:
7473
raise GitDiffParseError("Failed to parse diff patch") from err
7574

tests/git_client/test_github.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ def test_get_diff_from_url_successful() -> None:
131131
ModifiedLine(
132132
line=' {{ run }} ruff check {{ target_dirs }} {{ if report == "true" { "--format gitlab > tests/gl-code-quality-report.json" } else { "" } }}',
133133
line_number=48,
134-
relative_line_number=1,
134+
relative_line_number=5,
135135
modification_type="removed",
136136
),
137137
ModifiedLine(
138138
line=' {{ run }} ruff check {{ target_dirs }} {{ if report == "true" { "--output-format gitlab > tests/gl-code-quality-report.json" } else { "" } }}',
139139
line_number=48,
140-
relative_line_number=2,
140+
relative_line_number=6,
141141
modification_type="added",
142142
),
143143
],
@@ -154,13 +154,13 @@ def test_get_diff_from_url_successful() -> None:
154154
ModifiedLine(
155155
line="[tool.ruff.per-file-ignores]",
156156
line_number=78,
157-
relative_line_number=1,
157+
relative_line_number=5,
158158
modification_type="removed",
159159
),
160160
ModifiedLine(
161161
line="[tool.ruff.lint.per-file-ignores]",
162162
line_number=78,
163-
relative_line_number=2,
163+
relative_line_number=6,
164164
modification_type="added",
165165
),
166166
],

tests/git_parser/fixtures.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,128 @@
5757
ModifiedLine(line=" total += price", line_number=12, relative_line_number=5, modification_type="added"),
5858
],
5959
)
60+
61+
COMPLEX_DIFF_TEXT = '''
62+
'@@ -2,7 +2,7 @@
63+
import logging
64+
from collections.abc import Callable
65+
from importlib.metadata import version
66+
-from typing import get_args
67+
+from typing import Any, assert_never, get_args
68+
69+
import click
70+
import rich
71+
@@ -13,10 +13,12 @@
72+
get_summarizing_agent_with_settings,
73+
)
74+
from lgtm_ai.ai.schemas import AgentSettings, CommentCategory, SupportedAIModels, SupportedAIModelsList
75+
-from lgtm_ai.base.schemas import PRUrl
76+
+from lgtm_ai.base.schemas import OutputFormat, PRUrl
77+
from lgtm_ai.config.handler import ConfigHandler, PartialConfig
78+
+from lgtm_ai.formatters.base import Formatter
79+
+from lgtm_ai.formatters.json import JsonFormatter
80+
from lgtm_ai.formatters.markdown import MarkDownFormatter
81+
-from lgtm_ai.formatters.terminal import TerminalFormatter
82+
+from lgtm_ai.formatters.pretty import PrettyFormatter
83+
from lgtm_ai.git_client.utils import get_git_client
84+
from lgtm_ai.review import CodeReviewer
85+
from lgtm_ai.review.guide import ReviewGuideGenerator
86+
@@ -25,14 +27,15 @@
87+
parse_pr_url,
88+
validate_model_url,
89+
)
90+
+from rich.console import Console
91+
from rich.logging import RichHandler
92+
93+
__version__ = version("lgtm-ai")
94+
95+
logging.basicConfig(
96+
format="%(message)s",
97+
datefmt="[%X]",
98+
- handlers=[RichHandler(rich_tracebacks=True, show_path=False)],
99+
+ handlers=[RichHandler(rich_tracebacks=True, show_path=False, console=Console(stderr=True))],
100+
)
101+
logger = logging.getLogger("lgtm")
102+
103+
@@ -68,6 +71,7 @@ def _common_options[**P, T](func: Callable[P, T]) -> Callable[P, T]:
104+
help="Exclude files from the review. If not provided, all files in the PR will be reviewed. Uses UNIX-style wildcards.",
105+
)
106+
@click.option("--publish", is_flag=True, help="Publish the review or guide to the git service.")
107+
+ @click.option("--output-format", type=click.Choice([format.value for format in OutputFormat]))
108+
@click.option("--silent", is_flag=True, help="Do not print the review or guide to the console.")
109+
@click.option(
110+
"--ai-retries",
111+
@@ -104,6 +108,7 @@ def review(
112+
config: str | None,
113+
exclude: tuple[str, ...],
114+
publish: bool,
115+
+ output_format: OutputFormat | None,
116+
silent: bool,
117+
ai_retries: int | None,
118+
verbose: int,
119+
@@ -125,6 +130,7 @@ def review(
120+
model=model,
121+
model_url=model_url,
122+
publish=publish,
123+
+ output_format=output_format,
124+
silent=silent,
125+
ai_retries=ai_retries,
126+
),
127+
@@ -146,10 +152,10 @@ def review(
128+
129+
if not resolved_config.silent:
130+
logger.info("Printing review to console")
131+
- terminal_formatter = TerminalFormatter()
132+
- rich.print(terminal_formatter.format_review_summary_section(review))
133+
+ formatter, printer = _get_formatter_and_printer(resolved_config.output_format)
134+
+ printer(formatter.format_review_summary_section(review))
135+
if review.review_response.comments:
136+
- rich.print(terminal_formatter.format_review_comments_section(review.review_response.comments))
137+
+ printer(formatter.format_review_comments_section(review.review_response.comments))
138+
139+
if resolved_config.publish:
140+
logger.info("Publishing review to git service")
141+
@@ -168,6 +174,7 @@ def guide(
142+
config: str | None,
143+
exclude: tuple[str, ...],
144+
publish: bool,
145+
+ output_format: OutputFormat | None,
146+
silent: bool,
147+
ai_retries: int | None,
148+
verbose: int,
149+
@@ -184,6 +191,7 @@ def guide(
150+
ai_api_key=ai_api_key,
151+
model=model,
152+
publish=publish,
153+
+ output_format=output_format,
154+
silent=silent,
155+
ai_retries=ai_retries,
156+
),
157+
@@ -203,8 +211,8 @@ def guide(
158+
159+
if not resolved_config.silent:
160+
logger.info("Printing review to console")
161+
- terminal_formatter = TerminalFormatter()
162+
- rich.print(terminal_formatter.format_guide(guide))
163+
+ formatter, printer = _get_formatter_and_printer(resolved_config.output_format)
164+
+ printer(formatter.format_guide(guide))
165+
166+
if resolved_config.publish:
167+
logger.info("Publishing review guide to git service")
168+
@@ -220,3 +228,15 @@ def _set_logging_level(logger: logging.Logger, verbose: int) -> None:
169+
else:
170+
logger.setLevel(logging.DEBUG)
171+
logger.info("Logging level set to %s", logging.getLevelName(logger.level))
172+
+
173+
+
174+
+def _get_formatter_and_printer(output_format: OutputFormat) -> tuple[Formatter[Any], Callable[[Any], None]]:
175+
+ """Get the formatter and the print method based on the output format."""
176+
+ if output_format == OutputFormat.pretty:
177+
+ return PrettyFormatter(), rich.print
178+
+ elif output_format == OutputFormat.markdown:
179+
+ return MarkDownFormatter(), print
180+
+ elif output_format == OutputFormat.json:
181+
+ return JsonFormatter(), print
182+
+ else:
183+
+ assert_never(output_format)'
184+
'''

tests/git_parser/test_parser.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from lgtm_ai.git_parser.parser import DiffResult, parse_diff_patch
33
from tests.git_parser.fixtures import (
4+
COMPLEX_DIFF_TEXT,
45
DUMMY_METADATA,
56
PARSED_REFACTOR_DIFF,
67
PARSED_SIMPLE_DIFF,
@@ -18,3 +19,20 @@
1819
)
1920
def test_parse_diff_patch(input_diff: str, expected: DiffResult) -> None:
2021
assert parse_diff_patch(DUMMY_METADATA, input_diff) == expected
22+
23+
24+
@pytest.mark.parametrize(
25+
("content", "expected_line"),
26+
[
27+
("from typing import get_args", 4),
28+
("printer(formatter.format_review_comments_section(review.review_response.comments))", 75),
29+
("assert_never(output_format)", 121),
30+
],
31+
)
32+
def test_parse_diff_relative_line_multiple_hunks(content: str, expected_line: int) -> None:
33+
parsed = parse_diff_patch(DUMMY_METADATA, COMPLEX_DIFF_TEXT)
34+
35+
parsed_line = next(parsed_line for parsed_line in parsed.modified_lines if content in parsed_line.line)
36+
assert parsed_line.relative_line_number == expected_line, (
37+
f"Expected line number {expected_line}, got {parsed_line.line_number}"
38+
)

0 commit comments

Comments
 (0)