From 5ef193c3130dd28182ed6b3f774cc2f77a540692 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 23 Nov 2024 11:22:30 +0200 Subject: [PATCH] Fix string usage problems reported by analyzers (#5050) --- Directory.Build.props | 13 +----------- .../ResponseTypeAttribute.cs | 5 +++-- .../SwaggerResponseAttribute.cs | 5 +++-- .../Middlewares/OpenApiDocumentMiddleware.cs | 2 +- .../NSwag.AspNet.Owin.csproj | 1 + .../Middlewares/OpenApiDocumentMiddleware.cs | 6 +++--- src/NSwag.AspNetCore/NSwag.AspNetCore.csproj | 4 ++++ src/NSwag.AspNetCore/SwaggerUiSettingsBase.cs | 1 + .../Models/CSharpParameterModel.cs | 4 ++-- .../NSwag.CodeGeneration.CSharp.csproj | 2 ++ .../ClientGeneratorBase.cs | 2 +- .../Models/OperationModelBase.cs | 2 +- .../Document/ExecuteDocumentCommand.cs | 2 +- .../AspNetCore/AspNetCoreToOpenApiCommand.cs | 2 +- .../Commands/Generation/AspNetCore/Exe.cs | 10 ++++----- .../Commands/OutputCommandBase.cs | 6 +++--- src/NSwag.Commands/NSwag.Commands.csproj | 4 ++++ src/NSwag.Commands/NSwagDocument.cs | 10 ++++----- src/NSwag.Commands/NSwagDocumentBase.cs | 12 +++++++---- src/NSwag.Commands/PathUtilities.cs | 8 +++---- .../Collections/ObservableDictionary.cs | 10 ++++----- src/NSwag.Core/HttpUtilities.cs | 2 +- .../OpenApiDocument.Serialization.cs | 2 +- src/NSwag.Core/OpenApiDocument.cs | 13 ++++++------ src/NSwag.Core/Polyfills.cs | 10 +++++++++ .../AspNetCoreOpenApiDocumentGenerator.cs | 13 ++++++------ .../NSwag.Generation.AspNetCore.csproj | 5 +++++ .../Processors/OperationParameterProcessor.cs | 6 +++--- .../Processors/OperationResponseProcessor.cs | 2 +- .../NSwag.Generation.WebApi.csproj | 4 ++++ .../Processors/OperationParameterProcessor.cs | 4 ++-- .../WebApiOpenApiDocumentGenerator.cs | 21 ++++++++++--------- .../OperationResponseProcessorBase.cs | 2 +- .../Processors/OperationTagsProcessor.cs | 2 +- 34 files changed, 112 insertions(+), 85 deletions(-) create mode 100644 src/NSwag.Core/Polyfills.cs diff --git a/Directory.Build.props b/Directory.Build.props index 5b7517a480..7f943cb1a6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -72,32 +72,21 @@ [IDE0290] Use primary constructor [IDE0330] Use 'System.Threading.Lock' [CA1200] Avoid using cref tags with a prefix - [CA1304] The behavior of 'string.ToUpper()' could vary based on the current user's locale settings - [CA1305] The behavior of 'int.ToString()' could vary based on the current user's locale settings - [CA1310] The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings - [CA1311] The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings - [CA1507] Use nameof in place of string literal [CA1510] Use 'ArgumentNullException.ThrowIfNull' instead of explicitly throwing a new exception instance [CA1514] 'System.ReadOnlySpan.Slice(int, int)' uses a redundant length calculation that can be removed [CA1710] Rename to end in either 'Dictionary' or 'Collection' [CA1716] rename parameter property so that it no longer conflicts with the reserved language keyword [CA1720] Identifier 'xxx' contains type name [CA1725] Overriden parameter name mismatch - [CA1834] Use 'StringBuilder.Append(char)' instead of 'StringBuilder.Append(string)' when the input is a constant unit string [CA1845] Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - [CA1847] Use 'string.Contains(char)' instead of 'string.Contains(string)' - needs polyfill [CA1861] Prefer 'static readonly' fields over constant array arguments - [CA1862] Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison - [CA1865] Use 'string.StartsWith(char)' instead of 'string.StartsWith(string)' - needs polyfill - [CA1866] Use 'string.StartsWith(char)' instead of 'string.StartsWith(string)' - needs polyfill [CA1870] Use a cached 'SearchValues' instance for improved searching performance - [CA2249] Use 'string.Contains' instead of 'string.IndexOf' to improve readability - needs polyfill [CA2263] Prefer the generic overload 'System.Enum.GetValues()' [SYSLIB0012] 'Assembly.CodeBase' is obsolete --> $(NoWarn);IDE0005;IDE0008;IDE0017;IDE0021;IDE0022;IDE0025;IDE0027;IDE0029;IDE0032;IDE0039;IDE0045;IDE0046;IDE0055;IDE0056;IDE0057;IDE0060;IDE0061;IDE0090;IDE0130;IDE0160;IDE0200;IDE0270;IDE0290;IDE0330 - $(NoWarn);CA1200;CA1304;CA1305;CA1310;CA1311;CA1507;CA1510;CA1514;CA1710;CA1716;CA1720;CA1725;CA1834;CA1845;CA1847;CA1861;CA1862;CA1865;CA1866;CA1870;CA2249;CA2263;SYSLIB0012 + $(NoWarn);CA1200;CA1510;CA1514;CA1710;CA1716;CA1720;CA1725;CA1845;CA1861;CA1870;CA2263;SYSLIB0012 diff --git a/src/NSwag.Annotations/ResponseTypeAttribute.cs b/src/NSwag.Annotations/ResponseTypeAttribute.cs index a585fb2320..5b50ae192e 100644 --- a/src/NSwag.Annotations/ResponseTypeAttribute.cs +++ b/src/NSwag.Annotations/ResponseTypeAttribute.cs @@ -6,6 +6,7 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Globalization; using System.Net; namespace NSwag.Annotations @@ -37,7 +38,7 @@ public ResponseTypeAttribute(string httpStatusCode, Type responseType) /// The JSON result type of the MVC or Web API action method. public ResponseTypeAttribute(int httpStatusCode, Type responseType) { - HttpStatusCode = httpStatusCode.ToString(); + HttpStatusCode = httpStatusCode.ToString(CultureInfo.InvariantCulture); ResponseType = responseType; } @@ -46,7 +47,7 @@ public ResponseTypeAttribute(int httpStatusCode, Type responseType) /// The JSON result type of the MVC or Web API action method. public ResponseTypeAttribute(HttpStatusCode httpStatusCode, Type responseType) { - HttpStatusCode = ((int)httpStatusCode).ToString(); + HttpStatusCode = ((int)httpStatusCode).ToString(CultureInfo.InvariantCulture); ResponseType = responseType; } diff --git a/src/NSwag.Annotations/SwaggerResponseAttribute.cs b/src/NSwag.Annotations/SwaggerResponseAttribute.cs index a85a04981c..f498a1de65 100644 --- a/src/NSwag.Annotations/SwaggerResponseAttribute.cs +++ b/src/NSwag.Annotations/SwaggerResponseAttribute.cs @@ -6,6 +6,7 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Globalization; using System.Net; namespace NSwag.Annotations @@ -37,7 +38,7 @@ public SwaggerResponseAttribute(string httpStatusCode, Type responseType) /// The JSON result type of the MVC or Web API action method. public SwaggerResponseAttribute(int httpStatusCode, Type responseType) { - StatusCode = httpStatusCode.ToString(); + StatusCode = httpStatusCode.ToString(CultureInfo.InvariantCulture); Type = responseType; } @@ -46,7 +47,7 @@ public SwaggerResponseAttribute(int httpStatusCode, Type responseType) /// The JSON result type of the MVC or Web API action method. public SwaggerResponseAttribute(HttpStatusCode httpStatusCode, Type responseType) { - StatusCode = ((int)httpStatusCode).ToString(); + StatusCode = ((int)httpStatusCode).ToString(CultureInfo.InvariantCulture); Type = responseType; } diff --git a/src/NSwag.AspNet.Owin/Middlewares/OpenApiDocumentMiddleware.cs b/src/NSwag.AspNet.Owin/Middlewares/OpenApiDocumentMiddleware.cs index a0d50780e0..9bcb3f416d 100644 --- a/src/NSwag.AspNet.Owin/Middlewares/OpenApiDocumentMiddleware.cs +++ b/src/NSwag.AspNet.Owin/Middlewares/OpenApiDocumentMiddleware.cs @@ -31,7 +31,7 @@ public class OpenApiDocumentMiddleware : OwinMiddleware public OpenApiDocumentMiddleware(OwinMiddleware next, string path, IEnumerable controllerTypes, SwaggerSettings settings) : base(next) { - _path = path.StartsWith("/") ? path : '/' + path; + _path = path.StartsWith('/') ? path : '/' + path; _controllerTypes = controllerTypes; _settings = settings; } diff --git a/src/NSwag.AspNet.Owin/NSwag.AspNet.Owin.csproj b/src/NSwag.AspNet.Owin/NSwag.AspNet.Owin.csproj index c137a8b05f..944d488bd8 100644 --- a/src/NSwag.AspNet.Owin/NSwag.AspNet.Owin.csproj +++ b/src/NSwag.AspNet.Owin/NSwag.AspNet.Owin.csproj @@ -17,6 +17,7 @@ + diff --git a/src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs b/src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs index 4d56614aca..875fa0788e 100644 --- a/src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs +++ b/src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs @@ -39,7 +39,7 @@ public OpenApiDocumentMiddleware(RequestDelegate nextDelegate, IServiceProvider _nextDelegate = nextDelegate; _documentName = documentName; - _path = path.StartsWith("/") ? path : '/' + path; + _path = path.StartsWith('/') ? path : '/' + path; _apiDescriptionGroupCollectionProvider = serviceProvider.GetService() ?? throw new InvalidOperationException("API Explorer not registered in DI."); @@ -58,7 +58,7 @@ public async Task Invoke(HttpContext context) { var schemaJson = await GetDocumentAsync(context); context.Response.StatusCode = 200; - context.Response.Headers["Content-Type"] = _path.IndexOf(".yaml", StringComparison.OrdinalIgnoreCase) >= 0 ? + context.Response.Headers["Content-Type"] = _path.Contains(".yaml", StringComparison.OrdinalIgnoreCase) ? "application/yaml; charset=utf-8" : "application/json; charset=utf-8"; @@ -99,7 +99,7 @@ protected virtual async Task GetDocumentAsync(HttpContext context) try { var openApiDocument = await GenerateDocumentAsync(context); - var data = _path.IndexOf(".yaml", StringComparison.OrdinalIgnoreCase) >= 0 ? + var data = _path.Contains(".yaml", StringComparison.OrdinalIgnoreCase) ? OpenApiYamlDocument.ToYaml(openApiDocument) : openApiDocument.ToJson(); diff --git a/src/NSwag.AspNetCore/NSwag.AspNetCore.csproj b/src/NSwag.AspNetCore/NSwag.AspNetCore.csproj index e8bab7f7d2..9958b22194 100644 --- a/src/NSwag.AspNetCore/NSwag.AspNetCore.csproj +++ b/src/NSwag.AspNetCore/NSwag.AspNetCore.csproj @@ -54,6 +54,10 @@ + + + + diff --git a/src/NSwag.AspNetCore/SwaggerUiSettingsBase.cs b/src/NSwag.AspNetCore/SwaggerUiSettingsBase.cs index 71fe133810..721ab22cf0 100644 --- a/src/NSwag.AspNetCore/SwaggerUiSettingsBase.cs +++ b/src/NSwag.AspNetCore/SwaggerUiSettingsBase.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- #pragma warning disable IDE0005 +#pragma warning disable CA1305 using NSwag.Generation; using Newtonsoft.Json; diff --git a/src/NSwag.CodeGeneration.CSharp/Models/CSharpParameterModel.cs b/src/NSwag.CodeGeneration.CSharp/Models/CSharpParameterModel.cs index b71ca9c4aa..ea6b4b1842 100644 --- a/src/NSwag.CodeGeneration.CSharp/Models/CSharpParameterModel.cs +++ b/src/NSwag.CodeGeneration.CSharp/Models/CSharpParameterModel.cs @@ -40,10 +40,10 @@ public CSharpParameterModel( } /// Gets a value indicating whether the type is a Nullable<>. - public bool IsSystemNullable => Type.EndsWith("?"); + public bool IsSystemNullable => Type.EndsWith('?'); /// Gets the type of the parameter when used in a controller interface where we can set default values before calling. - public string TypeInControllerInterface => HasDefault ? Type.EndsWith("?") ? Type.Substring(0, Type.Length - 1) : Type : Type; + public string TypeInControllerInterface => HasDefault ? Type.EndsWith('?') ? Type.Substring(0, Type.Length - 1) : Type : Type; /// Gets a value indicating whether the parameter name is a valid CSharp identifier. public bool IsValidIdentifier => Name.Equals(VariableName, StringComparison.OrdinalIgnoreCase); diff --git a/src/NSwag.CodeGeneration.CSharp/NSwag.CodeGeneration.CSharp.csproj b/src/NSwag.CodeGeneration.CSharp/NSwag.CodeGeneration.CSharp.csproj index be24928503..ddfe5057c7 100644 --- a/src/NSwag.CodeGeneration.CSharp/NSwag.CodeGeneration.CSharp.csproj +++ b/src/NSwag.CodeGeneration.CSharp/NSwag.CodeGeneration.CSharp.csproj @@ -6,6 +6,7 @@ + @@ -15,4 +16,5 @@ + \ No newline at end of file diff --git a/src/NSwag.CodeGeneration/ClientGeneratorBase.cs b/src/NSwag.CodeGeneration/ClientGeneratorBase.cs index ec2c2e7472..eccc67c2dd 100644 --- a/src/NSwag.CodeGeneration/ClientGeneratorBase.cs +++ b/src/NSwag.CodeGeneration/ClientGeneratorBase.cs @@ -170,7 +170,7 @@ private List GetOperations(OpenApiDocument document) operationName = operationName.Replace(".", "_"); } - if (operationName.EndsWith("Async")) + if (operationName.EndsWith("Async", StringComparison.Ordinal)) { operationName = operationName.Substring(0, operationName.Length - "Async".Length); } diff --git a/src/NSwag.CodeGeneration/Models/OperationModelBase.cs b/src/NSwag.CodeGeneration/Models/OperationModelBase.cs index f8a2d14845..34f7232f1c 100644 --- a/src/NSwag.CodeGeneration/Models/OperationModelBase.cs +++ b/src/NSwag.CodeGeneration/Models/OperationModelBase.cs @@ -202,7 +202,7 @@ public TParameterModel ContentParameter public IEnumerable HeaderParameters => Parameters.Where(p => p.Kind == OpenApiParameterKind.Header); /// Gets or sets a value indicating whether the accept header is defined in a parameter. - public bool HasAcceptHeaderParameterParameter => HeaderParameters.Any(p => p.Name.ToLowerInvariant() == "accept"); + public bool HasAcceptHeaderParameterParameter => HeaderParameters.Any(p => p.Name.Equals("accept", StringComparison.OrdinalIgnoreCase)); /// Gets a value indicating whether the operation has form parameters. public bool HasFormParameters => Parameters.Any(p => p.Kind == OpenApiParameterKind.FormData); diff --git a/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs b/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs index 5bde415d9e..c7f485993b 100644 --- a/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs +++ b/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs @@ -24,7 +24,7 @@ public class ExecuteDocumentCommand : IConsoleCommand public async Task RunAsync(CommandLineProcessor processor, IConsoleHost host) { // input can be nix-like file path starting with / - if (!string.IsNullOrEmpty(Input) && (!Input.StartsWith("/") || File.Exists(Input) || Input.EndsWith("nswag.json")) && !Input.StartsWith("-")) + if (!string.IsNullOrEmpty(Input) && (!Input.StartsWith('/') || File.Exists(Input) || Input.EndsWith("nswag.json", StringComparison.OrdinalIgnoreCase)) && !Input.StartsWith('-')) { await ExecuteDocumentAsync(host, Input); } diff --git a/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs b/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs index f2b944b69a..555a66ce46 100644 --- a/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs +++ b/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs @@ -141,7 +141,7 @@ public override async Task RunAsync(CommandLineProcessor processor, ICon } if (projectMetadata.TargetFrameworkIdentifier == ".NETCoreApp" || - projectMetadata.TargetFrameworkIdentifier.StartsWith("net")) + projectMetadata.TargetFrameworkIdentifier.StartsWith("net", StringComparison.Ordinal)) { executable = "dotnet"; args.Add("exec"); diff --git a/src/NSwag.Commands/Commands/Generation/AspNetCore/Exe.cs b/src/NSwag.Commands/Commands/Generation/AspNetCore/Exe.cs index 6255bb734d..ada1859b0c 100644 --- a/src/NSwag.Commands/Commands/Generation/AspNetCore/Exe.cs +++ b/src/NSwag.Commands/Commands/Generation/AspNetCore/Exe.cs @@ -78,17 +78,17 @@ private static string ToArguments(IReadOnlyList args) var argument = args[i]; if (i != 0) { - builder.Append(" "); + builder.Append(' '); } - if (argument.IndexOf(' ') == -1) + if (!argument.Contains(' ')) { builder.Append(args[i]); continue; } - builder.Append("\""); + builder.Append('"'); var pendingBackslashs = 0; for (var j = 0; j < argument.Length; j++) @@ -113,7 +113,7 @@ private static string ToArguments(IReadOnlyList args) { if (pendingBackslashs == 1) { - builder.Append("\\"); + builder.Append('\\'); } else { @@ -133,7 +133,7 @@ private static string ToArguments(IReadOnlyList args) builder.Append('\\', pendingBackslashs * 2); } - builder.Append("\""); + builder.Append('"'); } return builder.ToString(); diff --git a/src/NSwag.Commands/Commands/OutputCommandBase.cs b/src/NSwag.Commands/Commands/OutputCommandBase.cs index 2c452de6c8..86e9b36b82 100644 --- a/src/NSwag.Commands/Commands/OutputCommandBase.cs +++ b/src/NSwag.Commands/Commands/OutputCommandBase.cs @@ -27,7 +27,7 @@ protected static Task ReadSwaggerDocumentAsync(string input) { if (!IsJson(input) && !IsYaml(input)) { - if (input.StartsWith("http://") || input.StartsWith("https://")) + if (input.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || input.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { if (input.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) || input.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)) @@ -67,12 +67,12 @@ protected static Task ReadSwaggerDocumentAsync(string input) protected static bool IsJson(string data) { - return data.StartsWith("{"); + return data.StartsWith('{'); } protected static bool IsYaml(string data) { - return !IsJson(data) && data.Contains("\n"); + return !IsJson(data) && data.Contains('\n'); } protected Task TryWriteFileOutputAsync(IConsoleHost host, Func generator) diff --git a/src/NSwag.Commands/NSwag.Commands.csproj b/src/NSwag.Commands/NSwag.Commands.csproj index ac9c285766..0142c8b67a 100644 --- a/src/NSwag.Commands/NSwag.Commands.csproj +++ b/src/NSwag.Commands/NSwag.Commands.csproj @@ -45,4 +45,8 @@ + + + + diff --git a/src/NSwag.Commands/NSwagDocument.cs b/src/NSwag.Commands/NSwagDocument.cs index c024fdafd3..cbdab1cd65 100644 --- a/src/NSwag.Commands/NSwagDocument.cs +++ b/src/NSwag.Commands/NSwagDocument.cs @@ -138,7 +138,7 @@ public async Task ExecuteCommandLineAsync(bool r /// The absolute path. protected override string ConvertToAbsolutePath(string pathToConvert) { - if (!string.IsNullOrEmpty(pathToConvert) && !System.IO.Path.IsPathRooted(pathToConvert) && !pathToConvert.Contains("%")) + if (!string.IsNullOrEmpty(pathToConvert) && !System.IO.Path.IsPathRooted(pathToConvert) && !pathToConvert.Contains('%')) { return PathUtilities.MakeAbsolutePath(pathToConvert, GetDocumentDirectory()); } @@ -151,7 +151,7 @@ protected override string ConvertToAbsolutePath(string pathToConvert) /// The relative path. protected override string ConvertToRelativePath(string pathToConvert) { - if (!string.IsNullOrEmpty(pathToConvert) && !pathToConvert.Contains("C:\\Program Files\\") && !pathToConvert.Contains("%")) + if (!string.IsNullOrEmpty(pathToConvert) && !pathToConvert.Contains("C:\\Program Files\\") && !pathToConvert.Contains('%')) { return PathUtilities.MakeRelativePath(pathToConvert, GetDocumentDirectory())?.Replace("\\", "/"); } @@ -192,17 +192,17 @@ private async Task StartCommandLineProcessAsync(string command) if (process.ExitCode != 0) { - var errorStart = output.IndexOf("..."); + var errorStart = output.IndexOf("...", StringComparison.Ordinal); if (errorStart < 0) { errorStart = Regex.Match(output, "\n[^\n\r]*?Exception: .*", RegexOptions.Singleline)?.Index ?? -1; } var error = errorStart > 0 ? output.Substring(errorStart + 4) : output; - var stackTraceStart = error.IndexOf("Server stack trace: "); + var stackTraceStart = error.IndexOf("Server stack trace: ", StringComparison.Ordinal); if (stackTraceStart < 0) { - stackTraceStart = error.IndexOf(" at "); + stackTraceStart = error.IndexOf(" at ", StringComparison.Ordinal); } var message = stackTraceStart > 0 ? error.Substring(0, stackTraceStart) : error; diff --git a/src/NSwag.Commands/NSwagDocumentBase.cs b/src/NSwag.Commands/NSwagDocumentBase.cs index 9e0bf3640a..86f70be446 100644 --- a/src/NSwag.Commands/NSwagDocumentBase.cs +++ b/src/NSwag.Commands/NSwagDocumentBase.cs @@ -6,6 +6,8 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +#pragma warning disable CA1507 + using System.ComponentModel; using System.Reflection; using System.Runtime.CompilerServices; @@ -308,7 +310,8 @@ private void ConvertToAbsolutePaths() { if (SwaggerGenerators.FromDocumentCommand != null) { - if (!SwaggerGenerators.FromDocumentCommand.Url.StartsWith("http://") && !SwaggerGenerators.FromDocumentCommand.Url.StartsWith("https://")) + if (!SwaggerGenerators.FromDocumentCommand.Url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + && !SwaggerGenerators.FromDocumentCommand.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { SwaggerGenerators.FromDocumentCommand.Url = ConvertToAbsolutePath(SwaggerGenerators.FromDocumentCommand.Url); } @@ -372,7 +375,8 @@ private void ConvertToRelativePaths() { if (SwaggerGenerators.FromDocumentCommand != null) { - if (!SwaggerGenerators.FromDocumentCommand.Url.StartsWith("http://") && !SwaggerGenerators.FromDocumentCommand.Url.StartsWith("https://")) + if (!SwaggerGenerators.FromDocumentCommand.Url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + && !SwaggerGenerators.FromDocumentCommand.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { SwaggerGenerators.FromDocumentCommand.Url = ConvertToRelativePath(SwaggerGenerators.FromDocumentCommand.Url); } @@ -436,7 +440,7 @@ private static string TransformLegacyDocument(string data, out bool saveFile) saveFile = false; // Swagger to OpenApi rename - if (data.Contains("\"typeScriptVersion\":") && !data.ToLowerInvariant().Contains("ExceptionClass".ToLowerInvariant())) + if (data.Contains("\"typeScriptVersion\":") && !data.Contains("ExceptionClass", StringComparison.OrdinalIgnoreCase)) { data = data.Replace("\"typeScriptVersion\":", "\"exceptionClass\": \"SwaggerException\", \"typeScriptVersion\":"); saveFile = true; @@ -509,7 +513,7 @@ private static string TransformLegacyDocument(string data, out bool saveFile) saveFile = true; } - if (data.Contains("\"noBuild\":") && !data.ToLowerInvariant().Contains("RequireParametersWithoutDefault".ToLowerInvariant())) + if (data.Contains("\"noBuild\":") && !data.Contains("RequireParametersWithoutDefault", StringComparison.OrdinalIgnoreCase)) { data = data.Replace("\"noBuild\":", "\"requireParametersWithoutDefault\": true, \"noBuild\":"); saveFile = true; diff --git a/src/NSwag.Commands/PathUtilities.cs b/src/NSwag.Commands/PathUtilities.cs index 85c73682f9..538fb178a9 100644 --- a/src/NSwag.Commands/PathUtilities.cs +++ b/src/NSwag.Commands/PathUtilities.cs @@ -29,14 +29,14 @@ public static IEnumerable ExpandFileWildcards(IEnumerable paths) var allFiles = new List(); foreach (var path in paths) { - if (path.Contains("*")) + if (path.Contains('*')) { - var starIndex = path.IndexOf("*", StringComparison.Ordinal); + var starIndex = path.IndexOf('*'); - var rootIndex = path.Substring(0, starIndex).LastIndexOf("\\", StringComparison.Ordinal); + var rootIndex = path.Substring(0, starIndex).LastIndexOf('\\'); if (rootIndex == -1) { - rootIndex = path.Substring(0, starIndex).LastIndexOf("/", StringComparison.Ordinal); + rootIndex = path.Substring(0, starIndex).LastIndexOf('/'); } var rootPath = rootIndex >= 0 ? path.Substring(0, rootIndex + 1) : Directory.GetCurrentDirectory(); diff --git a/src/NSwag.Core/Collections/ObservableDictionary.cs b/src/NSwag.Core/Collections/ObservableDictionary.cs index 5b28de0418..c2f36476ad 100644 --- a/src/NSwag.Core/Collections/ObservableDictionary.cs +++ b/src/NSwag.Core/Collections/ObservableDictionary.cs @@ -73,7 +73,7 @@ public void AddRange(IDictionary items) { if (items == null) { - throw new ArgumentNullException("items"); + throw new ArgumentNullException(nameof(items)); } if (items.Count > 0) @@ -171,10 +171,10 @@ protected void OnCollectionChanged(NotifyCollectionChangedAction action, IList n private void OnPropertyChanged() { - OnPropertyChanged("Count"); + OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); - OnPropertyChanged("Keys"); - OnPropertyChanged("Values"); + OnPropertyChanged(nameof(Keys)); + OnPropertyChanged(nameof(Values)); } #region IDictionary interface @@ -215,7 +215,7 @@ public virtual bool Remove(TKey key) { if (key == null) { - throw new ArgumentNullException("key"); + throw new ArgumentNullException(nameof(key)); } var removed = _dictionary.Remove(key); diff --git a/src/NSwag.Core/HttpUtilities.cs b/src/NSwag.Core/HttpUtilities.cs index 2bb356ee4a..f814bb5c73 100644 --- a/src/NSwag.Core/HttpUtilities.cs +++ b/src/NSwag.Core/HttpUtilities.cs @@ -16,7 +16,7 @@ public static class HttpUtilities /// true if success. public static bool IsSuccessStatusCode(string statusCode) { - return statusCode.Length == 3 && statusCode.StartsWith("2"); + return statusCode.Length == 3 && statusCode.StartsWith('2'); } } } diff --git a/src/NSwag.Core/OpenApiDocument.Serialization.cs b/src/NSwag.Core/OpenApiDocument.Serialization.cs index 9f2fdca4ff..8cf0011369 100644 --- a/src/NSwag.Core/OpenApiDocument.Serialization.cs +++ b/src/NSwag.Core/OpenApiDocument.Serialization.cs @@ -148,7 +148,7 @@ public ICollection Schemes _schemes = new ObservableCollection(Servers? .Where(s => s.Url.Contains("://")) - .Select(s => s.Url.StartsWith("http://") ? OpenApiSchema.Http : OpenApiSchema.Https) + .Select(s => s.Url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ? OpenApiSchema.Http : OpenApiSchema.Https) .Distinct() ?? []); _schemes.CollectionChanged += OnSchemesChanged; diff --git a/src/NSwag.Core/OpenApiDocument.cs b/src/NSwag.Core/OpenApiDocument.cs index 8c5624914b..f664c5b4e7 100644 --- a/src/NSwag.Core/OpenApiDocument.cs +++ b/src/NSwag.Core/OpenApiDocument.cs @@ -6,7 +6,6 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- -using System.Collections.ObjectModel; using System.Reflection; using System.Text.RegularExpressions; using Newtonsoft.Json; @@ -176,14 +175,14 @@ public static async Task FromJsonAsync(string data, string docu var match = Regex.Match(data, pattern, RegexOptions.IgnoreCase); if (match.Success) { - var schemaType = match.Groups["schemaType"].Value.ToLower(); - var schemaVersion = match.Groups["schemaVersion"].Value.ToLower(); + var schemaType = match.Groups["schemaType"].Value.ToLowerInvariant(); + var schemaVersion = match.Groups["schemaVersion"].Value.ToLowerInvariant(); - if (schemaType == "swagger" && schemaVersion.StartsWith("2")) + if (schemaType == "swagger" && schemaVersion.StartsWith('2')) { expectedSchemaType = SchemaType.Swagger2; } - else if (schemaType == "openapi" && schemaVersion.StartsWith("3")) + else if (schemaType == "openapi" && schemaVersion.StartsWith('3')) { expectedSchemaType = SchemaType.OpenApi3; } @@ -297,7 +296,7 @@ public void GenerateOperationIds() { if (group.Count() > 1) { - var methods = group.Select(o => o.Method.ToUpper()).Distinct(); + var methods = group.Select(o => o.Method.ToUpperInvariant()).Distinct(); if (methods.Count() == 1) { continue; @@ -305,7 +304,7 @@ public void GenerateOperationIds() foreach (var o in group) { - o.Operation.OperationId += o.Method.ToUpper(); + o.Operation.OperationId += o.Method.ToUpperInvariant(); } } } diff --git a/src/NSwag.Core/Polyfills.cs b/src/NSwag.Core/Polyfills.cs new file mode 100644 index 0000000000..8453ce3f50 --- /dev/null +++ b/src/NSwag.Core/Polyfills.cs @@ -0,0 +1,10 @@ +namespace NSwag; + +internal static class Polyfills +{ +#if !NETCOREAPP + public static bool StartsWith(this string str, char c) => str.Length > 0 && str[0] == c; + public static bool EndsWith(this string str, char c) => str.Length > 0 && str[str.Length - 1] == c; + public static bool Contains(this string str, string s, StringComparison comparison) => str.IndexOf(s, comparison) != -1; +#endif +} \ No newline at end of file diff --git a/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs b/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs index 2d93811210..a745844d72 100644 --- a/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs +++ b/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs @@ -9,6 +9,7 @@ #pragma warning disable IDE0005 using System.Collections; +using System.Globalization; using System.Reflection; using System.Text; using System.Text.Json; @@ -215,7 +216,7 @@ private List GenerateApiGroups( } var path = apiDescription.RelativePath; - if (!path.StartsWith("/", StringComparison.Ordinal)) + if (!path.StartsWith('/')) { path = "/" + path; } @@ -478,7 +479,7 @@ private string GetOperationId(OpenApiDocument document, ApiDescription apiDescri if (!string.IsNullOrWhiteSpace(httpMethod)) { var attributeName = Char.ToUpperInvariant(httpMethod[0]) + httpMethod.Substring(1).ToLowerInvariant(); - var typeName = string.Format("Microsoft.AspNetCore.Mvc.Http{0}Attribute", attributeName); + var typeName = string.Format(CultureInfo.InvariantCulture, "Microsoft.AspNetCore.Mvc.Http{0}Attribute", attributeName); httpAttribute = method? .GetCustomAttributes() .FirstAssignableToTypeNameOrDefault(typeName); @@ -531,8 +532,8 @@ private string GetOperationId(OpenApiDocument document, ApiDescription apiDescri httpMethod[0].ToString().ToUpperInvariant() + httpMethod.Substring(1) + string.Join("", apiDescription.RelativePath .Split('/', '\\', '}', ']', '-', '_') - .Where(t => !t.StartsWith("{")) - .Where(t => !t.StartsWith("[")) + .Where(t => !t.StartsWith('{')) + .Where(t => !t.StartsWith('[')) .Select(t => t.Length > 1 ? t[0].ToString().ToUpperInvariant() + t.Substring(1) : t.ToUpperInvariant())); } @@ -542,12 +543,12 @@ private string GetOperationId(OpenApiDocument document, ApiDescription apiDescri number++; } - return operationId + (number > 1 ? number.ToString() : string.Empty); + return operationId + (number > 1 ? number.ToString(CultureInfo.InvariantCulture) : string.Empty); } private static string GetActionName(string actionName) { - if (actionName.EndsWith("Async")) + if (actionName.EndsWith("Async", StringComparison.Ordinal)) { actionName = actionName.Substring(0, actionName.Length - 5); } diff --git a/src/NSwag.Generation.AspNetCore/NSwag.Generation.AspNetCore.csproj b/src/NSwag.Generation.AspNetCore/NSwag.Generation.AspNetCore.csproj index 93c97ac420..47b2dad663 100644 --- a/src/NSwag.Generation.AspNetCore/NSwag.Generation.AspNetCore.csproj +++ b/src/NSwag.Generation.AspNetCore/NSwag.Generation.AspNetCore.csproj @@ -27,4 +27,9 @@ + + + + + diff --git a/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs b/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs index 46fd126115..79f38a726d 100644 --- a/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs +++ b/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs @@ -87,7 +87,7 @@ public bool Process(OperationProcessorContext operationProcessorContext) { var parameterDescriptor = apiParameter.TryGetPropertyValue("ParameterDescriptor"); var parameterName = parameterDescriptor?.Name ?? apiParameter.Name; - parameter = methodParameters.FirstOrDefault(m => m.Name.ToLowerInvariant() == parameterName.ToLowerInvariant()); + parameter = methodParameters.FirstOrDefault(m => m.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase)); if (parameter != null) { extendedApiParameter.ParameterInfo = parameter; @@ -112,8 +112,8 @@ public bool Process(OperationProcessorContext operationProcessorContext) if (apiParameter.Source == BindingSource.Path) { // ignore unused implicit path parameters - if (!httpPath.ToLowerInvariant().Contains("{" + apiParameter.Name.ToLowerInvariant() + ":") && - !httpPath.ToLowerInvariant().Contains("{" + apiParameter.Name.ToLowerInvariant() + "}")) + if (!httpPath.Contains("{" + apiParameter.Name + ":", StringComparison.OrdinalIgnoreCase) && + !httpPath.Contains("{" + apiParameter.Name + "}", StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/src/NSwag.Generation.AspNetCore/Processors/OperationResponseProcessor.cs b/src/NSwag.Generation.AspNetCore/Processors/OperationResponseProcessor.cs index 63befcf6ae..d2e7573884 100644 --- a/src/NSwag.Generation.AspNetCore/Processors/OperationResponseProcessor.cs +++ b/src/NSwag.Generation.AspNetCore/Processors/OperationResponseProcessor.cs @@ -81,7 +81,7 @@ public bool Process(OperationProcessorContext operationProcessorContext) var nullableXmlAttribute = GetResponseXmlDocsElement(context.MethodInfo, httpStatusCode)?.Attribute("nullable"); var isResponseNullable = nullableXmlAttribute != null ? - nullableXmlAttribute.Value.ToLowerInvariant() == "true" : + nullableXmlAttribute.Value.Equals("true", StringComparison.OrdinalIgnoreCase) : _settings.SchemaSettings.ReflectionService.GetDescription(contextualReturnType, _settings.DefaultResponseReferenceTypeNullHandling, _settings.SchemaSettings).IsNullable; response.IsNullableRaw = isResponseNullable; diff --git a/src/NSwag.Generation.WebApi/NSwag.Generation.WebApi.csproj b/src/NSwag.Generation.WebApi/NSwag.Generation.WebApi.csproj index 892addd70c..5872457624 100644 --- a/src/NSwag.Generation.WebApi/NSwag.Generation.WebApi.csproj +++ b/src/NSwag.Generation.WebApi/NSwag.Generation.WebApi.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs b/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs index 984e7795fe..720e4a30c8 100644 --- a/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs +++ b/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs @@ -462,8 +462,8 @@ private OpenApiParameter AddPrimitiveParametersFromUri( InitializeFileParameter(operationParameter, isFileArray); } else if (fromRouteAttribute != null - || httpPath.ToLowerInvariant().Contains("{" + propertyName.ToLower() + "}") - || httpPath.ToLowerInvariant().Contains("{" + propertyName.ToLower() + ":")) + || httpPath.Contains("{" + propertyName + "}", StringComparison.OrdinalIgnoreCase) + || httpPath.Contains("{" + propertyName + ":", StringComparison.OrdinalIgnoreCase)) { operationParameter.Kind = OpenApiParameterKind.Path; operationParameter.IsNullableRaw = false; diff --git a/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs b/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs index dec729ea78..23ce344e0f 100644 --- a/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs +++ b/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using System.Collections; +using System.Globalization; using System.Reflection; using Namotion.Reflection; using NJsonSchema; @@ -34,7 +35,7 @@ public static IEnumerable GetControllerClasses(Assembly assembly) // TODO: Move to IControllerClassLoader interface return assembly.ExportedTypes .Where(t => !t.GetTypeInfo().IsAbstract) - .Where(t => t.Name.EndsWith("Controller") || + .Where(t => t.Name.EndsWith("Controller", StringComparison.Ordinal) || t.InheritsFromTypeName("ApiController", TypeNameStyle.Name) || t.InheritsFromTypeName("ControllerBase", TypeNameStyle.Name)) // in ASP.NET Core, a Web API controller inherits from Controller .Where(t => t.GetTypeInfo().ImplementedInterfaces.All(i => i.FullName != "System.Web.Mvc.IController")) // no MVC controllers (legacy ASP.NET) @@ -281,7 +282,7 @@ private static IEnumerable GetActionMethods(Type controllerType) m.GetCustomAttributes().Select(a => a.GetType()).All(a => !a.IsAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name) && !a.IsAssignableToTypeName("NonActionAttribute", TypeNameStyle.Name)) && - !m.DeclaringType.FullName.StartsWith("Microsoft.AspNet") && // .NET Core (Web API & MVC) + !m.DeclaringType.FullName.StartsWith("Microsoft.AspNet", StringComparison.Ordinal) && // .NET Core (Web API & MVC) m.DeclaringType.FullName != "System.Web.Http.ApiController" && m.DeclaringType.FullName != "System.Web.Mvc.Controller") .Where(m => @@ -304,7 +305,7 @@ private string GetOperationId(OpenApiDocument document, string controllerName, M if (!string.IsNullOrWhiteSpace(httpMethod)) { var attributeName = Char.ToUpperInvariant(httpMethod[0]) + httpMethod.Substring(1).ToLowerInvariant(); - var typeName = string.Format("Microsoft.AspNetCore.Mvc.Http{0}Attribute", attributeName); + var typeName = string.Format(CultureInfo.InvariantCulture, "Microsoft.AspNetCore.Mvc.Http{0}Attribute", attributeName); httpAttribute = method .GetCustomAttributes() .FirstAssignableToTypeNameOrDefault(typeName); @@ -320,7 +321,7 @@ private string GetOperationId(OpenApiDocument document, string controllerName, M } else { - if (controllerName.EndsWith("Controller")) + if (controllerName.EndsWith("Controller", StringComparison.Ordinal)) { controllerName = controllerName.Substring(0, controllerName.Length - 10); } @@ -334,7 +335,7 @@ private string GetOperationId(OpenApiDocument document, string controllerName, M number++; } - return operationId + (number > 1 ? number.ToString() : string.Empty); + return operationId + (number > 1 ? number.ToString(CultureInfo.InvariantCulture) : string.Empty); } private List GetHttpPaths(Type controllerType, MethodInfo method) @@ -352,7 +353,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) { foreach (var attribute in routeAttributes) { - if (attribute.Template.StartsWith("~/")) // ignore route prefixes + if (attribute.Template.StartsWith("~/", StringComparison.Ordinal)) // ignore route prefixes { httpPaths.Add(attribute.Template.Substring(1)); } @@ -362,7 +363,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) } else if (routeAttributesOnClass != null) { - if (attribute.Template.StartsWith("/")) + if (attribute.Template.StartsWith('/')) { httpPaths.Add(attribute.Template); } @@ -424,10 +425,10 @@ private static IEnumerable ExpandOptionalHttpPathParameters(string path, for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; - if (segment.EndsWith("?}")) + if (segment.EndsWith("?}", StringComparison.Ordinal)) { // Only expand if optional parameter is available in action method - if (method.GetParameters().Any(p => segment.StartsWith("{" + p.Name + ":") || segment.StartsWith("{" + p.Name + "?"))) + if (method.GetParameters().Any(p => segment.StartsWith("{" + p.Name + ":", StringComparison.Ordinal) || segment.StartsWith("{" + p.Name + "?", StringComparison.Ordinal))) { foreach (var p in ExpandOptionalHttpPathParameters(string.Join("/", segments.Take(i).Concat([segment.Replace("?", "")]).Concat(segments.Skip(i + 1))), method)) { @@ -506,7 +507,7 @@ private static string GetActionName(MethodInfo method) } var methodName = method.Name; - if (methodName.EndsWith("Async")) + if (methodName.EndsWith("Async", StringComparison.Ordinal)) { methodName = methodName.Substring(0, methodName.Length - 5); } diff --git a/src/NSwag.Generation/Processors/OperationResponseProcessorBase.cs b/src/NSwag.Generation/Processors/OperationResponseProcessorBase.cs index f76492c2af..a979aa64c8 100644 --- a/src/NSwag.Generation/Processors/OperationResponseProcessorBase.cs +++ b/src/NSwag.Generation/Processors/OperationResponseProcessorBase.cs @@ -191,7 +191,7 @@ private void ProcessOperationDescriptions(IEnumerable r.IsNullable) && _settings.SchemaSettings.ReflectionService.GetDescription(contextualReturnType, _settings.DefaultResponseReferenceTypeNullHandling, _settings.SchemaSettings).IsNullable; diff --git a/src/NSwag.Generation/Processors/OperationTagsProcessor.cs b/src/NSwag.Generation/Processors/OperationTagsProcessor.cs index 5d3a245c7a..f92d1a5bc7 100644 --- a/src/NSwag.Generation/Processors/OperationTagsProcessor.cs +++ b/src/NSwag.Generation/Processors/OperationTagsProcessor.cs @@ -52,7 +52,7 @@ public virtual bool Process(OperationProcessorContext context) protected virtual void AddControllerNameTag(OperationProcessorContext context) { var controllerName = context.ControllerType.Name; - if (controllerName.EndsWith("Controller")) + if (controllerName.EndsWith("Controller", StringComparison.Ordinal)) { controllerName = controllerName.Substring(0, controllerName.Length - 10); }