Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ env:

jobs:
build:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
runs-on: windows-latest

steps:
- name: Checkout
Expand All @@ -45,6 +48,17 @@ jobs:
- name: ${{ env.SLN }} ${{ env.CONFIGURATION }} Build
run: dotnet build ${{ env.SLN }} --no-restore --configuration ${{ env.CONFIGURATION }} -p:GITHUB_ACTIONS=true

- name: ${{ env.SLN }} ${{ env.CONFIGURATION }} Test
run: dotnet test ${{ env.SLN }} --no-restore --no-build --configuration ${{ env.CONFIGURATION }} --logger trx --results-directory '$(Agent.TempDirectory)/TestResults'

- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action/composite@v2
if: always()
with:
files: ${{ '$(Agent.TempDirectory)/TestResults/*.trx' }}
comment_mode: off


- name: Publish Artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
8 changes: 8 additions & 0 deletions OpenApiLINQPadDriver.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 17.7.34024.191
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiLINQPadDriver", "OpenApiLINQPadDriver\OpenApiLINQPadDriver.csproj", "{6CB2FF6D-F4D8-4477-9549-562D54856796}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiLINQPadDriverTests", "Tests\OpenApiLINQPadDriverTests\OpenApiLINQPadDriverTests.csproj", "{A02D0072-0870-4C52-BF0C-FCB6B3546A79}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug_Publish_To_LINQPad_Folder|Any CPU = Debug_Publish_To_LINQPad_Folder|Any CPU
Expand All @@ -18,6 +20,12 @@ Global
{6CB2FF6D-F4D8-4477-9549-562D54856796}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CB2FF6D-F4D8-4477-9549-562D54856796}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CB2FF6D-F4D8-4477-9549-562D54856796}.Release|Any CPU.Build.0 = Release|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Debug_Publish_To_LINQPad_Folder|Any CPU.ActiveCfg = Debug|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Debug_Publish_To_LINQPad_Folder|Any CPU.Build.0 = Debug|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A02D0072-0870-4C52-BF0C-FCB6B3546A79}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
3 changes: 3 additions & 0 deletions OpenApiLINQPadDriver.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/SuppressUninitializedWarningFix/Enabled/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deps/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
11 changes: 1 addition & 10 deletions OpenApiLINQPadDriver/Compilation/CompilationOutput.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
namespace OpenApiLINQPadDriver.Compilation;
internal sealed class CompilationOutput
{
public string[] References { get; }

public bool Successful { get; }

public string[] Errors { get; }

public string[] Warnings { get; }

public string? SourceCodeAroundFirstError { get; }

public CompilationOutput(string[] errors, string[] warnings, string[] references, string? sourceCodeAroundFirstError = null)
public CompilationOutput(string[] errors, string[] warnings)
{
Successful = errors.Length == 0;
Errors = errors;
Warnings = warnings;
References = references;
SourceCodeAroundFirstError = sourceCodeAroundFirstError;
}
}
41 changes: 18 additions & 23 deletions OpenApiLINQPadDriver/Compilation/RoslynHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ internal static class RoslynHelper
{
{ "CS1591", ReportDiagnostic.Suppress } //Missing XML comment for publicly visible type or member 'Type_or_Member'
};
public static CompilationOutput CompileSource(CompilationInput input, bool buildInRelease)
public static CompilationOutput CompileSource(CompilationInput input, bool buildInRelease, Action<string> logAction)
{
var optimization = buildInRelease ? OptimizationLevel.Release : OptimizationLevel.Debug;
var csharpParseOptions = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Diagnose);
var syntaxTrees = input.SourceCode
// https://forum.linqpad.net/discussion/3002/linqpad-seems-to-ignore-and-tags-in-documentation-comments#latest
.Select(source => source.Replace("<remarks>", "<summary>").Replace("</remarks>", "</summary>"))
.Select(source => CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Diagnose))).ToArray();
.Select(source => CSharpSyntaxTree.ParseText(source, csharpParseOptions))
.ToArray();

var executableReferences = input.FilePathsToReference.Select(fileReference => MetadataReference.CreateFromFile(fileReference));
logAction("Parsing code");

var executableReferences = input.FilePathsToReference.Select(fileReference => MetadataReference.CreateFromFile(fileReference)).ToArray();

var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(input.OutputPath);

