Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
15cf34d
Add C# language support with parser, printer, and pattern matching
jkschneider Jan 19, 2026
c9c91f1
Expand C# AST support and test coverage
jkschneider Feb 4, 2026
e7c8152
Add C# RPC infrastructure for Java-C# AST communication
jkschneider Feb 8, 2026
1f6cfc5
Merge origin/main into jkschneider/csharp-support
jkschneider Feb 8, 2026
a81ae82
Add C# preprocessor directive LST model, parser, and RPC support
jkschneider Feb 8, 2026
e64f8d0
Add C# recipe infrastructure and simplify RPC codec layer
jkschneider Feb 9, 2026
85f7d54
Delegate C# printing to RPC and add pattern matching, switch expressi…
jkschneider Feb 9, 2026
46c612e
Add C# recipe marketplace with NuGet package download support
jkschneider Feb 9, 2026
a4056e0
Add Markup, SearchResult, and MarkerPrinter support to C# printer
jkschneider Feb 9, 2026
9cd7960
Add C# type attribution, RPC receiver, and cross-language recipe support
jkschneider Feb 10, 2026
86aa32b
Support launching C# RPC server from published DLL or .csproj
jkschneider Feb 10, 2026
902cd80
Delete RoslynRecipe, target net10.0, and add NuGet publishing
jkschneider Feb 10, 2026
7fa46ca
Pass NuGet version via /p:Version instead of editing .csproj
jkschneider Feb 10, 2026
e740867
Add ParseProject, whitespace validation, and expand C# syntax support
jkschneider Feb 12, 2026
c930462
Delete CsClassDeclaration, use J.ClassDeclaration with ConstrainedTyp…
jkschneider Feb 15, 2026
ea16033
Add remaining C# syntax mappings and delete dead Cs types
jkschneider Feb 16, 2026
4cb8ac2
Implement multi-parse preprocessor directives for C#
jkschneider Feb 17, 2026
156d996
Replace C# record types with classes for LST node performance
jkschneider Feb 17, 2026
9f9402f
Replace line-based directive interleaving with sentinel-based section…
jkschneider Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ out/
*/src/main/gen/
*/src/main/antlr/gen
.idea/*
# Exception: Include Python module for IntelliJ (needs parent dirs unignored first)
# Exception: Include Python/C# modules for IntelliJ (needs parent dirs unignored first)
!.idea/modules/
.idea/modules/*
!.idea/modules/rewrite-python-src/
!.idea/modules/rewrite-csharp-src/
.project
.classpath
.settings/
Expand Down
22 changes: 22 additions & 0 deletions .idea/modules/rewrite-csharp-src/rewrite-csharp-src.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ public <T> List<T> receiveList(@Nullable List<T> before, @Nullable UnaryOperator
// Intentional fall-through...
case CHANGE:
msg = take(); // the next message should be a CHANGE with a list of positions
assert msg.getState() == RpcObjectData.State.CHANGE;
if (msg.getState() != RpcObjectData.State.CHANGE) {
throw new IllegalStateException("Expected CHANGE with positions in receiveList, but got " +
msg.getState() + " (valueType=" + msg.getValueType() + ", value=" + msg.getValue() + ", ref=" + msg.getRef() + ")");
}
List<Integer> positions = requireNonNull(msg.getValue());
List<T> after = new ArrayList<>(positions.size());
for (int beforeIdx : positions) {
Expand Down
167 changes: 167 additions & 0 deletions rewrite-csharp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
@file:Suppress("UnstableApiUsage")

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

plugins {
id("org.openrewrite.build.language-library")
id("org.openrewrite.build.moderne-source-available-license")
id("jvm-test-suite")
id("publishing")
}

dependencies {
api(project(":rewrite-core"))
api(project(":rewrite-java"))

api("org.jetbrains:annotations:latest.release")
api("com.fasterxml.jackson.core:jackson-annotations")

implementation("io.moderne:jsonrpc:latest.integration")

compileOnly(project(":rewrite-test"))
compileOnly(project(":rewrite-xml"))

testImplementation(project(":rewrite-test"))
testImplementation(project(":rewrite-xml"))
testImplementation("io.moderne:jsonrpc:latest.integration")
testRuntimeOnly(project(":rewrite-java-21"))
}

tasks.withType<Javadoc>().configureEach {
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
exclude("**/Cs.java")
}

