diff --git a/.gitignore b/.gitignore index 35063fc..7a26c89 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,8 @@ CodeCoverage/ # NUnit *.VisualState.xml TestResult.xml -nunit-*.xml \ No newline at end of file +nunit-*.xml +Codon.Plugin/.gradle +Codon.Plugin/.intellijPlatform +Codon.Plugin/build +Codon.Plugin/protocol/build diff --git a/Codon.BinaryCodec/BinaryCodecDefinitions.cs b/Codon.BinaryCodec/BinaryCodecDefinitions.cs index 4feb696..e79bdd9 100644 --- a/Codon.BinaryCodec/BinaryCodecDefinitions.cs +++ b/Codon.BinaryCodec/BinaryCodecDefinitions.cs @@ -267,7 +267,7 @@ public void Write(IByteBuffer buffer, Optional value) public Optional Read(IByteBuffer buffer) { - return BinaryCodecs.BOOLEAN.Read(buffer) ? Optional.Of(innerCodec.Read(buffer)) : Optional.Empty(); + return BinaryCodecs.BOOLEAN.Read(buffer) ? Optional.Of(innerCodec.Read(buffer)) : Optional.Empty(); } } diff --git a/Codon.BinaryCodec/BinaryCodecs.cs b/Codon.BinaryCodec/BinaryCodecs.cs index 77fee44..17f53a8 100644 --- a/Codon.BinaryCodec/BinaryCodecs.cs +++ b/Codon.BinaryCodec/BinaryCodecs.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; +using Codon.Optionals; using DotNetty.Buffers; #pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. @@ -39,13 +41,13 @@ public static class BinaryCodecs public static readonly IBinaryCodec GUID = BYTE_ARRAY.Transform(guid => guid.ToByteArray(), bytes => new Guid(bytes)); - public static BinaryCodecDefinitions.RecursiveBinaryCodec Recursive(Func, IBinaryCodec> self) where T : notnull => new(self); + public static IBinaryCodec Recursive(Func, IBinaryCodec> self) where T : notnull => new BinaryCodecDefinitions.RecursiveBinaryCodec(self); public static IBinaryCodec ByteArray(int? maxSize = null) => new BinaryCodecDefinitions.ByteArrayBinaryCodec(maxSize); public static IBinaryCodec ByteBuffer(int? maxSize = null) => new BinaryCodecDefinitions.ByteBufferBinaryCodec(maxSize); - public static BinaryCodecDefinitions.EnumBinaryCodec Enum() where E : Enum => new(); + public static IBinaryCodec Enum() where E : Enum => new BinaryCodecDefinitions.EnumBinaryCodec(); public static IBinaryCodec Flags() where Te : struct, Enum => BYTE.Transform(te => Unsafe.As(ref te), by => Unsafe.As(ref by)); @@ -73,32 +75,32 @@ public interface IBinaryCodec void Write(IByteBuffer buffer, T value); T Read(IByteBuffer buffer); - BinaryCodecDefinitions.OptionalBinaryCodec Optional() + IBinaryCodec> Optional() { return new BinaryCodecDefinitions.OptionalBinaryCodec(this); } - BinaryCodecDefinitions.DefaultBinaryCodec Default(T defaultValue) + IBinaryCodec Default(T defaultValue) { return new BinaryCodecDefinitions.DefaultBinaryCodec(this, defaultValue); } - BinaryCodecDefinitions.TransformativeBinaryCodec Transform(Func from, Func to) + IBinaryCodec Transform(Func from, Func to) { return new BinaryCodecDefinitions.TransformativeBinaryCodec(this, from, to); } - BinaryCodecDefinitions.DictionaryBinaryCodec MapTo(IBinaryCodec valueCodec, int? maxSize = null) where V : notnull + IBinaryCodec> MapTo(IBinaryCodec valueCodec, int? maxSize = null) where V : notnull { return new BinaryCodecDefinitions.DictionaryBinaryCodec(this, valueCodec, maxSize); } - BinaryCodecDefinitions.ListBinaryCodec List(int? maxSize = null) + IBinaryCodec> List(int? maxSize = null) { return new BinaryCodecDefinitions.ListBinaryCodec(this, maxSize); } - BinaryCodecDefinitions.UnionBinaryCodec Union(Func> serializerFactory, Func keyFunc) where K : notnull + IBinaryCodec Union(Func> serializerFactory, Func keyFunc) where K : notnull { return new BinaryCodecDefinitions.UnionBinaryCodec(this, keyFunc, serializerFactory); } diff --git a/Codon.Codec/Codecs.cs b/Codon.Codec/Codecs.cs index 1de4085..8b093ed 100644 --- a/Codon.Codec/Codecs.cs +++ b/Codon.Codec/Codecs.cs @@ -114,7 +114,7 @@ public override Optional Decode(ITranscoder transcoder, D value) return EqualityComparer.Default.Equals(value, nullValue) ? Optionals.Optional.Empty() - : Optionals.Optional.From(Inner.Decode(transcoder, value)); + : Optionals.Optional.Of(Inner.Decode(transcoder, value)); } catch (Exception) { diff --git a/Codon.Codec/Extensions.cs b/Codon.Codec/Extensions.cs index 158d41b..5ab7d28 100644 --- a/Codon.Codec/Extensions.cs +++ b/Codon.Codec/Extensions.cs @@ -16,15 +16,15 @@ public static class Extensions public static Optional ToOptional(this T? nullable) where T : struct { return nullable.HasValue - ? new Optional(nullable.Value) - : new Optional(); + ? Optional.Of(nullable.Value) + : Optional.Empty(); } public static Optional ToOptional(this T? obj) where T : class { return obj is not null - ? new Optional(obj) - : new Optional(); + ? Optional.Of(obj) + : Optional.Empty(); } public static JsonElement ToJson(this string text) => JsonDocument.Parse(text).RootElement; diff --git a/Codon.Optionals/Extensions.cs b/Codon.Optionals/Extensions.cs new file mode 100644 index 0000000..ecfd08f --- /dev/null +++ b/Codon.Optionals/Extensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Runtime.CompilerServices; + +namespace Codon.Optionals; + +public static class Extensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? ToNullableStruct(this Optional optional) where T : struct + { + return optional.IsPresent ? optional.Value : null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? ToNullableClass(this Optional optional) where T : class + { + return optional.IsPresent ? optional.Value : null; + } + +} diff --git a/Codon.Optionals/Optional.cs b/Codon.Optionals/Optional.cs index ee5a752..91a8457 100644 --- a/Codon.Optionals/Optional.cs +++ b/Codon.Optionals/Optional.cs @@ -1,58 +1,54 @@ namespace Codon.Optionals; -public class Optional(bool isPresent, T? value) +public class Optional { - public T? Value => IsPresent ? value : default; - - public readonly bool IsPresent = isPresent; + public T? Value { get; set; } + public bool IsPresent { get; set; } public bool IsMissing => !IsPresent; - public Optional() : this(false, default) + public Optional() { + IsPresent = false; + Value = default; } - public Optional(T? value) : this(value != null, value) + public Optional(object? value) { + if (value is null) + { + IsPresent = false; + Value = default; + } + else + { + IsPresent = true; + Value = (T)value; + } } - public T GetOrElse(T defaultValue) - { - return IsPresent ? Value! : defaultValue; - } + public T GetOrElse(T defaultValue) => IsPresent ? Value! : defaultValue; - public override string ToString() - { - return IsMissing ? "null" : Value!.ToString()!; - } + public override string ToString() => IsMissing ? "null" : Value!.ToString()!; public bool Equals(Optional? other) { if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - if (IsMissing && other.IsMissing) return true; - if (IsMissing || other.IsMissing) return false; return EqualityComparer.Default.Equals(Value!, other.Value!); } - public override bool Equals(object? obj) - { - return obj is Optional other && Equals(other); - } + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + public override int GetHashCode() => IsMissing ? 0 : EqualityComparer.Default.GetHashCode(Value!); - public override int GetHashCode() - { - return IsMissing ? 0 : EqualityComparer.Default.GetHashCode(Value!); - } } public static class Optional { - public static Optional Empty() => new(false, default); - public static Optional Of(T? value) => new(value); - public static Optional From(T value) => new(true, value); + public static Optional Empty() => new(); + public static Optional Of(object? value) => new(value); } diff --git a/Codon.Plugin/.editorconfig b/Codon.Plugin/.editorconfig new file mode 100644 index 0000000..c132eb6 --- /dev/null +++ b/Codon.Plugin/.editorconfig @@ -0,0 +1,9 @@ +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_size = 4 + +[*.yml] +indent_size = 2 diff --git a/Codon.Plugin/.gitattributes b/Codon.Plugin/.gitattributes new file mode 100644 index 0000000..3a94d56 --- /dev/null +++ b/Codon.Plugin/.gitattributes @@ -0,0 +1,5 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Preserve line endings in gradle scripts +gradlew* -text diff diff --git a/Codon.Plugin/.gitignore b/Codon.Plugin/.gitignore new file mode 100644 index 0000000..7cf1f11 --- /dev/null +++ b/Codon.Plugin/.gitignore @@ -0,0 +1,13 @@ +/.idea/ +/.gradle/ +/.intellijPlatform/ +/.kotlin/ + +bin/ +obj/ +build/ + +/src/rider/generated/**/*.Generated.kt +/src/dotnet/Rider.Plugins.CodonPlugin/Model/**/*.Generated.cs + +/src/dotnet/nuget.config diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.gitignore b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.gitignore new file mode 100644 index 0000000..57921d3 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/modules.xml +/.idea.CodonPlugin.sln.iml +/projectSettingsUpdater.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.name b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.name new file mode 100644 index 0000000..2483eb9 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/.name @@ -0,0 +1 @@ +CodonPlugin.sln \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/discord.xml b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/discord.xml new file mode 100644 index 0000000..912db82 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/encodings.xml b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/indexLayout.xml b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/vcs.xml b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin.sln/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.gitignore b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.gitignore new file mode 100644 index 0000000..61c55bf --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.CodonPlugin.iml +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.name b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.name new file mode 100644 index 0000000..e8071da --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/.name @@ -0,0 +1 @@ +CodonPlugin \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/discord.xml b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/discord.xml new file mode 100644 index 0000000..912db82 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/encodings.xml b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/indexLayout.xml b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/vcs.xml b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Codon.Plugin/.idea/.idea.CodonPlugin/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Codon.Plugin/.idea/dictionaries/project.xml b/Codon.Plugin/.idea/dictionaries/project.xml new file mode 100644 index 0000000..f0a7a7c --- /dev/null +++ b/Codon.Plugin/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + rdgen + + + \ No newline at end of file diff --git a/Codon.Plugin/CHANGELOG.md b/Codon.Plugin/CHANGELOG.md new file mode 100644 index 0000000..ad855e3 --- /dev/null +++ b/Codon.Plugin/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## 1.0.0 +- Initial version diff --git a/Codon.Plugin/CONTRIBUTING.md b/Codon.Plugin/CONTRIBUTING.md new file mode 100644 index 0000000..25d0a24 --- /dev/null +++ b/Codon.Plugin/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Contributor Guide +================= + +Prerequisites +------------- +To build the plugin, you'll need .NET SDK 8.0 or later. + +Build +----- +Use the following shell command. It will build a plugin ZIP archive in `build/distributions`. +```console +$ ./gradlew :buildPlugin +``` + +### Run local IDE +To run a test instance of Rider with your plugin, use the following shell command: +```console +$ ./gradlew :runIde +``` + +Test +---- +To run the tests, use the following shell command: +```console +$ ./gradlew :check +``` + +Upgrade Rider Version +--------------------- +To upgrade the IDE version targeted by the plugin, follow these steps. + +1. Update the `riderSdkVersion` in the `gradle.properties`. +2. Update the `kotlin` version in the `versions` section of the `gradle/libs.versions.toml` (see the comment there for the link to the corresponding documentation). diff --git a/Codon.Plugin/CodonPlugin.sln b/Codon.Plugin/CodonPlugin.sln new file mode 100644 index 0000000..53309b6 --- /dev/null +++ b/Codon.Plugin/CodonPlugin.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A9FD9AD-96D2-40B1-9DEE-4521D19D219C}" + ProjectSection(SolutionItems) = preProject + src\dotnet\Directory.Build.props = src\dotnet\Directory.Build.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rider.Plugins.CodonPlugin", "src\dotnet\Rider.Plugins.CodonPlugin\Rider.Plugins.CodonPlugin.csproj", "{3BE21350-43F9-4CBD-9BF2-AE25E9475095}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3BE21350-43F9-4CBD-9BF2-AE25E9475095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BE21350-43F9-4CBD-9BF2-AE25E9475095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BE21350-43F9-4CBD-9BF2-AE25E9475095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BE21350-43F9-4CBD-9BF2-AE25E9475095}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Codon.Plugin/CodonPlugin.sln.DotSettings.user b/Codon.Plugin/CodonPlugin.sln.DotSettings.user new file mode 100644 index 0000000..897ddbd --- /dev/null +++ b/Codon.Plugin/CodonPlugin.sln.DotSettings.user @@ -0,0 +1,8 @@ + + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + <AssemblyExplorer> + <Assembly Path="C:\Users\Synesthesia\.nuget\packages\jetbrains.psi.features.core\262.0.20260729.224244\DotFiles\JetBrains.ReSharper.Psi.CSharp.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/Codon.Plugin/README.md b/Codon.Plugin/README.md new file mode 100644 index 0000000..3c36e12 --- /dev/null +++ b/Codon.Plugin/README.md @@ -0,0 +1,5 @@ +Documentation +------------- +- [Contributor Guide][docs.contributing] + +[docs.contributing]: CONTRIBUTING.md diff --git a/Codon.Plugin/build.gradle.kts b/Codon.Plugin/build.gradle.kts new file mode 100644 index 0000000..d765ea5 --- /dev/null +++ b/Codon.Plugin/build.gradle.kts @@ -0,0 +1,226 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.jetbrains.changelog.exceptions.MissingVersionException +import org.jetbrains.intellij.platform.gradle.Constants +import org.jetbrains.intellij.platform.gradle.TestFrameworkType +import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import kotlin.io.path.absolute +import kotlin.io.path.isDirectory +import kotlin.io.path.isRegularFile + +plugins { + alias(libs.plugins.changelog) + alias(libs.plugins.gradleIntelliJPlatform) + alias(libs.plugins.gradleJvmWrapper) + alias(libs.plugins.kotlinJvm) + id("java") +} + +allprojects { + repositories { + mavenCentral() + } +} + +repositories { + intellijPlatform { + defaultRepositories() + jetbrainsRuntime() + } +} + +val pluginVersion: String by project +val buildConfiguration: String by project +val dotNetPluginId: String by project + +val dotNetSrcDir = File(projectDir, "src/dotnet") + +version = pluginVersion + +val riderSdkPath by lazy { + val path = intellijPlatform.platformPath.resolve("lib/DotNetSdkForRdPlugins").absolute() + if (!path.isDirectory()) error("$path does not exist or not a directory") + + println("Rider SDK path: $path") + return@lazy path +} + +dependencies { + intellijPlatform { + rider(libs.versions.riderSdk) { + useInstaller = false + } + + jetbrainsRuntime() + + bundledModule("intellij.rider.rdclient.dotnet") + + testFramework(TestFrameworkType.Bundled) + + testBundledPlugin("com.intellij.modules.jcef") + testBundledPlugin("intellij.bookmarks.plugin") + testBundledPlugin("intellij.libraries.misc.plugin") + testBundledPlugin("intellij.ssh.plugin") + testBundledPlugin("intellij.structureView.plugin") + } + + testImplementation(libs.junit) + testImplementation(libs.kotlin.test) + testImplementation(libs.openTest4J) + testImplementation(libs.testng) +} + +kotlin { + jvmToolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +sourceSets { + main { + kotlin.srcDir("src/rider/generated/kotlin") + kotlin.srcDir("src/rider/main/kotlin") + resources.srcDir("src/rider/main/resources") + } +} + +tasks { + val generateDotNetSdkProperties by registering { + val dotNetSdkGeneratedPropsFile = File(projectDir, "build/DotNetSdkPath.Generated.props") + doLast { + dotNetSdkGeneratedPropsFile.writeTextIfChanged(""" + + $riderSdkPath + + +""") + } + } + + val generateNuGetConfig by registering { + val nuGetConfigFile = File(dotNetSrcDir, "nuget.config") + doLast { + nuGetConfigFile.writeTextIfChanged(""" + + + + + + + + + """.trimIndent()) + } + } + + val rdGen = ":protocol:rdgen" + + register("prepare") { + dependsOn(rdGen, generateDotNetSdkProperties, generateNuGetConfig) + } + + val compileDotNet by registering(Exec::class) { + dependsOn(rdGen, generateDotNetSdkProperties, generateNuGetConfig) + inputs.property("buildConfiguration", buildConfiguration) + + executable("dotnet") + args("build", "-consoleLoggerParameters:ErrorsOnly", "--configuration", buildConfiguration) + } + + withType { + dependsOn(rdGen) + } + + buildPlugin { + dependsOn(compileDotNet) + } + + patchPluginXml { + val latestChangelog = try { + changelog.getUnreleased() + } catch (_: MissingVersionException) { + changelog.getLatest() + } + changeNotes.set(provider { + changelog.renderItem( + latestChangelog + .withHeader(false) + .withEmptySections(false), + org.jetbrains.changelog.Changelog.OutputType.HTML + ) + }) + } + + withType { + dependsOn(compileDotNet) + + val outputFolder = file("$dotNetSrcDir/$dotNetPluginId/bin/${dotNetPluginId}/$buildConfiguration") + val pluginFiles = listOf( + "$outputFolder/${dotNetPluginId}.dll", + "$outputFolder/${dotNetPluginId}.pdb" + ) + + from(pluginFiles) { + into("${rootProject.name}/dotnet") + } + + doLast { + for (f in pluginFiles) { + val file = file(f) + if (!file.exists()) throw RuntimeException("File \"$file\" does not exist.") + } + } + } + + runIde { + jvmArgs("-Xmx1500m") + } + + test { + useTestNG() + testLogging { + showStandardStreams = true + exceptionFormat = TestExceptionFormat.FULL + } + environment["LOCAL_ENV_RUN"] = "true" + } + + val testRiderPreview by intellijPlatformTesting.testIde.registering { + version = libs.versions.riderSdkPreview + useInstaller = false + task { + enabled = libs.versions.riderSdk.get() != libs.versions.riderSdkPreview.get() + } + } + + check { + dependsOn(testRiderPreview) + } +} + +val riderModel: Configuration by configurations.creating { + isCanBeConsumed = true + isCanBeResolved = false +} + +artifacts { + add(riderModel.name, provider { + intellijPlatform.platformPath.resolve("lib/rd/rider-model.jar").also { + check(it.isRegularFile()) { + "rider-model.jar is not found at \"$it\"." + } + } + }) { + builtBy(Constants.Tasks.INITIALIZE_INTELLIJ_PLATFORM_PLUGIN) + } +} + +fun File.writeTextIfChanged(content: String) { + val bytes = content.toByteArray() + + if (!exists() || !readBytes().contentEquals(bytes)) { + println("Writing $path") + parentFile.mkdirs() + writeBytes(bytes) + } +} diff --git a/Codon.Plugin/gradle.properties b/Codon.Plugin/gradle.properties new file mode 100644 index 0000000..c655a6a --- /dev/null +++ b/Codon.Plugin/gradle.properties @@ -0,0 +1,11 @@ +pluginVersion=1.0.0 + +buildConfiguration=Debug + +riderPluginId=com.jetbrains.rider.plugins.codonplugin +dotNetPluginId=Rider.Plugins.CodonPlugin + +kotlin.stdlib.default.dependency=false + +# We need at least 1 GiB to process the build tasks for several IDE versions: +org.gradle.jvmargs=-Xmx1024m diff --git a/Codon.Plugin/gradle/libs.versions.toml b/Codon.Plugin/gradle/libs.versions.toml new file mode 100644 index 0000000..67e5fb4 --- /dev/null +++ b/Codon.Plugin/gradle/libs.versions.toml @@ -0,0 +1,23 @@ +[versions] +# https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library +kotlin = "2.3.21-RC2" +# https://search.maven.org/artifact/com.jetbrains.rd/rd-gen +rdGen = "2026.2.5" +# https://www.jetbrains.com/intellij-repository/snapshots/com/jetbrains/intellij/rider/riderRD/maven-metadata.xml +# https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/rider/riderRD/maven-metadata.xml +riderSdk = "2026.2.0.1" +riderSdkPreview = "2026.2.0.1" + +[libraries] +junit = "junit:junit:4.13.2" +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } +kotlinStdLib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } +openTest4J = "org.opentest4j:opentest4j:1.3.0" +rdGen = { group = "com.jetbrains.rd", name = "rd-gen", version.ref = "rdGen" } +testng = "org.testng:testng:7.12.0" + +[plugins] +changelog = "org.jetbrains.changelog:2.5.0" +gradleIntelliJPlatform = "org.jetbrains.intellij.platform:2.18.1" +gradleJvmWrapper = "me.filippov.gradle.jvm.wrapper:0.16.0" +kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/Codon.Plugin/gradle/wrapper/gradle-wrapper.jar b/Codon.Plugin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b1b8ef5 Binary files /dev/null and b/Codon.Plugin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Codon.Plugin/gradle/wrapper/gradle-wrapper.properties b/Codon.Plugin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/Codon.Plugin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Codon.Plugin/gradlew b/Codon.Plugin/gradlew new file mode 100644 index 0000000..d67273b --- /dev/null +++ b/Codon.Plugin/gradlew @@ -0,0 +1,339 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# GRADLE JVM WRAPPER START MARKER +BUILD_DIR="${HOME}/.local/share/gradle-jvm" +JVM_ARCH=$(uname -m) +JVM_TEMP_FILE=$BUILD_DIR/gradle-jvm-temp.tar.gz +if [ "$darwin" = "true" ]; then + case $JVM_ARCH in + x86_64) + JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_macos-x64_bin.tar.gz + JVM_TARGET_DIR=$BUILD_DIR/jdk-25.0.2_macos-x64_bin-99fbca + ;; + arm64) + JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_macos-aarch64_bin.tar.gz + JVM_TARGET_DIR=$BUILD_DIR/jdk-25.0.2_macos-aarch64_bin-d7817e + ;; + *) + die "Unknown architecture $JVM_ARCH" + ;; + esac +elif [ "$cygwin" = "true" ] || [ "$msys" = "true" ]; then + JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_windows-x64_bin.zip + JVM_TARGET_DIR=$BUILD_DIR/jdk-25.0.2_windows-x64_bin-96701c +else + JVM_ARCH=$(linux$(getconf LONG_BIT) uname -m) + case $JVM_ARCH in + x86_64) + JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_linux-x64_bin.tar.gz + JVM_TARGET_DIR=$BUILD_DIR/jdk-25.0.2_linux-x64_bin-3c4431 + ;; + aarch64) + JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_linux-aarch64_bin.tar.gz + JVM_TARGET_DIR=$BUILD_DIR/jdk-25.0.2_linux-aarch64_bin-a88282 + ;; + *) + die "Unknown architecture $JVM_ARCH" + ;; + esac +fi + +set -e + +if [ -e "$JVM_TARGET_DIR/.flag" ] && [ -n "$(ls "$JVM_TARGET_DIR")" ] && [ "x$(cat "$JVM_TARGET_DIR/.flag")" = "x${JVM_URL}" ]; then + # Everything is up-to-date in $JVM_TARGET_DIR, do nothing + true +else + echo "Downloading $JVM_URL to $JVM_TEMP_FILE" + + rm -f "$JVM_TEMP_FILE" + mkdir -p "$BUILD_DIR" + if command -v curl >/dev/null 2>&1; then + if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi + # shellcheck disable=SC2086 + curl $CURL_PROGRESS -L --output "${JVM_TEMP_FILE}" "$JVM_URL" 2>&1 + elif command -v wget >/dev/null 2>&1; then + if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi + wget $WGET_PROGRESS -O "${JVM_TEMP_FILE}" "$JVM_URL" 2>&1 + else + die "ERROR: Please install wget or curl" + fi + + echo "Extracting $JVM_TEMP_FILE to $JVM_TARGET_DIR" + rm -rf "$JVM_TARGET_DIR" + mkdir -p "$JVM_TARGET_DIR" + + case "$JVM_URL" in + *".zip") unzip "$JVM_TEMP_FILE" -d "$JVM_TARGET_DIR" ;; + *) tar -x -f "$JVM_TEMP_FILE" -C "$JVM_TARGET_DIR" ;; + esac + + rm -f "$JVM_TEMP_FILE" + + echo "$JVM_URL" >"$JVM_TARGET_DIR/.flag" +fi + +JAVA_HOME= +for d in "$JVM_TARGET_DIR" "$JVM_TARGET_DIR"/* "$JVM_TARGET_DIR"/Contents/Home "$JVM_TARGET_DIR"/*/Contents/Home; do + if [ -e "$d/bin/java" ]; then + JAVA_HOME="$d" + fi +done + +if [ '!' -e "$JAVA_HOME/bin/java" ]; then + die "Unable to find bin/java under $JVM_TARGET_DIR" +fi + +# Make it available for child processes +export JAVA_HOME + +set +e + +# GRADLE JVM WRAPPER END MARKER + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Codon.Plugin/gradlew.bat b/Codon.Plugin/gradlew.bat new file mode 100644 index 0000000..38bd3e2 --- /dev/null +++ b/Codon.Plugin/gradlew.bat @@ -0,0 +1,167 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem GRADLE JVM WRAPPER START MARKER + +setlocal +set BUILD_DIR=%LOCALAPPDATA%\gradle-jvm + +for /f "tokens=3 delims= " %%A in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "PROCESSOR_ARCHITECTURE"') do set WIN_ARCH=%%A +if "%WIN_ARCH%" equ "AMD64" ( + set JVM_TARGET_DIR=%BUILD_DIR%\jdk-25.0.2_windows-x64_bin-96701c\ + set JVM_URL=https://download.oracle.com/java/25/archive/jdk-25.0.2_windows-x64_bin.zip +) else if "%WIN_ARCH%" equ "ARM64" ( + set JVM_TARGET_DIR=%BUILD_DIR%\microsoft-jdk-25.0.2-windows-aarch64-7d2a81\ + set JVM_URL=https://aka.ms/download-jdk/microsoft-jdk-25.0.2-windows-aarch64.zip +) else ( + echo Unknown architecture %WIN_ARCH% + goto fail +) + +set IS_TAR_GZ=0 +set JVM_TEMP_FILE=gradle-jvm.zip + +if /I "%JVM_URL:~-7%"==".tar.gz" ( + set IS_TAR_GZ=1 + set JVM_TEMP_FILE=gradle-jvm.tar.gz +) + +set POWERSHELL=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe + +if not exist "%JVM_TARGET_DIR%" MD "%JVM_TARGET_DIR%" + +if not exist "%JVM_TARGET_DIR%.flag" goto downloadAndExtractJvm + +set /p CURRENT_FLAG=<"%JVM_TARGET_DIR%.flag" +if "%CURRENT_FLAG%" == "%JVM_URL%" goto continueWithJvm + +:downloadAndExtractJvm + +PUSHD "%BUILD_DIR%" +if errorlevel 1 goto fail + +echo Downloading %JVM_URL% to %BUILD_DIR%\%JVM_TEMP_FILE% +if exist "%JVM_TEMP_FILE%" DEL /F "%JVM_TEMP_FILE%" +"%POWERSHELL%" -nologo -noprofile -Command "Set-StrictMode -Version 3.0; $ErrorActionPreference = \"Stop\"; (New-Object Net.WebClient).DownloadFile('%JVM_URL%', '%JVM_TEMP_FILE%')" +if errorlevel 1 goto fail + +POPD + +RMDIR /S /Q "%JVM_TARGET_DIR%" +if errorlevel 1 goto fail + +MKDIR "%JVM_TARGET_DIR%" +if errorlevel 1 goto fail + +PUSHD "%JVM_TARGET_DIR%" +if errorlevel 1 goto fail + +echo Extracting %BUILD_DIR%\%JVM_TEMP_FILE% to %JVM_TARGET_DIR% + +if "%IS_TAR_GZ%"=="1" ( + tar xf "..\\%JVM_TEMP_FILE%" +) else ( + "%POWERSHELL%" -nologo -noprofile -command "Set-StrictMode -Version 3.0; $ErrorActionPreference = \"Stop\"; Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('..\\%JVM_TEMP_FILE%', '.');" +) +if errorlevel 1 goto fail + +DEL /F "..\%JVM_TEMP_FILE%" +if errorlevel 1 goto fail + +POPD + +echo %JVM_URL%>"%JVM_TARGET_DIR%.flag" +if errorlevel 1 goto fail + +:continueWithJvm + +set JAVA_HOME= +for /d %%d in ("%JVM_TARGET_DIR%"*) do if exist "%%d\bin\java.exe" set JAVA_HOME=%%d +if not exist "%JAVA_HOME%\bin\java.exe" ( + echo Unable to find java.exe under %JVM_TARGET_DIR% + goto fail +) + +endlocal & set JAVA_HOME=%JAVA_HOME% + +@rem GRADLE JVM WRAPPER END MARKER + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/Codon.Plugin/protocol/build.gradle.kts b/Codon.Plugin/protocol/build.gradle.kts new file mode 100644 index 0000000..ec60600 --- /dev/null +++ b/Codon.Plugin/protocol/build.gradle.kts @@ -0,0 +1,54 @@ +import com.jetbrains.rd.generator.gradle.RdGenTask + +plugins { + alias(libs.plugins.kotlinJvm) + id("com.jetbrains.rdgen") version libs.versions.rdGen +} + +dependencies { + implementation(libs.rdGen) + implementation(libs.kotlinStdLib) + implementation( + project( + mapOf( + "path" to ":", + "configuration" to "riderModel" + ) + ) + ) +} + +val dotNetPluginId: String by project +val riderPluginId: String by project + +rdgen { + val csOutput = file("../src/dotnet/${dotNetPluginId}/Model").absolutePath + val ktOutput = file("../src/rider/generated/kotlin/${riderPluginId.replace('.','/').lowercase()}").absolutePath + + verbose = true + packages = "model" + + generator { + language = "kotlin" + transform = "asis" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + namespace = "$riderPluginId.model" + directory = ktOutput.toString() + generatedFileSuffix = ".Generated" + } + + generator { + language = "csharp" + transform = "reversed" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + namespace = "$dotNetPluginId.Model" + directory = csOutput.toString() + generatedFileSuffix = ".Generated" + } +} + +tasks.withType { + val classPath = sourceSets["main"].runtimeClasspath + dependsOn(classPath) + classpath(classPath) +} diff --git a/Codon.Plugin/protocol/src/main/kotlin/model/rider/RdCodonPluginModel.kt b/Codon.Plugin/protocol/src/main/kotlin/model/rider/RdCodonPluginModel.kt new file mode 100644 index 0000000..c83df31 --- /dev/null +++ b/Codon.Plugin/protocol/src/main/kotlin/model/rider/RdCodonPluginModel.kt @@ -0,0 +1,31 @@ +package model.rider + +import com.jetbrains.rd.generator.nova.* +import com.jetbrains.rd.generator.nova.PredefinedType.int +import com.jetbrains.rd.generator.nova.PredefinedType.string +import com.jetbrains.rd.generator.nova.csharp.CSharp50Generator +import com.jetbrains.rd.generator.nova.kotlin.Kotlin11Generator +import com.jetbrains.rider.model.nova.ide.ShellModel +import com.jetbrains.rider.model.nova.ide.SolutionModel + +@Suppress("unused") +object RdCodonPluginModel : Ext(SolutionModel.Solution) { + private val RdCallRequest = structdef { + field("myField", string) + } + + private val RdCallResponse = structdef { + field("myResult", int) + } + + init { + setting(Kotlin11Generator.Namespace, "com.jetbrains.rider.plugins.codonplugin.model") + setting(CSharp50Generator.Namespace, "Rider.Plugins.CodonPlugin.Model") + + call("myCall", RdCallRequest, RdCallResponse) + .doc("This is an example protocol call.") + + call("myIconCall", PredefinedType.void, ShellModel.IconModel) + .doc("This is an example protocol call for getting a backend icon.") + } +} diff --git a/Codon.Plugin/scripts/Get-Distribution.ps1 b/Codon.Plugin/scripts/Get-Distribution.ps1 new file mode 100644 index 0000000..b73404c --- /dev/null +++ b/Codon.Plugin/scripts/Get-Distribution.ps1 @@ -0,0 +1,22 @@ +<# + .SYNOPSIS + This script gets the distribution file available in the path passed to it. + .PARAMETER DistributionsLocation + Path to the directory containing compressed plugin distribution. +#> +param ( + [string] $DistributionsLocation = "$PSScriptRoot/../build/distributions" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$file = Get-Item $DistributionsLocation/*.zip +if (!$file) { + throw "File not found in $DistributionsLocation" +} +if (@($file).Count -gt 1) { + throw "Found more files than expected in ${DistributionsLocation}: $($file.Count)" +} + +return $file diff --git a/Codon.Plugin/scripts/Get-Version.ps1 b/Codon.Plugin/scripts/Get-Version.ps1 new file mode 100644 index 0000000..9b5944d --- /dev/null +++ b/Codon.Plugin/scripts/Get-Version.ps1 @@ -0,0 +1,22 @@ +<# + .SYNOPSIS + The purpose of this script is to extract the version information from the compressed plugin artifact, and to + return it via the standard output. + + It is used during CI builds to generate name for the artifact to upload. + .PARAMETER DistributionsLocation + Path to the directory containing compressed plugin distribution. +#> +param ( + [string] $DistributionsLocation = "$PSScriptRoot/../build/distributions" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$file = & "$PSScriptRoot/Get-Distribution.ps1" -DistributionsLocation $DistributionsLocation +if (!($file.Name -match 'RiderCodonPlugin-(.*?)\.zip')) { + throw "File name `"$($file.Name)`" doesn't match the expected pattern" +} + +$Matches[1] diff --git a/Codon.Plugin/scripts/Unpack-Distribution.ps1 b/Codon.Plugin/scripts/Unpack-Distribution.ps1 new file mode 100644 index 0000000..a9265b9 --- /dev/null +++ b/Codon.Plugin/scripts/Unpack-Distribution.ps1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + The purpose of this script is to unpack the compressed plugin artifact. + + It is used during CI builds to generate the layout for uploading. + .PARAMETER DistributionsLocation + Path to the directory containing compressed plugin distribution. +#> +param ( + [string] $DistributionsLocation = "$PSScriptRoot/../build/distributions" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$file = & "$PSScriptRoot/Get-Distribution.ps1" -DistributionsLocation $DistributionsLocation + +Expand-Archive -Path $file -DestinationPath $DistributionsLocation/unpacked diff --git a/Codon.Plugin/settings.gradle.kts b/Codon.Plugin/settings.gradle.kts new file mode 100644 index 0000000..f8583a8 --- /dev/null +++ b/Codon.Plugin/settings.gradle.kts @@ -0,0 +1,16 @@ +rootProject.name = "RiderCodonPlugin" +include(":protocol") + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } + resolutionStrategy { + eachPlugin { + if (requested.id.id == "com.jetbrains.rdgen") { + useModule("com.jetbrains.rd:rd-gen:${requested.version}") + } + } + } +} diff --git a/Codon.Plugin/src/dotnet/Directory.Build.props b/Codon.Plugin/src/dotnet/Directory.Build.props new file mode 100644 index 0000000..6c85c5e --- /dev/null +++ b/Codon.Plugin/src/dotnet/Directory.Build.props @@ -0,0 +1,30 @@ + + + net8.0 + latest + + NU1701 + + true + false + None + + obj\$(MSBuildProjectName)\ + $(DefaultItemExcludes);obj\** + bin\$(MSBuildProjectName)\$(Configuration)\ + + + + TRACE;DEBUG;JET_MODE_ASSERT + + + + + + + + + + $(DotNetSdkPath)\Build\PackageReference.JetBrains.Rider.RdBackend.Common.Props + + diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/CodedBuilder.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/CodedBuilder.cs new file mode 100644 index 0000000..21bc012 --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/CodedBuilder.cs @@ -0,0 +1,187 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rider.Plugins.CodonPlugin; + +public static class CodedBuilder +{ +public static string For(string csharpType, bool binary, bool isEnum = false) + { + var trimmed = csharpType.Trim(); + var nullable = trimmed.EndsWith('?'); + var baseType = nullable ? trimmed.Substring(0, trimmed.Length - 1) : trimmed; + var prefix = binary ? "BinaryCodecs" : "Codecs"; + + string field; + + if (isEnum) + { + field = binary ? $"BinaryCodecs.Enum<{baseType}>()" : $"Codecs.Enum<{baseType}>()"; + } + else if (baseType.EndsWith("[]")) + { + var elementType = baseType.Substring(0, baseType.Length - 2); + field = elementType switch + { + "byte" => $"{prefix}.BYTE_ARRAY", + "int" => $"{prefix}.INT_ARRAY", + "long" => $"{prefix}.LONG_ARRAY", + _ => $"{For(elementType, binary, isEnum)}.List()" + }; + } + else if (TryParseGeneric(baseType, "List", out var listArg) || TryParseGeneric(baseType, "IList", out listArg)) + { + field = $"{For(listArg, binary, isEnum)}.List()"; + } + else if (TryParseGenericPair(baseType, out var keyArg, out var valArg)) + { + field = $"{For(keyArg, binary)}.MapTo({For(valArg, binary)})"; + } + else + { + field = baseType switch + { + "bool" => $"{prefix}.BOOLEAN", + "byte" => $"{prefix}.BYTE", + "short" => $"{prefix}.SHORT", + "int" => $"{prefix}.INT", + "uint" when binary => $"{prefix}.UINT", + "long" => $"{prefix}.LONG", + "float" => $"{prefix}.FLOAT", + "double" => $"{prefix}.DOUBLE", + "string" => $"{prefix}.STRING", + "Guid" => $"{prefix}.GUID", + "IByteBuffer" when binary => $"{prefix}.BYTE_BUFFER", + _ => binary ? $"{baseType}.BINARY_CODEC" : $"{baseType}.CODEC" + }; + } + + return nullable ? $"{field}.Optional()" : field; + } + + public static string BuildCodecField(string className, bool binary, IReadOnlyList<(string Name, string Type, bool Nullable, bool IsEnum)> members) + { + var fieldName = binary ? "BINARY_CODEC" : "CODEC"; + var codecType = binary ? "IBinaryCodec" : "Codec"; + var builderType = binary ? "BinaryCodecs" : "StructCodec"; + + var sb = new StringBuilder(); + sb.AppendLine($"public static readonly {codecType}<{className}> {fieldName} = {builderType}.For<{className}>()"); + + foreach (var m in members) + { + var codec = For(m.Type, binary, m.IsEnum); + var accessor = m.Nullable ? $"c => Optional.Of(c.{m.Name})" : $"c => c.{m.Name}"; + + var nameArg = binary ? "" : $"\"{m.Name}\", "; + sb.AppendLine($" .Field({nameArg}{codec}, {accessor})"); + } + + var ctorParams = string.Join(", ", members.Select(m => m.Name.ToLowerInvariant())); + var ctorArgs = string.Join(", ", members.Select(m => + { + var mname = m.Name.ToLowerInvariant(); + var isStruct = m.IsEnum || isKnownStructType(m.Type); + + if (!m.Nullable) + return mname; + + + return isStruct ? $"{mname}.ToNullableStruct()" : $"{mname}.ToNullableClass()"; + })); + sb.AppendLine($" .Build(({ctorParams}) =>"); + sb.AppendLine($" new {className}({ctorArgs}));"); + + return sb.ToString(); + } + + public static bool TryParseGeneric(string type, string genericName, out string arg) + { + arg = null; + var prefix = $"{genericName}<"; + if (!type.StartsWith(prefix) || !type.EndsWith(">")) + { + return false; + } + + arg = type.Substring(prefix.Length, type.Length - prefix.Length - 1).Trim(); + return true; + } + + public static bool TryParseGenericPair(string type, out string key, out string value) + { + key = null; + value = null; + + foreach (var name in new[] { "Dictionary", "IDictionary" }) + { + var prefix = $"{name}<"; + if (!type.StartsWith(prefix) || !type.EndsWith(">")) + { + continue; + } + + var inner = type.Substring(prefix.Length, type.Length - prefix.Length - 1); + var depth = 0; + var splitAt = -1; + for (var i = 0; i < inner.Length; i++) + { + if (inner[i] == '<') + { + depth++; + } + else if (inner[i] == '>') + { + depth--; + } + else if (inner[i] == ',' && depth == 0) + { + splitAt = i; + break; + } + } + + if (splitAt < 0) + { + continue; + } + + key = inner.Substring(0, splitAt).Trim(); + value = inner.Substring(splitAt + 1).Trim(); + return true; + } + + return false; + } + + private static bool isKnownStructType(string typeName) + { + var baseType = typeName.TrimEnd('?').Trim(); + + return baseType switch + { + "bool" => true, + "byte" => true, + "sbyte" => true, + "char" => true, + "decimal" => true, + "double" => true, + "float" => true, + "int" => true, + "uint" => true, + "long" => true, + "ulong" => true, + "short" => true, + "ushort" => true, + "Guid" => true, + "DateTime" => true, + "TimeSpan" => true, + "DateTimeOffset" => true, + _ => false + }; + } +} diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/GenerateCodecsAction.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/GenerateCodecsAction.cs new file mode 100644 index 0000000..a7d570f --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/GenerateCodecsAction.cs @@ -0,0 +1,102 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using JetBrains.Application.DataContext; +using JetBrains.Application.Progress; +using JetBrains.Application.UI.Actions; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.DataContext; +using JetBrains.ReSharper.Feature.Services.ContextActions; +using JetBrains.ReSharper.Feature.Services.CSharp.ContextActions; +using JetBrains.ReSharper.Feature.Services.Util; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.CSharp.Tree; +using JetBrains.ReSharper.Psi.Util; +using JetBrains.TextControl; +using JetBrains.TextControl.DataContext; +using JetBrains.Util; + +namespace Rider.Plugins.CodonPlugin; + +[ContextAction( + Name = "GenerateCodecs", + Description = "Generates codec fields for the class", + GroupType = typeof(CSharpContextActions), + Disabled = false, + Priority = 1)] +public class GenerateCodecsAction(ICSharpContextActionDataProvider provider) : ContextActionBase +{ + public bool Update(IDataContext context, ActionPresentation presentation, DelegateUpdate nextUpdate) + { + var classDecl = GetClassDeclaration(context); + var enabled = classDecl?.PrimaryConstructorDeclaration != null; + presentation.Visible = classDecl != null; + return enabled; + } + + protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) + { + var classDecl = provider.GetSelectedElement(); + if (classDecl?.PrimaryConstructorDeclaration == null) return null; + + var className = classDecl.DeclaredElement?.ShortName; + if (className == null) return null; + + var members = ExtractMembers(classDecl.PrimaryConstructorDeclaration); + if (members.Count == 0) return null; + + var codecField = CodedBuilder.BuildCodecField(className, binary: false, members); + var binaryCodecField = CodedBuilder.BuildCodecField(className, binary: true, members); + + var factory = CSharpElementFactory.GetInstance(classDecl); + + var codecMember = (IClassMemberDeclaration)factory.CreateTypeMemberDeclaration(codecField); + var binaryCodecMember = (IClassMemberDeclaration)factory.CreateTypeMemberDeclaration(binaryCodecField); + + classDecl.AddClassMemberDeclaration(codecMember); + classDecl.AddClassMemberDeclaration(binaryCodecMember); + + return null; + } + + public override string Text => "Generate Codec"; + + public static IClassLikeDeclaration GetClassDeclaration(IDataContext context) + { + var solution = context.GetData(ProjectModelDataConstants.SOLUTION); + var textControl = context.GetData(TextControlDataConstants.TEXT_CONTROL); + if (solution == null || textControl == null) return null; + + return TextControlToPsi.GetElement(solution, textControl); + } + + public static IReadOnlyList<(string Name, string Type, bool Nullable, bool IsEnum)> ExtractMembers( + IPrimaryConstructorDeclaration primaryCtor) + { + var result = new List<(string, string, bool, bool)>(); + + foreach (var p in primaryCtor.Params.ParameterDeclarations) + { + var name = p.NameIdentifier!.Name; + + var underlyingType = p.Type.GetNullableUnderlyingType() ?? p.Type; + var isEnum = underlyingType.IsEnumType(); + + var isNullable = p.Type.IsNullable(); + var typeText = p.Type.GetPresentableName(CSharpLanguage.Instance!); + + result.Add((name, typeText, isNullable, isEnum)); + } + + return result; + } + + public override bool IsAvailable(IUserDataHolder cache) + { + var classDecl = provider.GetSelectedElement(); + return classDecl?.PrimaryConstructorDeclaration != null; + } +} diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Model/RdCodonPluginModel.Generated.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Model/RdCodonPluginModel.Generated.cs new file mode 100644 index 0000000..e84c5a3 --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Model/RdCodonPluginModel.Generated.cs @@ -0,0 +1,294 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a RdGen v1.13. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using JetBrains.Annotations; + +using JetBrains.Core; +using JetBrains.Diagnostics; +using JetBrains.Collections; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Serialization; +using JetBrains.Rd; +using JetBrains.Rd.Base; +using JetBrains.Rd.Impl; +using JetBrains.Rd.Tasks; +using JetBrains.Rd.Util; +using JetBrains.Rd.Text; + + +// ReSharper disable RedundantEmptyObjectCreationArgumentList +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantOverflowCheckingContext + + +namespace Rider.Plugins.CodonPlugin.Model +{ + + + /// + ///

