OpenRewrite

repository·main·Indexed 25 days ago

https://github.com/openrewrite/rewrite

An open-source automated refactoring ecosystem that uses Lossless Semantic Trees (LSTs) to perform code migrations, security fixes, and stylistic updates. It supports multiple programming languages including Java, Kotlin, Groovy, JavaScript/TypeScript, Python, and C#. For Java, refactoring can be executed via Gradle or Maven plugins. The ecosystem includes language-specific implementations such as @openrewrite/rewrite for JavaScript and a Python package for recipe authoring and testing.

Tokens
35.4K
Snippets
45
Records
183
Agent score
86%

What's inside OpenRewrite

  1. Overview of OpenRewrite

    main

    OpenRewrite is an open-source automated refactoring ecosystem designed to eliminate technical debt. It uses an auto-refactoring engine to run prepackaged recipes for framework migrations, security fixes, and stylistic consistency.

    Supported languages include:

    • Java
    • Kotlin
    • Groovy
    • JavaScript/TypeScript
    • Python
    • C#

    For Java developers, refactoring can be executed using the OpenRewrite Gradle Plugin or the OpenRewrite Maven Plugin against single repositories.

  2. Understand Enhanced Type Mapping for JavaScript/TypeScript

    main

    OpenRewrite's JavaScript/TypeScript parser uses an enhanced type mapping system that leverages the TypeScript type checker. This allows for rich type information during AST transformations, even when working with plain .js files (via checkJs enablement).

    Key capabilities include:

    • Primitive Mapping: Supports boolean, number, string, void, null, undefined, and bigint.
    • Fully Qualified Name Resolution: Resolves types using the format "module-specifier.TypeName" (e.g., @mui/material.Button or lib.Array).
    • Object and Class Types: Maps TypeScript classes, interfaces, and anonymous object literals to JavaType equivalents.
    • Function and Method Types: Supports mapping function declarations, method declarations, and expressions to JavaType.Method.
    • Array Support: Detects and maps arrays (including readonly and frozen arrays) to JavaType.Array.
    • Variable Type Resolution: Maps variable declarations, references, and imported variables to JavaType.Variable.
  3. Understand Scala Syntax Element Mapping in OpenRewrite

    main

    OpenRewrite's Scala support parses Scala 3 source files into a Lossless Semantic Tree (LST). It reuses the Java J model whenever possible and introduces Scala-specific S types and markers only when necessary to represent Scala-specific syntax.

    Scala-specific LST types (S.*)

    These types are used when the Scala syntax has no direct equivalent in the Java J model:

    • S.CompilationUnit: Used for Scala files containing top-level statements (vals, defs, expressions) outside of classes.
    • S.TuplePattern: Used for tuple pattern destructuring (e.g., val (a, b) = (1, 2)).
    • S.Wildcard: Represents the _ underscore as an expression (e.g., in partially applied arguments or placeholder lambda bodies). Note that J.Wildcard is reserved for type wildcards (? extends T).
    • S.BlockExpression: Used for { ... } blocks that act as expressions returning a value, which cannot be represented by the statement-only J.Block.

    Scala-specific markers

    Markers are applied to existing J types to handle Scala-specific printing or semantic requirements:

    • SObject: Applied to J.ClassDeclaration to print the object keyword for singletons.
    • LambdaParameter: Applied to J.VariableDeclarations in lambdas to suppress val/var keywords.
    • UnderscorePlaceholderLambda: Applied to J.Lambda to indicate a lambda with no explicit parameter list (using _).
    • FunctionApplication: Applied to J.MethodInvocation to print syntactic sugar like arr(0) instead of .apply(0).
    • InfixNotation: Applied to J.MethodInvocation to print calls like a + b instead of a.plus(b).
    • ImplicitReturn: Applied to J.Return to allow printing the last expression of a method without the return keyword.
    • OmitBraces: Applied to J.Block to skip emitting { } for empty bodies.
    • Implicit: Applied to J.Modifier to represent modifiers like final on objects that should not be printed.
    • ScalaForLoop: Applied to J.ForLoop to handle range-based generators (<-) that have no Java equivalent.
  4. Understand the Native Python Lock File Regeneration Engine

    main

    OpenRewrite uses a native Java engine for regenerating Python lock files (Pipenv) instead of shelling out to Python. This engine provides minimal-update semantics, meaning it only modifies the specific packages being edited and their required dependencies, preserving all other entries byte-for-byte. This behavior matches pipenv upgrade <pkg> rather than a full pipenv lock re-resolution.

    Accuracy Guarantees

    1. pipenv verify passes: The _meta.hash.sha256 is recomputed using pipenv's exact algorithm.
    2. Successful installation: Every entry contains genuine artifact digests from the configured index, and the pinned set is internally consistent.
    3. Minimal change: Only edited packages and their new requirements are modified.
    4. Fail loud: If metadata or hashes cannot be obtained (e.g., unreachable index, unsupported entry type), the engine emits no lock at all and returns a structured failure.
  5. Use the Rewrite Java Technology Compatibility Kit (TCK) to verify JavaParser implementations

    main
    The Rewrite Java Technology Compatibility Kit (TCK) is a suite of tests designed to verify that a JavaParser implementation conforms to the expected OpenRewrite standards. It is used to ensure compatibility across different Java language levels and parser implementations.
  6. Understand Native Python Lock File Regeneration Design

    main

    OpenRewrite is moving away from shelling out to real package managers (like pipenv lock or uv lock) for regenerating Python lock files (Pipfile.lock, uv.lock). This design addresses failures in large-scale environments where the Python toolchain is missing, index credentials are not inherited, or the execution environment's Python interpreter differs from the project's requirements.

    Key improvements in the native approach include:

    • No Python dependency: Works in JVM-only environments.
    • Credential inheritance: Uses provided index configurations instead of relying on local machine state.
    • Reduced failure surface: Avoids re-resolving the entire dependency graph when only a specific manifest edit is made.
    • Structured failures: Provides better visibility into which package or index caused a resolution error.
  7. Author a Go-native recipe

    main

    To create a Go recipe, embed recipe.Base in a struct, implement Name(), DisplayName(), and Description(), and return a TreeVisitor from the Editor() method.

    Critical Patterns:

    • Always use visitor.Init(...): This sets the Self field on the embedded GoVisitor to ensure virtual dispatch works correctly.
    • Never mutate in place: Always return a fresh value (e.g., a copy of the tree element) instead of mutating the existing one. In-place mutation breaks no-change detection and makes debugging difficult.
    package golang
    
    import (
        "github.com/openrewrite/rewrite/rewrite-go/pkg/recipe"
        "github.com/openrewrite/rewrite/rewrite-go/pkg/tree"
        "github.com/openrewrite/rewrite/rewrite-go/pkg/visitor"
    )
    
    type RenameXToFlag struct{ recipe.Base }
    
    func (r *RenameXToFlag) Name() string        { return "org.openrewrite.golang.test.RenameXToFlag" }
    func (r *RenameXToFlag) DisplayName() string { return "Rename x to flag" }
    func (r *RenameXToFlag) Description() string { return "Test recipe." }
    
    func (r *RenameXToFlag) Editor() recipe.TreeVisitor {
        return visitor.Init(&renameXVisitor{})
    }
    
    type renameXVisitor struct{ visitor.GoVisitor }
    
    func (v *renameXVisitor) VisitIdentifier(ident *tree.Identifier, _ any) tree.J {
        if ident.Name == "x" {
            c := *ident
            c.Name = "flag"
            return &c
        }
        return ident
    }
  8. Manage Architecture Decision Records (ADRs) using adr-tools

    main

    The project uses Architecture Decision Records (ADRs) to document architectural choices. You can manage these records using the adr-tools utility.

    To create a new record, ensure your VISUAL environment variable is set to your preferred editor (e.g., code for VS Code), then use the adr new command. To keep the ADR documentation organized, use adr generate toc to update the table of contents in doc/adr/README.md.

  9. Use the Builder API for dynamic template construction

    main

    While template literals are preferred for static, compile-time patterns, use the Builder API when you need to construct templates or patterns programmatically at runtime (e.g., when the structure is unknown until execution).

    When to use the Builder API:

    • When building patterns dynamically based on runtime logic.
    • When you need to conditionally add or remove parts of a template.
    • When generating repetitive patterns using loops.
    • To avoid manual string concatenation for complex structures.