Skip to content

Commit

Permalink
Implement --describe flag for codemodder
Browse files Browse the repository at this point in the history
  • Loading branch information
drdavella committed Dec 1, 2023
1 parent 1a9941c commit c058932
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 4 deletions.
37 changes: 33 additions & 4 deletions src/codemodder/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import argparse
import json
import sys

from codemodder import __version__
from codemodder.code_directory import DEFAULT_INCLUDED_PATHS, DEFAULT_EXCLUDED_PATHS
from codemodder.registry import CodemodRegistry
from codemodder.logging import OutputFormat, logger


Expand All @@ -15,7 +17,7 @@ def error(self, message):
sys.exit(3)


def build_list_action(codemod_registry):
def build_list_action(codemod_registry: CodemodRegistry):
class ListAction(argparse.Action):
""" """

Expand All @@ -39,6 +41,27 @@ def __call__(self, parser, *args, **kwargs):
return ListAction


def build_describe_action(codemod_registry: CodemodRegistry):
class DescribeAction(argparse.Action):
def _print_codemods(self, args: argparse.Namespace):
# TODO: this doesn't currently honor the include/exclude args
# This is because of the way arguments are parsed: at the time this
# action is called, the codemod arguments haven't necessarily been
# parsed yet. Making this work will require a fairly significant
# refactor of the argument parsing.
results = codemod_registry.describe_codemods(
args.codemod_include, args.codemod_exclude
)
print(json.dumps({"results": results}, indent=2))

def __call__(self, parser, *args, **kwargs):
parsed_args: argparse.Namespace = args[0]
self._print_codemods(parsed_args)
parser.exit()

return DescribeAction


class CsvListAction(argparse.Action):
"""
argparse Action to convert "a,b,c" into ["a", "b", "c"]
Expand All @@ -54,7 +77,7 @@ def validate_items(self, items):
"""Basic Action does not validate the items"""


def build_codemod_validator(codemod_registry):
def build_codemod_validator(codemod_registry: CodemodRegistry):
names = codemod_registry.names
ids = codemod_registry.ids

Expand All @@ -81,7 +104,7 @@ def validate_items(self, items):
return ValidatedCodmods


def parse_args(argv, codemod_registry):
def parse_args(argv, codemod_registry: CodemodRegistry):
"""
Parse CLI arguments according to:
https://www.notion.so/pixee/Codemodder-CLI-Arguments
Expand Down Expand Up @@ -116,7 +139,13 @@ def parse_args(argv, codemod_registry):
"--list",
action=build_list_action(codemod_registry),
nargs=0,
help="Print codemod(s) metadata",
help="Print codemod names to stdout and exit",
)
parser.add_argument(
"--describe",
action=build_describe_action(codemod_registry),
nargs=0,
help="Print detailed codemod metadata to stdout exit",
)
parser.add_argument(
"--output-format",
Expand Down
8 changes: 8 additions & 0 deletions src/codemodder/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ def yaml_files(self):
for yaml_file in getattr(self, "YAML_FILES", [])
]

def describe(self):
return {
"codemod": self.id,
"summary": self.summary,
"description": self.description,
"references": self.references,
}

def __repr__(self):
return "<{} at 0x{:x} for {}.{}>".format(
type(self).__name__,
Expand Down
8 changes: 8 additions & 0 deletions src/codemodder/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ def match_codemods(
for name in codemod_include
]

def describe_codemods(
self,
codemod_include: Optional[list] = None,
codemod_exclude: Optional[list] = None,
) -> list[dict]:
codemods = self.match_codemods(codemod_include, codemod_exclude)
return [codemod.describe() for codemod in codemods]


def load_registered_codemods() -> CodemodRegistry:
registry = CodemodRegistry()
Expand Down
16 changes: 16 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

import mock
import pytest

Expand Down Expand Up @@ -88,6 +90,20 @@ def test_list_prints_codemod_metadata(self, mock_print, cli_args):

assert err.value.args[0] == 0

@mock.patch("builtins.print")
def test_describe_prints_codemod_metadata(self, mock_print):
with pytest.raises(SystemExit) as err:
parse_args(
["--describe"],
self.registry,
)

assert err.value.args[0] == 0
assert mock_print.call_count == 1

results = json.loads(mock_print.call_args_list[0][0][0])
assert len(results["results"]) == len(self.registry.codemods)

@mock.patch("codemodder.cli.logger.error")
def test_bad_output_format(self, error_logger):
with pytest.raises(SystemExit) as err:
Expand Down

0 comments on commit c058932

Please sign in to comment.