feat(v3): add v3 migration codemod#334
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #334 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 32 33 +1
Lines 1548 1706 +158
Branches 194 225 +31
==========================================
+ Hits 1548 1706 +158 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
c233c9b to
18cf22f
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a LibCST-based codemod (MigrateCommand) to migrate Decoy v2 → v3 usages, per-package Ruff configs and pyproject edits, a comprehensive pytest codemod test suite (Python ≥3.10 gated), and a single token addition to Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI (uv)
participant Codemod as MigrateCommand
participant LibCST as LibCST AST
participant FS as File System
CLI->>Codemod: invoke transform on target files
Codemod->>FS: read source file
FS-->>Codemod: file contents
Codemod->>LibCST: parse to AST
LibCST-->>Codemod: AST nodes
Codemod->>LibCST: transform imports / when/verify / matchers / captors
LibCST-->>Codemod: transformed AST
Codemod->>FS: write updated file
FS-->>CLI: exit status
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
quibble: Walkthrough 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Review rate limit: 2/3 reviews remaining, refill in 20 minutes. Comment |
25d5c14 to
e0302b2
Compare
18cf22f to
c5326fd
Compare
7bb15a9 to
028d799
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@decoy/codemods/migrate.py`:
- Around line 417-437: The migration currently remaps matcher kwargs by position
(in the IsA and ErrorMatching branches) which swaps semantics when callers
passed keywords out of order; update the logic in the IsA and ErrorMatching
cases to locate original_args by their argument name (keyword) before
renaming—use or add a helper that searches original_args for an entry with
.keyword (or .arg name) equal to "type", "attrs", "match", etc., and fall back
to _rename_arg_at by index only if no matching keyword is found; specifically
change the IsA branch (original_method == "IsA") and the ErrorMatching branch
(original_method == "ErrorMatching") to use this name-based lookup when building
args instead of relying solely on positional _rename_arg_at calls so named
kwargs keep their intended mapping.
- Around line 121-129: The matcher for conditional-starred args can drop the
fallback branch because _extract_conditional_starred_arg() only emits the truthy
branch for patterns like m.BooleanOperation(... operator=m.Or(),
right=m.List()), so update _extract_conditional_starred_arg() to detect when the
BooleanOperation has a non-empty right (fallback) list and emit code that
preserves the fallback (e.g., produce an if/else or explicit extend of the
fallback list when the condition is falsy) instead of discarding it; locate the
handling of m.BooleanOperation and the code paths that return only the truthy
branch and add logic to generate the else branch using the
BooleanOperation.right AST node so the migrated output retains the original or
[...] fallback.
- Around line 241-252: The current migrate_matcher_arg always appends ".arg" to
the result of _migrate_matcher(updated_node), which incorrectly mutates
unknown/custom matchers; change migrate_matcher_arg to call
_migrate_matcher(updated_node) once, inspect its return and only wrap it in
cst.Attribute(..., attr=cst.Name("arg")) when the returned node is a migrated
matcher (i.e., not the original call/unchanged matchers.* call). Use identity or
a structural check on the returned value from _migrate_matcher to decide whether
to return it as-is or to return the cst.Attribute-wrapped version, keeping
references to migrate_matcher_arg and _migrate_matcher to locate the code.
In `@pyproject.toml`:
- Around line 33-34: The console script decoy-migrate points to
decoy.codemods.migrate:main but that callable doesn't exist; either add a
module-level main() in decoy.codemods.migrate that instantiates and runs
MigrateCommand, or change the pyproject.toml entry to reference an existing
callable (for example a factory/wrapper that invokes MigrateCommand). Locate the
MigrateCommand class in decoy.codemods.migrate and implement a small main() that
constructs MigrateCommand and calls its run/execute method, or update the
[project.scripts] value to the correct fully-qualified callable name that
actually exists.
- Around line 33-34: The decoy-migrate console script points to
decoy.codemods.migrate which imports libcst at module level, but libcst is only
in the dev dependency group; move the runtime dependency into the main project
dependencies by adding libcst>=1.0.1 to the [project] dependencies (so
decoy.codemods.migrate can import libcst when the package is installed and the
decoy-migrate CLI runs).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b823724c-201c-463b-bb04-95b902cc62a3
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
codebook.tomldecoy/codemods/__init__.pydecoy/codemods/migrate.pydecoy/codemods/pyproject.tomldecoy/next/pyproject.tomlpyproject.tomltests/codemods/test_migrate.py
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
decoy/codemods/migrate.py (1)
121-129:⚠️ Potential issue | 🟠 MajorPreserve the
or [...]fallback when expanding conditional spreads.
*(cond and [...] or [...])currently migrates only the truthy list. If the original spread has a non-empty fallback list, those verifications disappear from the migrated output.Also applies to: 368-398
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@decoy/codemods/migrate.py` around lines 121 - 129, The transformation that expands conditional spreads currently matches a nested m.BooleanOperation (left=m.BooleanOperation(... operator=m.And() ...) operator=m.Or() right=m.List()) but only emits the truthy branch; update the logic in migrate.py that handles m.BooleanOperation so that when you see the pattern (outer m.BooleanOperation with operator m.Or and inner left m.BooleanOperation with operator m.And and both right sides as m.List) you preserve and emit both the true-branch list and the fallback list (the outer right) instead of dropping the fallback; adjust the expansion routine that constructs the spread output to include the fallback list as the `or [...]` branch and apply this same fix in the other occurrence around the 368-398 block so both sites preserve non-empty fallback lists.pyproject.toml (1)
56-57:⚠️ Potential issue | 🟠 Major
libcststill looks like a runtime dependency, not just a dev one.
decoy/codemods/migrate.pyimportslibcstat module import time, but this adds it only underdev. If the codemod ships in the published package, importingdecoy.codemods.migratein a normal install will fail unlesslibcstis moved into[project.dependencies]or made an explicit optional extra.#!/bin/bash set -euo pipefail echo "== top-level libcst imports in the codemod ==" rg -n '^(import libcst as cst|from libcst )' decoy/codemods/migrate.py echo echo "== libcst declarations in pyproject.toml ==" rg -n '^\[project\]|^dependencies = \[|^\[dependency-groups\]|libcst' pyproject.tomlExpected result:
libcstshows up as a top-level import indecoy/codemods/migrate.py, but only under[dependency-groups].devinpyproject.toml.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pyproject.toml` around lines 56 - 57, decoy/codemods/migrate.py performs a top-level import of libcst but pyproject.toml only lists libcst under the dev dependency group, causing normal installs to fail; fix by either moving the libcst entry from the dev dependency group into [project.dependencies] in pyproject.toml so decoy.codemods.migrate can import it at runtime, or make it an optional extra (e.g., extras name "codemods" or "libcst") and update pyproject.toml accordingly and/or change decoy/codemods/migrate.py to perform a local/lazy import of libcst inside the function that uses it (rather than at module import time) and raise a clear error if the optional extra is not installed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@decoy/codemods/migrate.py`:
- Around line 134-140: The matcher currently only permits the keyword names
"times" and "ignore_extra_args" (in the m.Arg tuple) so calls to when(...) or
verify(...) that use is_entered= are skipped; update the m.Arg keyword tuple
(both the occurrence around m.ZeroOrMore(...) and the similar occurrence at
lines ~193-195) to include m.Name("is_entered") alongside m.Name("times") and
m.Name("ignore_extra_args") so is_entered= is recognized and preserved during
the migration; keep the rest of the matcher structure identical.
- Around line 105-114: The matcher is currently hard-coded to only match
receiver names "decoy", "self.decoy", and "self._decoy", so calls like
my_decoy.verify(...) or fixture.when(...) are missed; update the Attribute
matcher in migrate.py (the func=m.Attribute(...) node that currently uses
m.Name("decoy") / m.Name("self") / m.Name("_decoy") and attr=m.Name("verify"))
to accept any receiver Name or Attribute (i.e., remove the specific name checks
and allow a generic m.Name() or m.Attribute(...) as the value) while still
requiring attr=m.Name("verify"); apply the same generalization to the similar
matcher at the other occurrence (the block around lines referenced 181-190) so
all Decoy receiver names are matched.
---
Duplicate comments:
In `@decoy/codemods/migrate.py`:
- Around line 121-129: The transformation that expands conditional spreads
currently matches a nested m.BooleanOperation (left=m.BooleanOperation(...
operator=m.And() ...) operator=m.Or() right=m.List()) but only emits the truthy
branch; update the logic in migrate.py that handles m.BooleanOperation so that
when you see the pattern (outer m.BooleanOperation with operator m.Or and inner
left m.BooleanOperation with operator m.And and both right sides as m.List) you
preserve and emit both the true-branch list and the fallback list (the outer
right) instead of dropping the fallback; adjust the expansion routine that
constructs the spread output to include the fallback list as the `or [...]`
branch and apply this same fix in the other occurrence around the 368-398 block
so both sites preserve non-empty fallback lists.
In `@pyproject.toml`:
- Around line 56-57: decoy/codemods/migrate.py performs a top-level import of
libcst but pyproject.toml only lists libcst under the dev dependency group,
causing normal installs to fail; fix by either moving the libcst entry from the
dev dependency group into [project.dependencies] in pyproject.toml so
decoy.codemods.migrate can import it at runtime, or make it an optional extra
(e.g., extras name "codemods" or "libcst") and update pyproject.toml accordingly
and/or change decoy/codemods/migrate.py to perform a local/lazy import of libcst
inside the function that uses it (rather than at module import time) and raise a
clear error if the optional extra is not installed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a064aeb0-fe70-409f-af15-0f43074502f7
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
codebook.tomldecoy/codemods/__init__.pydecoy/codemods/migrate.pydecoy/codemods/pyproject.tomldecoy/next/pyproject.tomlpyproject.tomltests/codemods/test_migrate.py
✅ Files skipped from review due to trivial changes (4)
- codebook.toml
- decoy/next/pyproject.toml
- decoy/codemods/init.py
- decoy/codemods/pyproject.toml
4fa5cf7 to
0cbf918
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@decoy/codemods/migrate.py`:
- Around line 119-145: The multi-rehearsal matcher for verify_order currently
forces every positional rehearsal through _as_call(), which skips attribute-get
rehearsals like decoy.verify(mock.attr, ...); update the AtLeastN/ZeroOrMore
matcher to accept attribute expressions (m.Attribute) and/or the same
m.Arg(m.Call() | m.Await(m.Call()) | m.Arg(m.Attribute() ...)) pattern used in
the single-rehearsal path, and instead of assuming calls convert attributes via
the same normalization helper used for single rehearsals (reuse the _as_call
normalization logic), so attribute-get rehearsals are normalized to .get();
apply the same change to the other two matcher sites mentioned (the blocks
around the ranges that correspond to lines 163-170 and 412-415).
- Around line 286-290: track_captor_assignment() is adding every assignment in
the scope with the same identifier instead of only the assignment introduced by
the captor statement, causing later rebindings to be mis-marked; change the
update to add only the specific assignment node created by the current captor
statement (use the exact assignment object/AST node from the current
visit/handler rather than filtering scope.assignments by name). Apply the same
change to the analogous block around lines 309-312 so only the exact assignment
node (not all assignments with that name) is recorded in
self._captor_assignments, ensuring migrate_captor_arg() only treats the original
captor binding as a captor.
- Around line 335-349: The property-specific match only recognizes a bare decoy
receiver; update the m.matches pattern used around rehearsal (the m.Call ->
m.Attribute -> m.Call -> m.Attribute chain that currently checks m.Name("decoy")
and m.Name("prop")) so it also accepts m.Attribute receivers for self.decoy and
self._decoy (i.e., allow the inner value to be either m.Name("decoy") or an
m.Attribute whose value is m.Name("self") and attr is m.Name("decoy") or
m.Name("_decoy")); ensure the branch that preserves .set(...) / .delete() still
triggers for these receiver forms so that the later rewrite does not fall
through to the generic call path.
In `@docs/v3/migration.md`:
- Around line 15-18: The example command shown ("uv run --with libcst python -m
libcst.tool codemod decoy.codemods.migrate.MigrateCommand .") contradicts the
“run it against your test files” guidance; update the example in the migration
guide to either target the common test directory (replace the trailing "." with
"tests") or reword the sentence to say “run it against the relevant Python
paths” so the command and guidance match.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f6ab6b72-2aab-4e59-9cbd-77647844d436
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
codebook.tomldecoy/codemods/__init__.pydecoy/codemods/migrate.pydecoy/codemods/pyproject.tomldecoy/next/pyproject.tomldocs/v3/migration.mdpyproject.tomltests/codemods/test_migrate.py
✅ Files skipped from review due to trivial changes (3)
- codebook.toml
- decoy/codemods/init.py
- decoy/codemods/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- decoy/next/pyproject.toml
- pyproject.toml
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
decoy/codemods/migrate.py (3)
286-290:⚠️ Potential issue | 🟠 Major | ⚡ Quick winissue: Captor tracking adds all scope assignments with matching name, not just the captor assignment.
When
track_captor_assignmentfires, it queriesscope.assignmentsand filters by name. This returns ALL assignments to that name in the scope—including later rebindings likecaptor = SomethingElse(). The pipeline failure intest_migrate_captor_rebound_noopconfirms this: the secondmock("world", captor)incorrectly gets.argappended.Could the code track only the specific assignment node being visited rather than all same-named assignments?
Suggested approach
def track_captor_assignment(self, node: cst.Assign) -> None: """Find captor assignments to add `.arg`.""" scope = self._get_scope(node) - names = { - cst.ensure_type(target.target, cst.Name).value - for target in node.targets - if m.matches(target.target, m.Name()) - } - self._captor_assignments.update( - a - for a in scope.assignments # pyright: ignore[reportAttributeAccessIssue] - if a.name in names - ) + for target in node.targets: + if m.matches(target.target, m.Name()): + name = cst.ensure_type(target.target, cst.Name).value + for assignment in scope[name]: + if assignment.node is target: + self._captor_assignments.add(assignment)This matches only the assignment node from this specific captor statement.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@decoy/codemods/migrate.py` around lines 286 - 290, The current update to self._captor_assignments iterates scope.assignments and picks all assignments with matching names (via scope.assignments and a.name in names), which captures later rebindings; instead, add only the specific AST assignment node for this captor visit. Change the code in track_captor_assignment so it does not iterate scope.assignments but directly inserts the exact assignment node being visited (the local parameter representing the captor assignment) into self._captor_assignments, using the same set/update structure but with that single node instead of a comprehension over scope.assignments.
119-145:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winissue: The verify_order matcher excludes attribute-get rehearsals, causing pipeline failures.
The matcher at lines 120-136 only accepts
m.Arg(m.Call() | m.Await(m.Call()))for positional arguments. When a verify call contains an attribute rehearsal likemock.some_prop, the matcher doesn't fire and the code falls through incorrectly. This is confirmed by the pipeline error intest_verify_order_attribute_get.Could the matcher be extended to also accept
m.Attribute()in the positional arg patterns?args=[ m.AtLeastN( n=2, matcher=( - m.Arg(m.Call() | m.Await(m.Call())) + m.Arg(m.Call() | m.Attribute() | m.Await(m.Call() | m.Attribute())) | m.Arg( m.BooleanOperation(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@decoy/codemods/migrate.py` around lines 119 - 145, The positional-argument matcher inside the AtLeastN block (the m.Arg(...) branch) currently only accepts m.Call() or m.Await(m.Call()), which excludes attribute-get rehearsals like mock.some_prop; update that matcher to also accept m.Attribute() (and the awaited form m.Await(m.Attribute())) so attribute accesses are matched as valid positional args—i.e., in the AtLeastN args list replace/add alternatives in the m.Arg(...) clause to include m.Attribute() and m.Await(m.Attribute()) alongside m.Call()/m.Await(m.Call()).
163-171:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winissue:
_as_callwill raise ValueError for attribute-get rehearsals in verify_order.When processing positional args at line 167, the code calls
_as_call(arg.value)which expects aCallnode. For attribute-get rehearsals (mock.some_prop), the value is anAttribute, causing theValueError: Expected a Call but got a Attributepipeline failure.Would it make sense to handle the attribute case here similar to how
migrate_calldoes at lines 235-239?Suggested approach
verify_statements: list[cst.BaseStatement] = [ _extract_conditional_starred_arg(arg.value, attr, kwargs) if arg.star == "*" + else cst.SimpleStatementLine( + body=[cst.Expr(_migrate_attribute_rehearsal(attr, arg.value, kwargs))] + ) + if m.matches(arg.value, m.Attribute()) else cst.SimpleStatementLine( body=[cst.Expr(_migrate_rehearsal(attr, _as_call(arg.value), kwargs))] ) for arg in call.args if not arg.keyword ]This would require a helper like
_migrate_attribute_rehearsalthat produces the.get()chain, or refactoring_migrate_rehearsalto accept both Calls and Attributes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@decoy/codemods/migrate.py` around lines 163 - 171, The list comprehension in verify_statements calls _as_call(arg.value) assuming a Call, which raises ValueError for Attribute nodes (e.g., mock.some_prop); update the non-starred branch to detect Attribute values and handle them like migrate_call does: either create a new helper _migrate_attribute_rehearsal(attribute_node, kwargs) that returns the .get() chain and call _migrate_rehearsal with that, or refactor _migrate_rehearsal to accept both Call and Attribute; replace the direct _as_call(arg.value) usage in verify_statements with logic that uses _as_call when arg.value is a Call and uses _migrate_attribute_rehearsal (or the refactored _migrate_rehearsal) when arg.value is an Attribute so attribute-get rehearsals no longer raise ValueError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@decoy/codemods/migrate.py`:
- Around line 391-407: The helper _statements currently always wraps each
element with _as_call(...) which raises ValueError for attribute rehearsals
inside conditional spreads (e.g., *(flag and [mock.some_prop] or [])); update
_statements so it mirrors the main verify_order fix: attempt to convert with
_as_call(cst.ensure_type(element, cst.Element).value) but catch ValueError (or
detect non-Call shapes like attribute/conditional elements) and fall back to
passing the raw element value through to _migrate_rehearsal; keep references to
_statements, _as_call, _migrate_rehearsal and decoy_func so the migration
handles attribute rehearsals in conditional spreads without raising.
---
Duplicate comments:
In `@decoy/codemods/migrate.py`:
- Around line 286-290: The current update to self._captor_assignments iterates
scope.assignments and picks all assignments with matching names (via
scope.assignments and a.name in names), which captures later rebindings;
instead, add only the specific AST assignment node for this captor visit. Change
the code in track_captor_assignment so it does not iterate scope.assignments but
directly inserts the exact assignment node being visited (the local parameter
representing the captor assignment) into self._captor_assignments, using the
same set/update structure but with that single node instead of a comprehension
over scope.assignments.
- Around line 119-145: The positional-argument matcher inside the AtLeastN block
(the m.Arg(...) branch) currently only accepts m.Call() or m.Await(m.Call()),
which excludes attribute-get rehearsals like mock.some_prop; update that matcher
to also accept m.Attribute() (and the awaited form m.Await(m.Attribute())) so
attribute accesses are matched as valid positional args—i.e., in the AtLeastN
args list replace/add alternatives in the m.Arg(...) clause to include
m.Attribute() and m.Await(m.Attribute()) alongside m.Call()/m.Await(m.Call()).
- Around line 163-171: The list comprehension in verify_statements calls
_as_call(arg.value) assuming a Call, which raises ValueError for Attribute nodes
(e.g., mock.some_prop); update the non-starred branch to detect Attribute values
and handle them like migrate_call does: either create a new helper
_migrate_attribute_rehearsal(attribute_node, kwargs) that returns the .get()
chain and call _migrate_rehearsal with that, or refactor _migrate_rehearsal to
accept both Call and Attribute; replace the direct _as_call(arg.value) usage in
verify_statements with logic that uses _as_call when arg.value is a Call and
uses _migrate_attribute_rehearsal (or the refactored _migrate_rehearsal) when
arg.value is an Attribute so attribute-get rehearsals no longer raise
ValueError.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2b059076-86d1-4693-9f8d-28d2cb96b24a
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
codebook.tomldecoy/codemods/__init__.pydecoy/codemods/migrate.pydecoy/codemods/pyproject.tomldecoy/next/pyproject.tomldocs/v3/migration.mdpyproject.tomltests/codemods/test_migrate.py
✅ Files skipped from review due to trivial changes (4)
- codebook.toml
- decoy/next/pyproject.toml
- decoy/codemods/pyproject.toml
- decoy/codemods/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
- pyproject.toml
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/v3/migration.md`:
- Around line 13-18: The Codemod documentation omits the Python version
requirement and should state that Python 3.10+ is required to run the codemod;
update the "Codemod" section to add a brief note that the codemod targets Python
3.10 (see decoy/codemods/pyproject.toml target-version = "py310") and uses
3.10-only syntax, and instruct users to run the command with Python 3.10+ (e.g.,
before the uv run line mention "Requires Python 3.10+") so users invoking the
MigrateCommand via `python -m libcst.tool codemod
decoy.codemods.migrate.MigrateCommand` won’t hit syntax errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1ca820fd-b76d-478c-b715-34acc3d9c2a9
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
codebook.tomldecoy/codemods/__init__.pydecoy/codemods/migrate.pydecoy/codemods/pyproject.tomldecoy/next/pyproject.tomldocs/v3/migration.mdpyproject.tomltests/codemods/test_migrate.py
✅ Files skipped from review due to trivial changes (2)
- decoy/codemods/pyproject.toml
- decoy/codemods/init.py
🚧 Files skipped from review as they are similar to previous changes (3)
- decoy/next/pyproject.toml
- codebook.toml
- tests/codemods/test_migrate.py
Overview
This PR adds a
libcst-powered codemod to migrate a codebase from Decoy v2 to the Decoy v3 previewChange log
decoy.codemods.migratemodule