Release
Refactron v0.2.3: We Doubled the Transform Catalog
Ten new deterministic transforms in v0.2.3 — six for Python, four for TypeScript — bringing the catalog from ten to twenty. A new pythonVersion configuration key so version-gated rewrites can be opted in safely. An engine composition bug that silently dropped changes in earlier releases, now fixed. This is the largest catalog expansion since the v2.0 rebuild.
What is new in Python
The six new Python transforms group into three themes: type modernization, stdlib aliases, and micro-cleanups. Each transform has a tight refusal set and is gated on the project's Python version where the language change requires it.
Type modernization. The two transforms here move codebases off the typing-module generic aliases and onto the native syntax that PEP 585 and PEP 604 introduced.
- pep585_generics rewrites typing.List[X] to list[X], typing.Dict[K, V] to dict[K, V], typing.Tuple[...] to tuple[...], and the rest of the typing-module collection generics. Gated on Python 3.9 or higher, or a from __future__ import annotations at the top of the file. Refuses files that use Pydantic v1 or call get_type_hints — both evaluate annotations at runtime and would crash on the new syntax.
- pep604_optional_union rewrites Optional[X] to X | None and Union[A, B] to A | B. Gated on Python 3.10 or higher, or future-annotations. Refuses the same Pydantic v1 and get_type_hints cases as pep585.
Stdlib aliases. Two narrow rewrites that swap a verbose form for a newer alias the standard library introduced.
- datetime_utc_alias rewrites datetime.timezone.utc to datetime.UTC. Gated on Python 3.11 or higher.
- lru_cache_to_cache rewrites @functools.lru_cache(maxsize=None) to @functools.cache and fixes the from functools import line to import cache instead of lru_cache where appropriate. Gated on Python 3.9 or higher.
Micro-cleanups. Two transforms that remove redundancy without changing semantics, with one careful exception.
- super_no_args rewrites super(ClassName, self).method(...) to super().method(...). Refuses when the class name passed to super() differs from the enclosing class — that case is MRO-sensitive and changing it would alter method resolution.
- yield_from_for_loop rewrites for x in y: yield x — when that is the entire body of the loop — to yield from y. Refuses inside an async def, because yield from in an async function is a CPython compile-stage SyntaxError that LibCST's parser does not catch.
What is new in TypeScript
Four new TypeScript transforms: three type-aware easy wins and one Vue-specific rewrite.
- indexof_to_includes rewrites arr.indexOf(x) !== -1 and its variants to arr.includes(x). Type-aware via ts-morph — the receiver must be a String, Array, or ReadonlyArray. Other types with an indexOf method are refused. Gated on a tsconfig target of ES2016 or higher.
- object_assign_to_spread rewrites Object.assign({}, a, b) to an object spread expression with a and b spread into a new object literal. The first argument must be an object literal — calls that mutate an existing object are refused. Spread-element sources are refused. Gated on a tsconfig target of ES2018 or higher.
- string_concat_to_template_literal rewrites a sequence of string-plus-expression concatenations into a template literal. Refuses any operand typed as any, unknown, or a non-primitive — those would change the runtime stringification behavior. Gated on a tsconfig target of ES2015 or higher.
- vue_set_delete_to_assignment rewrites Vue.set and this.$set to direct property assignment, and Vue.delete and this.$delete to the delete operator. Applies to .js and .ts files only — single-file-component .vue parsing is deferred to v0.4. On Vue 2 codebases this is a semantic change, because Vue.set is required to create new reactive keys on an existing reactive object. The caveat ships in the suggestion text so the reviewer sees it before applying.
pythonVersion as a first-class config
Four of the new Python transforms — pep585_generics, pep604_optional_union, datetime_utc_alias, and lru_cache_to_cache — need to know the project's target Python version, because the language features they emit are only available from a specific version forward. We introduced pythonVersion as a top-level config key to make this explicit.
When the key is unset, Refactron auto-detects the project's Python version from the requires-python field in pyproject.toml. When neither the key nor a requires-python field is present, the version-gated transforms refuse rather than guess. We would rather decline to refactor than emit code that fails to parse on the target interpreter.
Setting it explicitly is one line:
{
"pythonVersion": "3.11"
}Existing .refactronrc.json files without the key continue to work for every non-version-gated transform.
Engine composition, fixed
When multiple transforms touched the same file in the same run, only one transform's changes survived to disk. The bug shipped silently in v0.2.0 and v0.2.1, was caught during v0.2.3 integration testing on a project where four Python transforms had overlapping file coverage, and is fixed in this release. A full postmortem is in a follow-up post.
What is not in this release
A few things readers have asked about that did not make this release.
- Single-file-component .vue support is deferred to v0.4. SFC parsing needs a dedicated parser layer that is its own infrastructure, and shipping a half-built version would be worse than waiting.
- No new language adapters. Go, Ruby, and Java are still not supported. Adding a third adapter is a v2.x roadmap item, not a patch-release item.
- No TypeScript transforms that require deeper type information. nullish_coalescing, optional_chain, and the any-to-unknown rewrites all need a richer type-info layer than the current ts-morph integration exposes. That layer is planned for v2.2.
Upgrading
npm install -g refactron@0.2.3
No breaking changes from v0.2.0, v0.2.1, or v0.2.2. Existing .refactronrc.json files continue to work without modification. The new pythonVersion key is optional — if you set it, the four version-gated Python transforms can run; if you do not, they stay refused on files where the version requirement matters.