Expand All @@ -39,17 +43,25 @@ public static CompilationOutput CompileSource(CompilationInput input, bool build
if (errorsFromCompilation.Length > 0)
{
var diagnosticsFromCompilation = diagnostics.GetWarnings();
var sourceCodeAroundFirstError = GetSurroundingSource(diagnostics.First(d => d.Severity == DiagnosticSeverity.Error).Location);
var compilationOutput = new CompilationOutput(errorsFromCompilation, diagnosticsFromCompilation, input.FilePathsToReference, sourceCodeAroundFirstError);
var compilationOutput = new CompilationOutput(errorsFromCompilation, diagnosticsFromCompilation);

logAction("Getting initial diagnostics");

return compilationOutput;
}

logAction("Getting initial diagnostics");

var emitResult = csharpCompilation.Emit(input.OutputPath, xmlDocPath: GetXmlDocumentFileNameWithPath(input.OutputPath, fileNameWithoutExtension));

logAction("Emitting dlls");

var errors = emitResult.Diagnostics.GetErrors();
var warnings = emitResult.Diagnostics.GetWarnings();

var output = new CompilationOutput(errors, warnings, input.FilePathsToReference);
logAction("Reading diagnostics from emit result");

var output = new CompilationOutput(errors, warnings);

return output;

Expand All @@ -60,21 +72,4 @@ static string GetXmlDocumentFileNameWithPath(string outputPath, string fileNameW
return Path.Join(dllDirectory, fileNameWithoutExtension) + ".xml";
}
}

