Skip to content

Commit

Permalink
Handle requirements.txt files without trailing newlines
Browse files Browse the repository at this point in the history
  • Loading branch information
drdavella committed Nov 17, 2023
1 parent d0414dd commit cded591
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 24 deletions.
19 changes: 7 additions & 12 deletions src/codemodder/codemodder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from concurrent.futures import ThreadPoolExecutor
import datetime
import difflib
import itertools
import logging
import os
Expand All @@ -17,6 +16,7 @@
from codemodder.change import ChangeSet
from codemodder.code_directory import file_line_patterns, match_files
from codemodder.context import CodemodExecutionContext
from codemodder.diff import create_diff as create_diff_from_lines
from codemodder.executor import CodemodExecutorWrapper
from codemodder.project_analysis.python_repo_manager import PythonRepoManager
from codemodder.report.codetf_reporter import report_default
Expand Down Expand Up @@ -49,18 +49,13 @@ def find_semgrep_results(


def create_diff(original_tree: cst.Module, new_tree: cst.Module) -> str:
diff_lines = list(
difflib.unified_diff(
original_tree.code.splitlines(keepends=True),
new_tree.code.splitlines(keepends=True),
)
"""
Create a diff between the original and output trees.
"""
return create_diff_from_lines(
original_tree.code.splitlines(keepends=True),
new_tree.code.splitlines(keepends=True),
)
# All but the last diff line should end with a newline
# The last diff line should be preserved as-is (with or without a newline)
diff_lines = [
line if line.endswith("\n") else line + "\n" for line in diff_lines[:-1]
] + [diff_lines[-1]]
return "".join(diff_lines)


def apply_codemod_to_file(
Expand Down
20 changes: 11 additions & 9 deletions src/codemodder/dependency_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from pathlib import Path
from typing import Optional

import difflib
from packaging.requirements import Requirement

from codemodder.change import Action, Change, ChangeSet, PackageAction, Result
from codemodder.diff import create_diff
from codemodder.dependency import Dependency


Expand Down Expand Up @@ -38,13 +38,18 @@ def write(self, dry_run: bool = False) -> Optional[ChangeSet]:
if not (self.dependency_file and self._new_requirements):
return None

updated = self._lines + self.new_requirements + ["\n"]
original_lines = self._lines.copy()
if not original_lines[-1].endswith("\n"):
original_lines[-1] += "\n"

diff = "".join(difflib.unified_diff(self._lines, updated))
requirement_lines = [f"{req}\n" for req in self.new_requirements]

updated = original_lines + requirement_lines
diff = create_diff(self._lines, updated)

changes = [
Change(
lineNumber=len(self._lines) + i + 1,
lineNumber=len(original_lines) + i + 1,
description=dep.build_description(),
properties={"contextual_description": True},
packageActions=[
Expand All @@ -56,11 +61,8 @@ def write(self, dry_run: bool = False) -> Optional[ChangeSet]:

if not dry_run:
with open(self.dependency_file, "w", encoding="utf-8") as f:
f.writelines(self._lines)
if not self._lines[-1].endswith("\n"):
f.write("\n")

f.writelines([f"{line}\n" for line in self.new_requirements])
f.writelines(original_lines)
f.writelines(requirement_lines)

self.dependency_file_changed = True
return ChangeSet(
Expand Down
12 changes: 12 additions & 0 deletions src/codemodder/diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import difflib


def create_diff(original_lines: list[str], new_lines: list[str]) -> str:
diff_lines = list(difflib.unified_diff(original_lines, new_lines))

# All but the last diff line should end with a newline
# The last diff line should be preserved as-is (with or without a newline)
diff_lines = [
line if line.endswith("\n") else line + "\n" for line in diff_lines[:-1]
] + [diff_lines[-1]]
return "".join(diff_lines)
17 changes: 14 additions & 3 deletions tests/test_dependency_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ def test_add_dependency_preserve_comments(self, tmpdir, dry_run):
assert changeset.diff == (
"--- \n"
"+++ \n"
"@@ -1,3 +1,5 @@\n"
"@@ -1,3 +1,4 @@\n"
" # comment\n"
" \n"
" requests\n"
"+defusedxml~=0.7.1+\n"
"+defusedxml~=0.7.1\n"
)
assert len(changeset.changes) == 1
assert changeset.changes[0].lineNumber == 4
Expand Down Expand Up @@ -96,7 +96,18 @@ def test_dependency_file_no_terminating_newline(self, tmpdir):

dm = DependencyManager(Path(tmpdir))
dm.add([Security])
dm.write()
changeset = dm.write()

assert changeset is not None
assert changeset.diff == (
"--- \n"
"+++ \n"
"@@ -1,2 +1,3 @@\n"
" requests\n"
"-whatever\n"
"+whatever\n"
"+security~=1.2.0\n"
)

assert dependency_file.read_text(encoding="utf-8") == (
"requests\nwhatever\nsecurity~=1.2.0\n"
Expand Down

0 comments on commit cded591

Please sign in to comment.