Skip to content

Commit 6808b55

Browse files
authored
Enable per-codemod configuration of included files (#480)
1 parent a70cdcf commit 6808b55

File tree

7 files changed

+86
-5
lines changed

7 files changed

+86
-5
lines changed

src/codemodder/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55
from codemodder import __version__
6-
from codemodder.code_directory import DEFAULT_EXCLUDED_PATHS, DEFAULT_INCLUDED_PATHS
6+
from codemodder.code_directory import DEFAULT_EXCLUDED_PATHS
77
from codemodder.logging import OutputFormat, logger
88
from codemodder.registry import CodemodRegistry
99

@@ -149,7 +149,7 @@ def parse_args(argv, codemod_registry: CodemodRegistry):
149149
parser.add_argument(
150150
"--path-include",
151151
action=CsvListAction,
152-
default=DEFAULT_INCLUDED_PATHS,
152+
default=[],
153153
help="Comma-separated set of UNIX glob patterns to include",
154154
)
155155
parser.add_argument(

src/codemodder/codemodder.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,17 @@ def run(original_args) -> int:
186186
sast_only=argv.sonar_issues_json or argv.sarif,
187187
)
188188

189+
included_paths = argv.path_include or codemod_registry.default_include_paths
190+
189191
log_section("setup")
190192
log_list(logging.INFO, "running", codemods_to_run, predicate=lambda c: c.id)
191-
log_list(logging.INFO, "including paths", argv.path_include)
193+
log_list(logging.INFO, "including paths", included_paths)
192194
log_list(logging.INFO, "excluding paths", argv.path_exclude)
193195

194196
files_to_analyze: list[Path] = match_files(
195-
context.directory, argv.path_exclude, argv.path_include
197+
context.directory,
198+
argv.path_exclude,
199+
included_paths,
196200
)
197201

198202
full_names = [str(path) for path in files_to_analyze]

src/codemodder/codemods/base_codemod.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,21 @@ class BaseCodemod(metaclass=ABCMeta):
6767
_metadata: Metadata
6868
detector: BaseDetector | None
6969
transformer: BaseTransformerPipeline
70+
default_extensions: list[str] | None
7071

7172
def __init__(
7273
self,
7374
*,
7475
metadata: Metadata,
7576
detector: BaseDetector | None = None,
7677
transformer: BaseTransformerPipeline,
78+
default_extensions: list[str] | None = None,
7779
):
7880
# Metadata should only be accessed via properties
7981
self._metadata = metadata
8082
self.detector = detector
8183
self.transformer = transformer
84+
self.default_extensions = default_extensions or [".py"]
8285

8386
@property
8487
@abstractmethod
@@ -154,6 +157,16 @@ def _apply(
154157
else None
155158
)
156159

160+
files_to_analyze = (
161+
[
162+
path
163+
for path in files_to_analyze
164+
if path.suffix in self.default_extensions
165+
]
166+
if self.default_extensions
167+
else files_to_analyze
168+
)
169+
157170
process_file = functools.partial(
158171
self._process_file, context=context, results=results, rules=rules
159172
)

src/codemodder/registry.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from __future__ import annotations
22

3+
import os
34
import re
45
from dataclasses import dataclass
56
from importlib.metadata import entry_points
7+
from itertools import chain
68
from typing import TYPE_CHECKING, Optional
79

810
from codemodder.logging import logger
@@ -31,10 +33,12 @@ class CodemodCollection:
3133
class CodemodRegistry:
3234
_codemods_by_name: dict[str, BaseCodemod]
3335
_codemods_by_id: dict[str, BaseCodemod]
36+
_default_include_paths: set[str]
3437

3538
def __init__(self):
3639
self._codemods_by_name = {}
3740
self._codemods_by_id = {}
41+
self._default_include_paths = set()
3842

3943
@property
4044
def names(self):
@@ -48,11 +52,23 @@ def ids(self):
4852
def codemods(self):
4953
return list(self._codemods_by_name.values())
5054

55+
@property
56+
def default_include_paths(self) -> list[str]:
57+
return list(self._default_include_paths)
58+
5159
def add_codemod_collection(self, collection: CodemodCollection):
5260
for codemod in collection.codemods:
5361
wrapper = codemod() if isinstance(codemod, type) else codemod
5462
self._codemods_by_name[wrapper.name] = wrapper
5563
self._codemods_by_id[wrapper.id] = wrapper
64+
self._default_include_paths.update(
65+
chain(
66+
*[
67+
(f"*{ext}", os.path.join("**", f"*{ext}"))
68+
for ext in wrapper.default_extensions
69+
]
70+
)
71+
)
5672

5773
def match_codemods(
5874
self,

src/core_codemods/api/core_codemod.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ def __init__(
3434
metadata: Metadata,
3535
detector: BaseDetector | None = None,
3636
transformer: BaseTransformerPipeline,
37+
default_extensions: list[str] | None = None,
3738
requested_rules: list[str] | None = None,
3839
):
39-
super().__init__(metadata=metadata, detector=detector, transformer=transformer)
40+
super().__init__(
41+
metadata=metadata,
42+
detector=detector,
43+
transformer=transformer,
44+
default_extensions=default_extensions,
45+
)
4046
self.requested_rules = [self.name]
4147
if requested_rules:
4248
self.requested_rules.extend(requested_rules)

tests/codemods/test_base_codemod.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
from pathlib import Path
2+
13
import libcst as cst
24
import mock
5+
import pytest
36
from libcst.codemod import CodemodContext
47

58
from codemodder.codemods.api import Metadata, ReviewGuidance, SimpleCodemod
9+
from core_codemods.api import CoreCodemod
610

711

812
class DoNothingCodemod(SimpleCodemod):
@@ -32,3 +36,21 @@ def run_and_assert(self, input_code, expected_output):
3236
def test_empty_results(self):
3337
input_code = """print('Hello World')"""
3438
self.run_and_assert(input_code, input_code)
39+
40+
41+
@pytest.mark.parametrize("ext,call_count", [(".py", 2), (".txt", 1), (".js", 1)])
42+
def test_filter_apply_by_extension(mocker, ext, call_count):
43+
process_file = mocker.patch("core_codemods.api.CoreCodemod._process_file")
44+
45+
codemod = CoreCodemod(
46+
metadata=mocker.MagicMock(),
47+
transformer=mocker.MagicMock(),
48+
default_extensions=[ext],
49+
)
50+
51+
codemod.apply(
52+
mocker.MagicMock(),
53+
[Path("file.py"), Path("file.txt"), Path("file.js"), Path("file2.py")],
54+
)
55+
56+
assert process_file.call_count == call_count

tests/test_registry.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from codemodder.registry import CodemodCollection, CodemodRegistry
2+
3+
4+
def test_default_extensions(mocker):
5+
registry = CodemodRegistry()
6+
assert registry.default_include_paths == []
7+
8+
CodemodA = mocker.MagicMock(default_extensions=[".py"])
9+
CodemodB = mocker.MagicMock(default_extensions=[".py", ".txt"])
10+
11+
registry.add_codemod_collection(
12+
CodemodCollection(origin="origin", codemods=[CodemodA, CodemodB])
13+
)
14+
15+
assert sorted(registry.default_include_paths) == [
16+
"**/*.py",
17+
"**/*.txt",
18+
"*.py",
19+
"*.txt",
20+
]

0 commit comments

Comments
 (0)