Skip to content

Conversation

@knutwannheden
Copy link
Contributor

@knutwannheden knutwannheden commented Jan 9, 2026

Summary

This PR makes sync visitors the default in the TypeScript OpenRewrite implementation, with async visitors available as an alternative. This change improves performance by up to 20x for visitor traversals that don't require async operations.

Key API Changes

Visitor Base Classes

Before (Async Default) After (Sync Default) Async Alternative
TreeVisitor TreeVisitor (sync) AsyncTreeVisitor
JavaVisitor<P> JavaVisitor<P> (sync) AsyncJavaVisitor<P>
JavaScriptVisitor<P> JavaScriptVisitor<P> (sync) AsyncJavaScriptVisitor<P>
TypeVisitor<P> TypeVisitor<P> (sync) AsyncTypeVisitor<P>
JsonVisitor<P> JsonVisitor<P> (sync) AsyncJsonVisitor<P>
YamlVisitor<P> YamlVisitor<P> (sync) AsyncYamlVisitor<P>
PlainTextVisitor<P> PlainTextVisitor<P> (sync) AsyncPlainTextVisitor<P>
ParseErrorVisitor<P> ParseErrorVisitor<P> (sync) AsyncParseErrorVisitor<P>

TreePrinter API

  • TreePrinter.print(): Now returns string directly (sync)
  • TreePrinters.register(): Takes sync TreeVisitor<any, PrintOutputCapture> callback

Pattern & Template API

  • Pattern.match(): Returns MatchResult | undefined (sync)
  • Pattern.matchWithExplanation(): Returns MatchAttemptResult (sync)
  • Template.apply(): Returns J | undefined (sync)
  • PatternMatchingComparator.compare(): Returns boolean (sync)

Parser API

  • Parser.parseOne(): Changed from Promise<SourceFile> to SourceFile (sync)
  • All parsers now have sync parseOne(): JsonParser, YamlParser, PlainTextParser, JavaScriptParser

RPC Infrastructure

  • RpcCodec.rpcSend(): Changed from Promise to void
  • RpcSendQueue methods: All converted to sync (no more await needed)
  • Sender visitors: JavaSender, TypeSender, JavaScriptSender, JsonSender, YamlSender now extend sync visitor base classes

Method Signatures

Visitor methods changed from:

protected async visitMethodDeclaration(method: J.MethodDeclaration, p: P): Promise<J | undefined>

To:

protected visitMethodDeclaration(method: J.MethodDeclaration, p: P): J | undefined

Collection Mapping

  • Use mapSync() instead of mapAsync() for synchronous visitor operations
  • mapAsync() still available for async visitors

Migration Guide

For code extending visitor base classes:

  1. If your visitor needs async or for minimal work: Just change the visitor to extend AsyncXxxVisitor instead
  2. If your visitor doesn't require async: Either change to extend AsyncXxxVisitor or proceed as in this example

Example migration to new sync visitor base class:

// Before
class MyVisitor extends JavaScriptVisitor<P> {
  override async visitMethodDeclaration(...): Promise<J | undefined> {
    // uses of `await` keyword and `mapAsync()`
  }
}

// After
class MyVisitor extends JavaScriptVisitor<P> {
  override visitMethodDeclaration(...): J | undefined {
    // removed uses of `await` keyword and use `mapSync()` instead
  }
}

bmuschko and others added 7 commits January 8, 2026 17:25
* UUID generation fallback for Node version pre-14.17.0

* Apply suggestions from code review

* Use module-load-time selection for performance

* Polish

---------

Co-authored-by: Tim te Beek <tim@moderne.io>
Co-authored-by: Knut Wannheden <knut@moderne.io>
… a direct dependency if needed (#6434)

* Allowing `AddDependency` to broaden the scope of existing dependency, provided you're requesting an equal or higher version number. If requesting same scope but higher version number, the version number will be upgraded.

* Dropping `import` scope validity in favour of `compile`, given `import` is only applicable for dependencies in `dependencyManagement`

Co-authored-by: Tim te Beek <tim@moderne.io>

---------

Co-authored-by: Tim te Beek <tim@moderne.io>
* Drop Lombok hint from RemoveUnusedImports class

Since we've supported Lombok for quite some time now.

* Also update recipes.csv
* unit test with poc for UnfoldProperties

* use imperative CopyValue in unit test

* Invoke UnfoldProperties after MergeYaml

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Slight polish

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tim te Beek <tim@moderne.io>
@knutwannheden knutwannheden changed the title JavaScript: Make visitor base clases sync and add async alternative JavaScript: More key APIs are now non-async Jan 9, 2026
@knutwannheden knutwannheden marked this pull request as ready for review January 9, 2026 17:25
steve-aom-elliott and others added 12 commits January 9, 2026 15:36
…en direct vs indirect dependency. (#6505)

* If transitive, actually just allow regardless of version, as per old behaviour.
…PomDownloader` to prevent the situation where `/` was producing `/\pom.xml` on Windows rather than `\pom.xml` (#6506)
* Convert package-manager and related APIs from async to sync

- Convert runInstallInTempDir and runWorkspaceInstallInTempDir to use sync
  fs APIs (fs.mkdtempSync, fs.writeFileSync, fs.readFileSync, fs.rmSync)
- Convert runInstallIfNeeded callback from async to sync
- Convert updateNodeResolutionMarker to sync (was marked async with no awaits)
- Convert createLockFileEditor to use sync JsonVisitor and TreeVisitor
- Update add-dependency, upgrade-dependency-version, and
  upgrade-transitive-dependency-version recipes to use sync visitors
- Convert Result.diff() to sync (createTwoFilesPatch is already sync)

The package manager operations were unnecessarily async since the underlying
process spawning (spawnSync) was already synchronous. Only the file I/O was
async, which is negligible compared to the npm/yarn/pnpm install time.

* Remove unnecessary async from IsSourceFile.preVisit()

* Add async property to TreeVisitor and AsyncTreeVisitor

Add a readonly `async` property to both visitor base classes:
- TreeVisitor: async = false
- AsyncTreeVisitor: async = true

This allows runtime discrimination of sync vs async visitors via the
RecipeVisitor union type. Useful for sync-to-sync recipe composition
where a sync visitor wants to call another sync visitor without async
overhead.

* Lift async I/O out of visitors into editorWithData()

Refactor package-manager recipes to do async I/O (npm install) in
editorWithData() before returning the visitor, keeping visitors pure.

Changes:
- Add async runInstallInTempDirAsync() using spawn() and fs.promises
- Add async runWorkspaceInstallInTempDirAsync() for workspace support
- Refactor AddDependency, UpgradeDependencyVersion, and
  UpgradeTransitiveDependencyVersion to run package manager installs
  in editorWithData() before returning visitors
- Remove unused runInstallIfNeeded() helper function
- Visitors are now pure tree transformations with no I/O

This provides a cleaner separation of concerns:
- Async I/O happens in the recipe's async editor() method
- Visitors are pure, synchronous tree transformations using pre-computed data

* More sync visitors

* Simplify receivers by extending visitors

---------

Co-authored-by: Claude <noreply@anthropic.com>
Clarified description for 'onlyIfUsing' option to specify its importance in multi-module projects.

Fixes #5795
# Conflicts:
#	rewrite-core/src/main/java/org/openrewrite/rpc/RewriteRpc.java
@knutwannheden knutwannheden merged commit 7e9133e into js-sync-rpc Jan 14, 2026
@knutwannheden knutwannheden deleted the js-sync-visitors branch January 14, 2026 09:59
@github-project-automation github-project-automation bot moved this from In Progress to Done in OpenRewrite Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

8 participants