private static string? GetSurroundingSource(Location? location)
{
if (location == null || !location.IsInSource)
return null;

var str = location.SourceTree.ToString();
var mappedLineSpan = location.GetMappedLineSpan();
var startLinePosition = mappedLineSpan.StartLinePosition;
var line = startLinePosition.Line;
if (line < 0)
return null;
var source = str.Split('\n');
var count = Math.Max(0, line - 5);
var num = Math.Min(source.Length - 1, line + 5);
return string.Concat(source.Skip(count).Take(line - count).Select(l => l.Replace("\r", string.Empty) + "\r\n")) + "===>" + source[line].Replace("\r", string.Empty) + "<===" + string.Concat(source.Skip(line + 1).Take(num - line).Select(l => "\r\n" + l.Replace("\r", string.Empty)));
}
}
5 changes: 2 additions & 3 deletions OpenApiLINQPadDriver/ConnectionDialog.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
Expand Down Expand Up @@ -109,7 +108,7 @@ private async void DownloadApiUriHyperlink_OnClick(object sender, RoutedEventArg
{
var document = await OpenApiDocumentHelper.GetFromUriAsync(new Uri(properties.OpenApiDocumentUri!), properties.OpenApiFormat).ConfigureAwait(false);

var firstServerOrNull = document.Servers.FirstOrDefault();
var firstServerOrNull = document.Servers?.FirstOrDefault();
if (firstServerOrNull != null)
{
properties.ApiUri = firstServerOrNull.Url;
Expand All @@ -118,7 +117,7 @@ private async void DownloadApiUriHyperlink_OnClick(object sender, RoutedEventArg
}
else
{
MessageBox.Show("This Open API documentation does not contain server address, please input it manually", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
MessageBox.Show("This Open API documentation does not contain any server address, please input it manually", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
catch (Exception ex)
Expand Down
14 changes: 10 additions & 4 deletions OpenApiLINQPadDriver/OpenApiLINQPadDriver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<Description>$(AssemblyTitle).</Description>
</PropertyGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>OpenApiLINQPadDriverTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
Expand Down Expand Up @@ -65,10 +71,10 @@
<PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="NJsonSchema.CodeGeneration" Version="11.0.0" />
<PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="11.0.0" />
<PackageReference Include="NSwag.CodeGeneration" Version="14.0.1" />
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.0.1" />
<PackageReference Include="NSwag.Core" Version="14.0.1" />
<PackageReference Include="NSwag.Core.Yaml" Version="14.0.1" />
<PackageReference Include="NSwag.CodeGeneration" Version="14.0.3" />
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.0.3" />
<PackageReference Include="NSwag.Core" Version="14.0.3" />
<PackageReference Include="NSwag.Core.Yaml" Version="14.0.3" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageReference Include="Prism.Core" Version="8.1.97" />
</ItemGroup>
Expand Down
52 changes: 44 additions & 8 deletions OpenApiLINQPadDriver/SchemaBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using Microsoft.CodeAnalysis.CSharp;
using Newtonsoft.Json;
using NJsonSchema;
using NJsonSchema.CodeGeneration.CSharp;
using NSwag.CodeGeneration.CSharp;
using NSwag.CodeGeneration.OperationNameGenerators;
Expand Down Expand Up @@ -47,6 +50,7 @@ internal static List<ExplorerItem> GetSchemaAndBuildAssembly(OpenApiContextDrive

MeasureTimeAndAddTimeExecutionExplorerItem("Generating NSwag classes");

//possibly this switch should be an if based on SupportsMultipleClients?
var clientSourceCode = endpointGrouping switch
{
EndpointGrouping.SingleClientFromOperationIdOperationName => ClientGenerator.SingleClientFromOperationIdOperationNameGenerator(mainContextType),
Expand All @@ -69,9 +73,7 @@ internal static List<ExplorerItem> GetSchemaAndBuildAssembly(OpenApiContextDrive
FilePathsToReference = references,
OutputPath = assemblyPath,
SourceCode = [codeGeneratedByNSwag, clientSourceCode]
}, driverProperties.BuildInRelease);

MeasureTimeAndAddTimeExecutionExplorerItem("Compiling code");
}, driverProperties.BuildInRelease, MeasureTimeAndAddTimeExecutionExplorerItem);

var explorerItems = new List<ExplorerItem>();

Expand Down Expand Up @@ -130,21 +132,25 @@ void MeasureTimeAndAddTimeExecutionExplorerItem(string name)
var elapsed = stopWatch.Elapsed;
stopWatch.Restart();
#endif
// File.AppendAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "log.txt"), name + " " + elapsed + Environment.NewLine);
timeExplorerItem.Children.Add(ExplorerItemHelper.CreateForElapsedTime(name, elapsed));
}
}

private static CSharpClientGeneratorSettings CreateCsharpClientGeneratorSettings(EndpointGrouping endpointGrouping, JsonLibrary jsonLibrary, ClassStyle classStyle, bool generateSyncMethods,
private static CustomCSharpClientGeneratorSettings CreateCsharpClientGeneratorSettings(EndpointGrouping endpointGrouping, JsonLibrary jsonLibrary, ClassStyle classStyle, bool generateSyncMethods,
TypeDescriptor type)
{
var (operationNameGenerator, className) = endpointGrouping switch
{
EndpointGrouping.MultipleClientsFromFirstTagAndOperationName => ((IOperationNameGenerator)new MultipleClientsFromFirstTagAndOperationNameGenerator(), "{controller}" + ClientPostFix),
EndpointGrouping.SingleClientFromOperationIdOperationName => (new SingleClientFromOperationIdOperationNameGenerator(), type.Name),
EndpointGrouping.MultipleClientsFromFirstTagAndOperationName
=> ((IOperationNameGenerator)new MultipleClientsFromFirstTagAndOperationNameGenerator(), "{controller}" + ClientPostFix),

EndpointGrouping.SingleClientFromOperationIdOperationName
=> (new SingleClientFromOperationIdOperationNameGenerator(), type.Name),
_ => throw new InvalidOperationException()
};

var settings = new CSharpClientGeneratorSettings
var settings = new CustomCSharpClientGeneratorSettings
{
GenerateClientClasses = true,
GenerateOptionalParameters = true,
Expand All @@ -159,8 +165,38 @@ private static CSharpClientGeneratorSettings CreateCsharpClientGeneratorSettings
UseHttpClientCreationMethod = false,
DisposeHttpClient = true,
GenerateSyncMethods = generateSyncMethods,
GeneratePrepareRequestAndProcessResponseAsAsyncMethods = false,
GeneratePrepareRequestAndProcessResponseAsAsyncMethods = false
};
return settings;
}

private sealed class CustomCSharpClientGeneratorSettings : CSharpClientGeneratorSettings
{
public override string GenerateControllerName(string controllerName)
{
var convertedToUpperCaseUsingNSwagLib = ConversionUtilities.ConvertToUpperCamelCase(controllerName, false);
var escapedToBeValidIdentifier = EscapeCSharpIdentifier(convertedToUpperCaseUsingNSwagLib);
return ClassName.Replace("{controller}", escapedToBeValidIdentifier);
}

//https://github.com/icsharpcode/CodeConverter/blob/3284c3d228040fc4d0ea9c5a05129b1b2c0fc858/CodeConverter/CSharp/CommonConversions.cs#L342
private static string EscapeCSharpIdentifier(string text)
{
if(string.IsNullOrEmpty(text))
return text;

if (!SyntaxFacts.IsValidIdentifier(text))
{
text = new string(text.Where(SyntaxFacts.IsIdentifierPartCharacter).ToArray());
if (!SyntaxFacts.IsIdentifierStartCharacter(text[0]))
text = "a" + text;
}

//not needed because we concat this name with 'Client' postfix, but if we end up ditching it, it may be useful
if (SyntaxFacts.GetKeywordKind(text) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(text) != SyntaxKind.None)
text = "@" + text;

return text;
}
}
}
Loading