Skip to content

Commit

Permalink
Add support for current package to codemod context.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jennifer Taylor authored and DragonMinded committed Feb 7, 2020
1 parent f6a1c77 commit c21ae59
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 2 deletions.
42 changes: 40 additions & 2 deletions libcst/codemod/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,35 @@ def exec_transform_with_prettyprint(
return code


def _calculate_module(repo_root: Optional[str], filename: str) -> Optional[str]:
# Given an absolute repo_root and an absolute filename, calculate the
# python module name for the file.
if repo_root is None:
# We don't have a repo root, so this is impossible to calculate.
return None

# Make sure the absolute path for the root ends in a separator.
if repo_root[-1] != os.path.sep:
repo_root = repo_root + os.path.sep

if not filename.startswith(repo_root):
# This file seems to be out of the repo root.
return None

# Get the relative path, get rid of any special cases and extensions.
relative_filename = filename[len(repo_root) :]
for ending in [
f"{os.path.sep}__init__.py",
f"{os.path.sep}__main__.py",
".py",
]:
if relative_filename.endswith(ending):
relative_filename = relative_filename[: -len(ending)]

# Now, convert all line separators to dots to represent the python module.
return relative_filename.replace(os.path.sep, ".")


@dataclass(frozen=True)
class ParallelExecResult:
# File we have results for
Expand All @@ -197,6 +226,7 @@ def _parallel_exec_process_stub( # noqa: C901
result_queue: "Queue[ParallelExecResult]",
transformer: Codemod,
filename: str,
repo_root: Optional[str],
unified_diff: Optional[int],
include_generated: bool,
generated_code_marker: str,
Expand Down Expand Up @@ -241,7 +271,11 @@ def _parallel_exec_process_stub( # noqa: C901
# We do this after the fork so that a context that was initialized with
# some defaults before calling parallel_exec_transform_with_prettyprint
# will be updated per-file.
transformer.context = replace(transformer.context, filename=filename)
transformer.context = replace(
transformer.context,
filename=filename,
full_module_name=_calculate_module(repo_root, filename),
)

# Run the transform, bail if we failed or if we aren't formatting code
try:
Expand Down Expand Up @@ -562,11 +596,13 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
return ParallelTransformResult(successes=0, failures=0, skips=0, warnings=0)

if repo_root:
# Make sure if there is a root that we have the absolute path to it.
repo_root = os.path.abspath(repo_root)
# Spin up a full repo metadata manager so that we can provide metadata
# like type inference to individual forked processes.
print("Calculating full-repo metadata...", file=sys.stderr)
metadata_manager = FullRepoManager(
os.path.abspath(repo_root), files, transform.get_inherited_dependencies(),
repo_root, files, transform.get_inherited_dependencies(),
)
metadata_manager.resolve_cache()
transform.context = replace(
Expand All @@ -586,6 +622,7 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
queue,
transform,
files[0],
repo_root,
unified_diff=unified_diff,
include_generated=include_generated,
generated_code_marker=generated_code_marker,
Expand Down Expand Up @@ -653,6 +690,7 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
queue,
transform,
f,
repo_root,
unified_diff,
include_generated,
generated_code_marker,
Expand Down
6 changes: 6 additions & 0 deletions libcst/codemod/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class CodemodContext:
#: running codemods from the command line.
filename: Optional[str] = None

#: The current module if a codemod is being executed against a file that
#: lives on disk, and the repository root is correctly configured. This
#: Will take the form of a dotted name such as ``foo.bar.baz`` for a file
#: in the repo named ``foo/bar/baz.py``.
full_module_name: Optional[str] = None

#: The current top level metadata wrapper for the module being modified.
#: To access computed metadata when inside an actively running codemod, use
#: the :meth:`~libcst.MetadataDependent.get_metadata` method on
Expand Down
46 changes: 46 additions & 0 deletions libcst/codemod/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#
# pyre-strict
from typing import Optional

from libcst.codemod._cli import _calculate_module
from libcst.testing.utils import UnitTest, data_provider


class TestPackageCalculation(UnitTest):
@data_provider(
(
# Providing no root should give back no module.
(None, "/some/dummy/file.py", None),
# Providing a file outside the root should give back no module.
("/home/username/root", "/some/dummy/file.py", None),
("/home/username/root/", "/some/dummy/file.py", None),
("/home/username/root", "/home/username/file.py", None),
# Various files inside the root should give back valid modules.
("/home/username/root", "/home/username/root/file.py", "file"),
("/home/username/root/", "/home/username/root/file.py", "file"),
(
"/home/username/root/",
"/home/username/root/some/dir/file.py",
"some.dir.file",
),
# Various special files inside the root should give back valid modules.
(
"/home/username/root/",
"/home/username/root/some/dir/__init__.py",
"some.dir",
),
(
"/home/username/root/",
"/home/username/root/some/dir/__main__.py",
"some.dir",
),
),
)
def test_calculate_module(
self, repo_root: Optional[str], filename: str, module: str
) -> None:
self.assertEqual(_calculate_module(repo_root, filename), module)

0 comments on commit c21ae59

Please sign in to comment.