// C#-specific build tasks
val csharpDir = projectDir.resolve("csharp")

// Find dotnet executable
fun findDotnet(): String {
val candidates = listOf("dotnet")
for (cmd in candidates) {
try {
val process = ProcessBuilder(cmd, "--version")
.redirectErrorStream(true)
.start()
if (process.waitFor() == 0) {
return cmd
}
} catch (e: Exception) {
// Command not found, try next
}
}
throw GradleException(".NET SDK not found. Please install .NET 8.0+ SDK and ensure 'dotnet' is on your PATH.")
}

val csharpBuild by tasks.registering(Exec::class) {
group = "csharp"
description = "Build C# projects"

workingDir = csharpDir
commandLine(findDotnet(), "build")

doFirst {
logger.lifecycle("Building C# projects in ${csharpDir}")
}
}

testing {
suites {
register<JvmTestSuite>("integTest") {
useJUnitJupiter()

dependencies {
implementation(project())
implementation(project(":rewrite-java-21"))
implementation(project(":rewrite-test"))
implementation("org.assertj:assertj-core:latest.release")
implementation("org.junit.platform:junit-platform-suite-api")
runtimeOnly("org.junit.platform:junit-platform-suite-engine")
}
}
}
}

// Run tests serially to avoid issues with concurrent C# RPC processes
tasks.withType<Test> {
// Ensure C# is built before running tests
dependsOn(csharpBuild)

maxParallelForks = 1
// Add timeout to identify hanging tests
systemProperty("junit.jupiter.execution.timeout.default", "30s")
// Show test names as they run
testLogging {
events("started", "passed", "failed", "skipped")
showStandardStreams = true
}
}

// ============================================
// NuGet Publishing Tasks
// ============================================

// Generate a NuGet-compatible version for CI builds
// Snapshots use pre-release suffix: 8.73.0-snapshot.20260110143252
// Releases use clean version: 8.73.0
val nugetVersion: String = if (System.getenv("CI") != null) {
project.version.toString().replace(
"-SNAPSHOT",
"-snapshot.${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))}"
)
} else {
project.version.toString().replace("-SNAPSHOT", "-dev")
}

// Task to pack the C# project as a NuGet tool package
// Version is injected via /p:Version so the .csproj is never modified
val csharpPack by tasks.registering(Exec::class) {
group = "csharp"
description = "Pack C# project as NuGet package"

workingDir = csharpDir
commandLine(
findDotnet(), "pack",
"--configuration", "Release",
"--output", "dist",
"/p:Version=$nugetVersion"
)

inputs.dir(csharpDir.resolve("OpenRewrite"))
inputs.property("version", nugetVersion)
outputs.dir(csharpDir.resolve("dist"))

doFirst {
csharpDir.resolve("dist").deleteRecursively()
logger.lifecycle("Packing C# NuGet package (version: $nugetVersion)")
}
}

// Task to publish NuGet package
val csharpPublish by tasks.registering(Exec::class) {
group = "csharp"
description = "Publish C# NuGet package"

dependsOn(csharpPack)

workingDir = csharpDir
commandLine(
findDotnet(), "nuget", "push",
"dist/*.nupkg",
"--source", "https://api.nuget.org/v3/index.json",
"--api-key", project.findProperty("nugetApiKey")?.toString() ?: ""
)

doFirst {
if (!project.hasProperty("nugetApiKey")) {
throw GradleException("nugetApiKey property is required for NuGet publishing")
}
logger.lifecycle("Publishing C# NuGet package (version: $nugetVersion)")
}
}

// Wire into the main publish task
tasks.named("publish") {
dependsOn(csharpPublish)
}
27 changes: 27 additions & 0 deletions rewrite-csharp/csharp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Build results
bin/
obj/

# Visual Studio
.vs/
*.user
*.suo
*.userosscache
*.sln.docstates

# JetBrains Rider
.idea/

# NuGet
*.nupkg
*.snupkg
.nuget/
packages/

# Test results
TestResults/
*.trx

# OS generated
.DS_Store
Thumbs.db
22 changes: 22 additions & 0 deletions rewrite-csharp/csharp/OpenRewrite.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRewrite", "OpenRewrite\OpenRewrite.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
Loading
Loading