Tags: openrewrite/rewrite
Tags
UpgradePluginVersion: handle null resolved versions consistently (#7656) `DependencyVersionSelector.select(...)` is `@Nullable` (e.g. when the configured selector matches no version in the plugin's Maven metadata, or when exotic versions trigger an internal `IllegalStateException`), but the scanner had three branches that each handled the contract differently: - Literal version: passed the (possibly null) result to the second visitor, which already short-circuits on null. Safe. - GString version (`id 'foo' version "$prop"`): a bare `assert resolvedPluginVersion != null` fired, surfacing as `AssertionError: null` from `UpgradePluginVersion.java:173`. - Identifier version (`id 'foo' version someIdent`): put `null` into `pluginIdToNewVersion`, which then NPE'd inside `VersionComparator.checkVersion` when the property-file visitor passed it to `VersionComparator.upgrade(currentVersion, singletonList(null))`. Apply a single rule: when `select(...)` returns `null`, leave the build script element unchanged and do not record anything in the accumulator. Also guard `visitEntry` against a `null` resolved version and a blank current version on the gradle.properties side.
Package Go RPC sources as part of the rewrite-go JAR (#7715) * Package Go RPC sources as part of the rewrite-go JAR * Fix the Gradle task dependencies
Kotlin recipe DSL: chained-call patterns + cursor exposure in visitX … …actions (#7708) * Kotlin recipe DSL: chained-call patterns Adds depth-2 chain support to the K2 plugin so authors can write `rewrite { xs: List<T>, p -> xs.filter(p).first() } to { xs, p -> xs.first(p) }` and similar fused-terminal rewrites in one pattern. Five recipes in `recipes-kotlin`'s Performance category (UseFirstWithPredicate, UseLastWithPredicate, UseCountWithPredicate, UseAnyWithPredicate, UseNoneWithPredicate) drive the design. K2 plugin (`RecipeIrGenerationExtension`): - `BeforeLambda` gained an optional `inner: BeforeLambda?` field. When set, `rootCall` is the outer segment and `inner` holds the inner segment with its own arg signatures and receiver-param binding. - `validateBeforeLambda` recurses one level when the root call's receiver is itself an `IrCall` (depth-2 chains only). Outer args must be literal constants in v1; lambda-param bindings come from the inner segment. - Substitution-source CSV switched from `Int` to `String`. Single-segment recipes still emit plain integer strings (`-1`, `0`, ...) for backward compatibility. Chains emit segment-tagged entries: `o:N` / `o:-1` for outer-call args / receiver, `i:N` / `i:-1` for the inner segment. - Matcher-spec line for a chain pattern is `<outerSpec>\t<innerSpec>` — the tab distinguishes it from the `\n`-separated multi-before encoding. - Multi-before chains are rejected at IR-extract time in v1. Runtime (`GeneratedRecipeSupport`): - `methodInvocationRewrite` detects `\t` in `matcherSpecsLines` and branches to a new `chainMethodInvocationRewrite` that matches outer-then-`select-is-inner` and substitutes per the tagged CSV. * Kotlin recipe DSL: expose `cursor` inside `visitX { node -> ... }` Switches the visit-primitive action signature from `(Node) -> Node` to `<Visitor>.(Node) -> Node`. The runtime visitor is now the implicit receiver inside the lambda, so authors can read `cursor` — and any other visitor state — without subclassing or threading a thread-local. Why now: `rewrite-kotlin/.../HotPath.kt` already ships `Cursor.isInsideLoop()`, `Cursor.isInsideComposable()`, `Cursor.isInsideHotPath()`, etc. — but under the old 1-arg lambda those helpers were stranded because the DSL never handed the author a cursor. With the receiver-style change, an imperative recipe gates as `visitMethodInvocation { mi -> if (cursor.isInsideLoop()) SearchResult.found(mi, "…") else mi }`. Migration cost: zero. Existing 1-arg lambdas auto-promote — Kotlin accepts `{ node -> ... }` where a `T.(P) -> R` is expected, with the receiver implicit. All in-tree imperative recipes compile unchanged. Implementation: - Codegen template (`GenerateLanguageScopesTask`) emits the new receiver-style signature plus a Function2-style unchecked cast (`((Any, Any) -> Any)`) — receiver-with function types compile to Function2 on the JVM. The dispatcher passes `this` as the receiver when invoking the action. - Hand-written `KotlinScope.kt` mirrors the template change across all ~70 visit primitives. - New `RecipeDslSurfaceTest` case drives a tiny Java source through the DSL and asserts the cursor is populated inside the action.
Delegate org.openrewrite.semver classes to parent in RecipeClassLoader ( #7669) When a recipe's runtime classpath contains rewrite-core, the child RecipeClassLoader was redefining org.openrewrite.semver.DependencyMatcher from the recipe-side jar while the parent classloader had already defined it from the CLI/worker classpath. Code in rewrite-maven (specifically DependencyTreeWalker, reachable from DependencyInsight) then tripped a JVM loader-constraint violation when its method signatures referenced DependencyMatcher across the loader boundary, surfacing as: java.lang.LinkageError: loader constraint violation: loader URLClassLoader wants to load class org.openrewrite.semver.DependencyMatcher. A different class with the same name was previously loaded by CliRecipeClassLoader. org.openrewrite.semver.* (DependencyMatcher, LatestRelease, Semver, etc.) are stable rewrite-core API types intended to be shared across the recipe classloader boundary, so add the package to PARENT_DELEGATED_PREFIXES.
Stop closing recipe classloaders on CachingMavenRecipeBundleResolver … …close (#7670) CachingMavenRecipeBundleResolver hands out RecipeBundleReader instances whose CliRecipeClassLoaders are retained externally -- e.g. by Recipe instances cached in moderne-cli's RunTask.RECIPE_CACHE that get reused across partitions in a multi-repo `mod run`. Once a Recipe has been prepared, the JVM's class cache contains classes defined by that classloader, and any later getResourceAsStream / inner-class load against those classes requires the classloader's URLClassPath jar handles to remain open. Two close paths in the prior implementation closed those handles while consumers still held the classloader: 1. `close()` cascaded through every cached MavenRecipeBundleResolver (added by #7277 to fix Windows CI file-handle leaks) -- closing the reader, which closed its URLClassLoader's URLClassPath. 2. `resolverFor()` compared the cached entry's bundle.getVersion() against the incoming bundle's version and called markEvicted() on mismatch, also closing the inner resolver/reader/classloader. MavenRecipeBundleResolver.resolve() mutates the input bundle's version to the dated snapshot after resolution, so any fresh RecipeBundle still on the requested SNAPSHOT version triggered eviction. The RPC PrepareRecipe handler routinely passes such fresh bundles back through the resolvers. The visible symptom is inner-class NoClassDefFoundError on a CliRecipeClassLoader whose `==` identity is unchanged but whose URLClassPath has been closed (so findResource returns null silently; loadClass falls back to parent which doesn't have the recipe class; calling code sees NCDFE on `new InnerClass()`). Reproduced against rewrite-prethink (jtokkit cl100k_base.tiktoken null → ComprehendCodeTokenCounter$CharCountTokenizer NCDFE) and rewrite-static-analysis (FinalClassVisitor$FinalizingVisitor NCDFE). See moderneinc/customer-requests#2346. Drops the lease/proxy/eviction machinery; the cache is now a plain package-coordinate computeIfAbsent. `close()` is a no-op with a doc explaining the ownership transfer semantics. Consumers that need deterministic cleanup must close the readers they retain (or rely on JVM exit for one-shot processes). The new CachingMavenRecipeBundleResolverTest pins the contract: a TrackingRecipeClassLoader handed out by the resolver must have closeCount == 0 after `resolver.close()`. Confirmed the test fails against the prior implementation (closeCount was 1).
rpc: allow sharing the in-flight Semaphore across ExecutionContexts (#… …7647) The cap added in #7614 was scoped per-ExecutionContext: setMaxInFlight stored an int in the ctx message map and withInFlightSlot lazily created a Semaphore from it. Each independent ExecutionContext therefore got its own Semaphore, which made the cap a per-ctx cap rather than the host-wide cap callers like Moderne SaaS and the mod CLI expect when they create one InMemoryExecutionContext per repo and run several in parallel. Replace the int-message + lazy-create plumbing with a Semaphore-valued message and an explicit setInFlightSemaphore(Semaphore) setter. Callers that want a host-wide cap construct one Semaphore at the orchestrator level and install the same instance on every participating context. The old setMaxInFlight(int) is kept as a convenience for the single-ctx case but its Javadoc now spells out that it is not host-wide. withInFlightSlot runs the work unthrottled if no Semaphore is installed, preserving the "unbounded by default" behavior. Adds RewriteRpcExecutionContextViewTest covering: no-op default, fresh Semaphore from setMaxInFlight, per-ctx isolation (the misuse pattern), true sharing across many ctx/threads, and permit release on exception.
rewrite-python: Unknown declaring-type for unattributed method calls … …+ public SearchResult.found (#7631) Two paired changes that together unblock ``Preconditions.check(uses_method(...), V())`` gates from failing wire-side evaluation when Python sources lack receiver-type attribution (e.g. unit-test fixtures that don't import the receiver). ## type_mapping ``PythonTypeMapping._get_declaring_type`` previously returned ``None`` when Ty couldn't resolve the receiver and AST inference didn't yield a recognizable type. That left ``JavaType.Method._declaring_type`` as ``None`` for method invocations like ``my_array.tostring()`` in unattributed sources. Java's ``MethodMatcher.matches`` accepts ``JavaType.Unknown`` receivers under wildcard patterns (``*..*``) but rejects method types whose declaring type is null at the ``TypesInUse.hasMethodUse`` lookup level. Now ``_get_declaring_type`` returns a non-null ``FullyQualified`` in every case — falling back to the shared ``_UNKNOWN`` singleton — so ``HasMethod`` / ``UsesMethod`` precondition gates work against unattributed code. The function signature changes from ``Optional[JavaType.FullyQualified]`` to ``JavaType.FullyQualified`` to document the new invariant. Resolution paths that previously returned None now fall through to the AST-inference and Unknown fallbacks. ## SearchResult.found Adds a public ``SearchResult.found(tree, description=None)`` static factory mirroring Java's ``org.openrewrite.marker.SearchResult.found``. Returns a new tree carrying a fresh ``SearchResult`` marker — the canonical "different tree" sentinel that ``Check`` / ``CompositePrecondition`` interpret as the gate matching. The native ``rewrite/python/search/{IsSourceFile,UsesType,UsesMethod}`` visitors now call this instead of constructing the Markers/Marker pair by hand.
PreviousNext