Generated from: RdCodonPluginModel.kt:11

+ ///
+ public class RdCodonPluginModel : RdExtBase + { + //fields + //public fields + + /// + /// This is an example protocol call. + /// + [NotNull] public IRdEndpoint MyCall => _MyCall; + + /// + /// This is an example protocol call for getting a backend icon. + /// + [NotNull] public IRdEndpoint MyIconCall => _MyIconCall; + + //private fields + [NotNull] private readonly RdCall _MyCall; + [NotNull] private readonly RdCall _MyIconCall; + + //primary constructor + private RdCodonPluginModel( + [NotNull] RdCall myCall, + [NotNull] RdCall myIconCall + ) + { + if (myCall == null) throw new ArgumentNullException("myCall"); + if (myIconCall == null) throw new ArgumentNullException("myIconCall"); + + _MyCall = myCall; + _MyIconCall = myIconCall; + BindableChildren.Add(new KeyValuePair("myCall", _MyCall)); + BindableChildren.Add(new KeyValuePair("myIconCall", _MyIconCall)); + } + //secondary constructor + internal RdCodonPluginModel ( + ) : this ( + new RdCall(RdCallRequest.Read, RdCallRequest.Write, RdCallResponse.Read, RdCallResponse.Write), + new RdCall(JetBrains.Rd.Impl.Serializers.ReadVoid, JetBrains.Rd.Impl.Serializers.WriteVoid, JetBrains.Rider.Model.IconModel.Read, JetBrains.Rider.Model.IconModel.Write) + ) {} + //deconstruct trait + //statics + + + + protected override long SerializationHash => -2925658527367673754L; + + protected override Action Register => RegisterDeclaredTypesSerializers; + public static void RegisterDeclaredTypesSerializers(ISerializers serializers) + { + + serializers.RegisterToplevelOnce(typeof(JetBrains.Rider.Model.IdeRoot), JetBrains.Rider.Model.IdeRoot.RegisterDeclaredTypesSerializers); + } + + + //constants + + //custom body + //methods + //equals trait + //hash code trait + //pretty print + public override void Print(PrettyPrinter printer) + { + printer.Println("RdCodonPluginModel ("); + using (printer.IndentCookie()) { + printer.Print("myCall = "); _MyCall.PrintEx(printer); printer.Println(); + printer.Print("myIconCall = "); _MyIconCall.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + public static class SolutionRdCodonPluginModelEx + { + public static RdCodonPluginModel GetRdCodonPluginModel(this JetBrains.Rider.Model.Solution solution) + { + return solution.GetOrCreateExtension("rdCodonPluginModel", () => new RdCodonPluginModel()); + } + } + + + /// + ///

Generated from: RdCodonPluginModel.kt:13

+ ///
+ public sealed class RdCallRequest : IPrintable, IEquatable + { + //fields + //public fields + [NotNull] public string MyField {get; private set;} + + //private fields + //primary constructor + public RdCallRequest( + [NotNull] string myField + ) + { + if (myField == null) throw new ArgumentNullException("myField"); + + MyField = myField; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([NotNull] out string myField) + { + myField = MyField; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var myField = reader.ReadString(); + var _result = new RdCallRequest(myField); + return _result; + }; + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.MyField); + }; + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((RdCallRequest) obj); + } + public bool Equals(RdCallRequest other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return MyField == other.MyField; + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + MyField.GetHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("RdCallRequest ("); + using (printer.IndentCookie()) { + printer.Print("myField = "); MyField.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + + + /// + ///

Generated from: RdCodonPluginModel.kt:17

+ ///
+ public sealed class RdCallResponse : IPrintable, IEquatable + { + //fields + //public fields + public int MyResult {get; private set;} + + //private fields + //primary constructor + public RdCallResponse( + int myResult + ) + { + MyResult = myResult; + } + //secondary constructor + //deconstruct trait + public void Deconstruct(out int myResult) + { + myResult = MyResult; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var myResult = reader.ReadInt(); + var _result = new RdCallResponse(myResult); + return _result; + }; + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.MyResult); + }; + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((RdCallResponse) obj); + } + public bool Equals(RdCallResponse other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return MyResult == other.MyResult; + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + MyResult.GetHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("RdCallResponse ("); + using (printer.IndentCookie()) { + printer.Print("myResult = "); MyResult.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } +} diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyIconIds.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyIconIds.cs new file mode 100644 index 0000000..d5dd26c --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyIconIds.cs @@ -0,0 +1,9 @@ +using JetBrains.RdBackend.Common.Features.Icons; +using JetBrains.UI.Icons; + +namespace Rider.Plugins.CodonPlugin; + +public static class MyIconIds +{ + public static readonly IconId RiderIconId = new FrontendIconId("icons/rider.svg"); +} diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyRdHost.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyRdHost.cs new file mode 100644 index 0000000..951e3ad --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/MyRdHost.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using JetBrains.Application.Parts; +using JetBrains.Core; +using JetBrains.IDE.UI; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.Rd.Tasks; +using JetBrains.ReSharper.Feature.Services.Protocol; +using JetBrains.Rider.Model; +using Rider.Plugins.CodonPlugin.Model; + +namespace Rider.Plugins.CodonPlugin; + +[SolutionComponent(Instantiation.ContainerAsyncAnyThreadUnsafe)] +public class MyRdHost +{ + private readonly ISolution _solution; + + public MyRdHost(ISolution solution) + { + _solution = solution; + + var model = _solution.GetProtocolSolution().GetRdCodonPluginModel(); + model.MyCall.SetAsync(HandleCall); + model.MyIconCall.SetAsync(HandleIconCall); + } + + private async Task HandleCall(Lifetime lt, RdCallRequest request) + { + await Task.Delay(1000, lt); + return lt.Execute(() => new RdCallResponse(request.MyField.Length)); + } + + private async Task HandleIconCall(Lifetime lt, Unit _) + { + await Task.Delay(1000, lt); + return lt.Execute(() => _solution.GetComponent().Transform(MyIconIds.RiderIconId)); + } +} diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Rider.Plugins.CodonPlugin.csproj b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Rider.Plugins.CodonPlugin.csproj new file mode 100644 index 0000000..383b609 --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/Rider.Plugins.CodonPlugin.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/ZoneMarker.cs b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/ZoneMarker.cs new file mode 100644 index 0000000..efb1c07 --- /dev/null +++ b/Codon.Plugin/src/dotnet/Rider.Plugins.CodonPlugin/ZoneMarker.cs @@ -0,0 +1,12 @@ +using JetBrains.Application.BuildScript.Application.Zones; +using JetBrains.DocumentModel; +using JetBrains.ProjectModel; +using JetBrains.Rider.Model; + +namespace Rider.Plugins.CodonPlugin; + +[ZoneMarker] +public class ZoneMarker + : IRequire, + IRequire, + IRequire; diff --git a/Codon.Plugin/src/dotnet/nuget.config b/Codon.Plugin/src/dotnet/nuget.config new file mode 100644 index 0000000..8a8f79f --- /dev/null +++ b/Codon.Plugin/src/dotnet/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Codon.Plugin/src/rider/generated/kotlin/com/jetbrains/rider/plugins/codonplugin/RdCodonPluginModel.Generated.kt b/Codon.Plugin/src/rider/generated/kotlin/com/jetbrains/rider/plugins/codonplugin/RdCodonPluginModel.Generated.kt new file mode 100644 index 0000000..710eac8 --- /dev/null +++ b/Codon.Plugin/src/rider/generated/kotlin/com/jetbrains/rider/plugins/codonplugin/RdCodonPluginModel.Generated.kt @@ -0,0 +1,219 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package com.jetbrains.rider.plugins.codonplugin.model + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [RdCodonPluginModel.kt:11] + */ +class RdCodonPluginModel private constructor( + private val _myCall: RdCall, + private val _myIconCall: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + val classLoader = javaClass.classLoader + serializers.register(LazyCompanionMarshaller(RdId(-3835427627873167508), classLoader, "com.jetbrains.rider.plugins.codonplugin.model.RdCallRequest")) + serializers.register(LazyCompanionMarshaller(RdId(-8217792021757949180), classLoader, "com.jetbrains.rider.plugins.codonplugin.model.RdCallResponse")) + } + + + + + + const val serializationHash = -2925658527367673754L + + } + override val serializersOwner: ISerializersOwner get() = RdCodonPluginModel + override val serializationHash: Long get() = RdCodonPluginModel.serializationHash + + //fields + + /** + * This is an example protocol call. + */ + val myCall: IRdCall get() = _myCall + + /** + * This is an example protocol call for getting a backend icon. + */ + val myIconCall: IRdCall get() = _myIconCall + //methods + //initializer + init { + bindableChildren.add("myCall" to _myCall) + bindableChildren.add("myIconCall" to _myIconCall) + } + + //secondary constructor + internal constructor( + ) : this( + RdCall(RdCallRequest, RdCallResponse), + RdCall(FrameworkMarshallers.Void, AbstractPolymorphic(com.jetbrains.rd.ide.model.IconModel)) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RdCodonPluginModel (") + printer.indent { + print("myCall = "); _myCall.print(printer); println() + print("myIconCall = "); _myIconCall.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): RdCodonPluginModel { + return RdCodonPluginModel( + _myCall.deepClonePolymorphic(), + _myIconCall.deepClonePolymorphic() + ) + } + //contexts + //threading + override val extThreading: ExtThreadingKind get() = ExtThreadingKind.Default +} +val com.jetbrains.rd.ide.model.Solution.rdCodonPluginModel get() = getOrCreateExtension("rdCodonPluginModel", ::RdCodonPluginModel) + + + +/** + * #### Generated from [RdCodonPluginModel.kt:13] + */ +data class RdCallRequest ( + val myField: String +) : IPrintable { + //write-marshaller + private fun write(ctx: SerializationCtx, buffer: AbstractBuffer) { + buffer.writeString(myField) + } + //companion + + companion object : IMarshaller { + override val _type: KClass = RdCallRequest::class + override val id: RdId get() = RdId(-3835427627873167508) + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): RdCallRequest { + val myField = buffer.readString() + return RdCallRequest(myField) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RdCallRequest) { + value.write(ctx, buffer) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as RdCallRequest + + if (myField != other.myField) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + myField.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RdCallRequest (") + printer.indent { + print("myField = "); myField.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts + //threading +} + + +/** + * #### Generated from [RdCodonPluginModel.kt:17] + */ +data class RdCallResponse ( + val myResult: Int +) : IPrintable { + //write-marshaller + private fun write(ctx: SerializationCtx, buffer: AbstractBuffer) { + buffer.writeInt(myResult) + } + //companion + + companion object : IMarshaller { + override val _type: KClass = RdCallResponse::class + override val id: RdId get() = RdId(-8217792021757949180) + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): RdCallResponse { + val myResult = buffer.readInt() + return RdCallResponse(myResult) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RdCallResponse) { + value.write(ctx, buffer) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as RdCallResponse + + if (myResult != other.myResult) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + myResult.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RdCallResponse (") + printer.indent { + print("myResult = "); myResult.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts + //threading +} diff --git a/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/MyIcons.kt b/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/MyIcons.kt new file mode 100644 index 0000000..168ee9c --- /dev/null +++ b/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/MyIcons.kt @@ -0,0 +1,8 @@ +package com.jetbrains.rider.plugins.codonplugin + +import com.intellij.openapi.util.IconLoader + +object MyIcons { + @JvmField + val RiderIcon = IconLoader.getIcon("icons/rider.svg", javaClass) +} diff --git a/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/ProtocolCaller.kt b/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/ProtocolCaller.kt new file mode 100644 index 0000000..40ce33f --- /dev/null +++ b/Codon.Plugin/src/rider/main/kotlin/com/jetbrains/rider/plugins/codonplugin/ProtocolCaller.kt @@ -0,0 +1,28 @@ +package com.jetbrains.rider.plugins.codonplugin + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.jetbrains.rd.ui.icons.ProtocolIconRegistryService +import com.jetbrains.rider.plugins.codonplugin.model.RdCallRequest +import com.jetbrains.rider.plugins.codonplugin.model.rdCodonPluginModel +import com.jetbrains.rider.projectView.solution +import javax.swing.Icon + +@Service(Service.Level.PROJECT) +class ProtocolCaller(private val project: Project) { + + suspend fun doCall(input: String): Int { + val model = project.solution.rdCodonPluginModel + val request = RdCallRequest(input) + val response = model.myCall.startSuspending(request) + return response.myResult + } + + suspend fun doIconCall(): Icon { + val model = project.solution.rdCodonPluginModel + val response = model.myIconCall.startSuspending(Unit) + return ApplicationManager.getApplication().service().createIcon(response) + } +} diff --git a/Codon.Plugin/src/rider/main/resources/META-INF/plugin.xml b/Codon.Plugin/src/rider/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000..17c4838 --- /dev/null +++ b/Codon.Plugin/src/rider/main/resources/META-INF/plugin.xml @@ -0,0 +1,11 @@ + + com.jetbrains.rider.plugins.codonplugin + Codon + _PLACEHOLDER_ + Synesthesia Dev + com.intellij.modules.rider + + + Rider plugin to generate Codon's codec definitions + + diff --git a/Codon.Plugin/src/rider/main/resources/icons/rider.svg b/Codon.Plugin/src/rider/main/resources/icons/rider.svg new file mode 100644 index 0000000..f873549 --- /dev/null +++ b/Codon.Plugin/src/rider/main/resources/icons/rider.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Codon.Plugin/src/test/kotlin/com/jetbrains/rider/plugins/template/test/cases/MyTestCase.kt b/Codon.Plugin/src/test/kotlin/com/jetbrains/rider/plugins/template/test/cases/MyTestCase.kt new file mode 100644 index 0000000..3dfa98f --- /dev/null +++ b/Codon.Plugin/src/test/kotlin/com/jetbrains/rider/plugins/template/test/cases/MyTestCase.kt @@ -0,0 +1,70 @@ +package com.jetbrains.rider.plugins.codonplugin.test.cases + +import com.intellij.openapi.components.service +import com.jetbrains.rd.platform.diagnostics.RdLogTraceScenarios +import com.jetbrains.rider.plugins.codonplugin.MyIcons +import com.jetbrains.rider.plugins.codonplugin.ProtocolCaller +import com.jetbrains.rider.protocol.protocol +import com.jetbrains.rider.test.OpenSolutionParams +import com.jetbrains.rider.test.annotations.Solution +import com.jetbrains.rider.test.annotations.TestSettings +import com.jetbrains.rider.test.asserts.shouldBe +import com.jetbrains.rider.test.base.PerClassSolutionTestBase +import com.jetbrains.rider.test.enums.BuildTool +import com.jetbrains.rider.test.enums.sdk.SdkVersion +import com.jetbrains.rider.test.facades.solution.RiderSolutionApiFacade +import com.jetbrains.rider.test.facades.solution.SolutionApiFacade +import com.jetbrains.rider.test.scriptingApi.runBlockingWithProtocolPumping +import org.testng.annotations.Test +import java.awt.image.BufferedImage + +@TestSettings(sdkVersion = SdkVersion.AUTODETECT, buildTool = BuildTool.AUTODETECT) +@Solution("MyTestSolution") +class MyTestCase : PerClassSolutionTestBase() { + override val traceScenarios = setOf(RdLogTraceScenarios.Commands) + + override val solutionApiFacade: SolutionApiFacade = object : RiderSolutionApiFacade() { + override fun waitForSolution(params: OpenSolutionParams) { + // This may sometimes take a long time on CI agents. + params.projectModelReadyTimeout = params.projectModelReadyTimeout.multipliedBy(10L) + + return super.waitForSolution(params) + } + } + + @Test + fun protocolCallTest() { + runBlockingWithProtocolPumping(project.protocol, "protocolCallTest") { + val myService = project.service() + val result = myService.doCall("test-string") + result.shouldBe(11) + } + } + + @Test + fun iconCallTest() { + runBlockingWithProtocolPumping(project.protocol, "iconCallTest") { + val myService = project.service() + + val iconFromBackend = myService.doIconCall() + val iconFromFrontend = MyIcons.RiderIcon + + val imageFrontend = BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB) + val graphicsFrontend = imageFrontend.createGraphics() + iconFromFrontend.paintIcon(null, graphicsFrontend, 0, 0) + + val imageBackend = BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB) + val graphicsBackend = imageBackend.createGraphics() + iconFromBackend.paintIcon(null, graphicsBackend, 0, 0) + + for (x in 0.. + + + + + + + diff --git a/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/Class1.cs b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/Class1.cs new file mode 100644 index 0000000..5cec708 --- /dev/null +++ b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/Class1.cs @@ -0,0 +1,6 @@ +namespace MyTestProject; + +public class Class1 +{ + +} diff --git a/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/MyTestProject.csproj b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/MyTestProject.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestProject/MyTestProject.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Codon.Plugin/testData/solutions/MyTestSolution/MyTestSolution.sln b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestSolution.sln new file mode 100644 index 0000000..45df11d --- /dev/null +++ b/Codon.Plugin/testData/solutions/MyTestSolution/MyTestSolution.sln @@ -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}") = "MyTestProject", "MyTestProject\MyTestProject.csproj", "{4E1EC6B2-3BEB-4D00-B152-72380DF814E2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4E1EC6B2-3BEB-4D00-B152-72380DF814E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E1EC6B2-3BEB-4D00-B152-72380DF814E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E1EC6B2-3BEB-4D00-B152-72380DF814E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E1EC6B2-3BEB-4D00-B152-72380DF814E2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Codon.Tests/BinaryCodecsTests.cs b/Codon.Tests/BinaryCodecsTests.cs index e959dc3..e2683a8 100644 --- a/Codon.Tests/BinaryCodecsTests.cs +++ b/Codon.Tests/BinaryCodecsTests.cs @@ -144,7 +144,7 @@ public void Optional_Codec_WritesPresenceAndValue() { var optionalInt = BinaryCodecs.INT.Optional(); - var some = Optional.Of(123); + var some = Optional.Of(123); var bufSome = Unpooled.Buffer(); optionalInt.Write(bufSome, some); var readSome = optionalInt.Read(bufSome); @@ -192,7 +192,7 @@ public void Transformative_Codec_RoundTrip() [Test] public void List_And_Dictionary_Codecs_RoundTrip() { - BinaryCodecDefinitions.ListBinaryCodec listCodec = BinaryCodecs.STRING.List(); + var listCodec = BinaryCodecs.STRING.List(); var list = new List { "a", "b", "c" }; Assert.That(roundTrip(listCodec, list), Is.EqualTo(list)); diff --git a/Codon.Tests/CodecTests.cs b/Codon.Tests/CodecTests.cs index 2c08a22..8391889 100644 --- a/Codon.Tests/CodecTests.cs +++ b/Codon.Tests/CodecTests.cs @@ -29,7 +29,7 @@ public record Car(string Model, List Passengers, Optional Driver [Test] public void TestCodec() { - var person = new Person("Silly Billy", 18, Optional.Of(true)); + var person = new Person("Silly Billy", 18, Optional.Of(true)); var encoded = Person.CODEC.Encode(JsonTranscoder.INSTANCE, person); Console.WriteLine(encoded.GetRawText()); // {"name":"Silly Billy","age":18,"is_awesome":true} diff --git a/Codon.Tests/EdgeCasesTests.cs b/Codon.Tests/EdgeCasesTests.cs index 75057b3..5d005f1 100644 --- a/Codon.Tests/EdgeCasesTests.cs +++ b/Codon.Tests/EdgeCasesTests.cs @@ -3,7 +3,7 @@ using Codon.Codec; using Codon.IniTranscoder.Elements; -using SynesthesiaDev.Synx; +using Codon.Optionals; using SynesthesiaDev.Synx.Codon; namespace Codon.Tests; @@ -11,12 +11,20 @@ namespace Codon.Tests; public class EdgeCasesTests { private readonly Codecs.OptionalCodec optionalString = new(Codecs.STRING); - private record TestingClass(string TestString, TestEnummm? Enuming) + private record TestingClass(string TestString, TestEnummm? Enuming, Thing? Thing) { public static readonly StructCodec CODEC = StructCodec.For() .Field("TestString", Codecs.STRING, t => t.TestString) .Field("Enuming", Codecs.Enum().Optional(), t => t.Enuming.ToOptional()) - .Build((s, e) => new TestingClass(s ,e.Value)); + .Field("Thing", Thing.CODEC.Optional(), t => t.Thing.ToOptional()) + .Build((s, e, t) => new TestingClass(s ,e.ToNullableStruct(), t.ToNullableClass())); + } + + public record Thing(string Name) + { + public static readonly Codec CODEC = StructCodec.For() + .Field("Name", Codecs.STRING, t => t.Name) + .Build(n => new Thing(n)); } private enum TestEnummm @@ -39,9 +47,10 @@ public void Test() [Test] public void TestEnum() { - var encoded = TestingClass.CODEC.Encode(SynxTranscoder.INSTANCE, new TestingClass("yo", null)); + var encoded = TestingClass.CODEC.Encode(SynxTranscoder.INSTANCE, new TestingClass("yo", null, null)); var decoded = TestingClass.CODEC.Decode(SynxTranscoder.INSTANCE, encoded); Assert.That(decoded.Enuming, Is.Null); + Assert.That(decoded.Thing, Is.Null); } } diff --git a/Codon.Tests/OptionalAndDefaultCodecTests.cs b/Codon.Tests/OptionalAndDefaultCodecTests.cs index a7e2a66..1f9e590 100644 --- a/Codon.Tests/OptionalAndDefaultCodecTests.cs +++ b/Codon.Tests/OptionalAndDefaultCodecTests.cs @@ -12,7 +12,7 @@ public class OptionalAndDefaultCodecTests public void Optional_Present_RoundTrip() { var codec = Codecs.INT.Optional(); - var value = Optional.Of(42); + var value = Optional.Of(42); var encoded = codec.Encode(t, value); var decoded = codec.Decode(t, encoded); Assert.That(decoded.IsPresent, Is.True); diff --git a/Codon.Tests/VersionCodecTests.cs b/Codon.Tests/VersionCodecTests.cs index fa623c2..ec45a71 100644 --- a/Codon.Tests/VersionCodecTests.cs +++ b/Codon.Tests/VersionCodecTests.cs @@ -117,7 +117,7 @@ public void Decode_WhenMigrationIsMissing_ThrowsKeyNotFoundException() [Test] public void Encode_AlwaysAddsSchemaVersion_AndRoundTripsWithDecode() { - var original = new Person("Synesthesia Dev", 123, Optional.Of(true)); + var original = new Person("Synesthesia Dev", 123, Optional.Of(true)); var encoded = Person.VERSIONED_CODEC.Encode(JsonTranscoder.INSTANCE, original); diff --git a/Codon.sln b/Codon.sln index 3844959..df92298 100644 --- a/Codon.sln +++ b/Codon.sln @@ -42,5 +42,17 @@ Global {5797D492-E412-4EA2-8E05-A3F404276A9A}.Debug|Any CPU.Build.0 = Debug|Any CPU {5797D492-E412-4EA2-8E05-A3F404276A9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {5797D492-E412-4EA2-8E05-A3F404276A9A}.Release|Any CPU.Build.0 = Release|Any CPU + {812997EE-A4A5-48BE-A34B-3E246E5F9D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {812997EE-A4A5-48BE-A34B-3E246E5F9D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {812997EE-A4A5-48BE-A34B-3E246E5F9D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {812997EE-A4A5-48BE-A34B-3E246E5F9D5A}.Release|Any CPU.Build.0 = Release|Any CPU + {B194AD0A-36CC-49F0-94ED-420C756DE128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B194AD0A-36CC-49F0-94ED-420C756DE128}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B194AD0A-36CC-49F0-94ED-420C756DE128}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B194AD0A-36CC-49F0-94ED-420C756DE128}.Release|Any CPU.Build.0 = Release|Any CPU + {BE187904-202A-4264-B5A3-BC342A8AD5D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE187904-202A-4264-B5A3-BC342A8AD5D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE187904-202A-4264-B5A3-BC342A8AD5D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE187904-202A-4264-B5A3-BC342A8AD5D9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal