diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 42e5fa8037..9e303b8608 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,7 +11,7 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], - "problemMatcher": "$msCompile" + "problemMatcher": [] }, { "label": "publish", @@ -25,7 +25,7 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], - "problemMatcher": "$msCompile" + "problemMatcher": [] }, { "label": "watch", @@ -38,7 +38,7 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], - "problemMatcher": "$msCompile" + "problemMatcher": [] } ] } \ No newline at end of file diff --git a/perf/Utilities/MSBuildRegister.cs b/perf/Utilities/MSBuildRegister.cs index 1963771bee..59585672d6 100644 --- a/perf/Utilities/MSBuildRegister.cs +++ b/perf/Utilities/MSBuildRegister.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Tools.Perf { public static class MSBuildRegister { - private static int s_registered = 0; + private static int s_registered; public static void RegisterInstance() { diff --git a/perf/Utilities/SolutionPathSetter.cs b/perf/Utilities/SolutionPathSetter.cs index 37a723ca64..157f78871f 100644 --- a/perf/Utilities/SolutionPathSetter.cs +++ b/perf/Utilities/SolutionPathSetter.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Tools.Perf { public static class SolutionPathSetter { - private static int s_registered = 0; + private static int s_registered; private static string s_currentDirectory; public static string RepositoryRootDirectory => Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.FullName; diff --git a/src/Analyzers/AnalyzerFormatter.cs b/src/Analyzers/AnalyzerFormatter.cs index 80336483c2..39c29ce44d 100644 --- a/src/Analyzers/AnalyzerFormatter.cs +++ b/src/Analyzers/AnalyzerFormatter.cs @@ -101,7 +101,7 @@ private async Task>> Get foreach (var project in solution.Projects) { var analyzers = projectAnalyzers[project]; - if (analyzers.Length == 0) + if (analyzers.IsEmpty) { continue; } @@ -116,13 +116,18 @@ private async Task>> Get static void LogDiagnosticLocations(Solution solution, IEnumerable diagnostics, string workspacePath, bool changesAreErrors, ILogger logger, List formattedFiles) { - var workspaceFolder = Path.GetDirectoryName(workspacePath); + var workspaceFolder = Path.GetDirectoryName(workspacePath) ?? workspacePath; foreach (var diagnostic in diagnostics) { var message = $"{diagnostic.GetMessage()} ({diagnostic.Id})"; - var filePath = diagnostic.Location.SourceTree?.FilePath; var document = solution.GetDocument(diagnostic.Location.SourceTree); + if (document is null) + { + continue; + } + + var filePath = document.FilePath ?? document.Name; var mappedLineSpan = diagnostic.Location.GetMappedLineSpan(); var changePosition = mappedLineSpan.StartLinePosition; @@ -252,7 +257,7 @@ internal static async Task(this Type type, [NotNullWhen(returnValue var defaultCtor = type.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, - new Type[] { }, + Array.Empty(), modifiers: null); instance = defaultCtor != null @@ -45,7 +45,7 @@ public static bool TryCreateInstance(this Type type, [NotNullWhen(returnValue BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, args: null, - culture: null) + culture: null)! : null; return instance != null; @@ -160,7 +160,7 @@ static bool TryGetSeverityFromCodeStyleOption( severity = DiagnosticSeverity.Hidden; var parameters = new object?[] { descriptor.Id, compilation.Language, null }; - var result = (bool)TryGetMappedOptionsMethod.Invoke(null, parameters); + var result = (bool)(TryGetMappedOptionsMethod.Invoke(null, parameters) ?? false); if (!result) { @@ -170,7 +170,7 @@ static bool TryGetSeverityFromCodeStyleOption( var codeStyleOptions = (IEnumerable)parameters[2]!; foreach (var codeStyleOptionObj in codeStyleOptions) { - var codeStyleOption = (IOption)codeStyleOptionObj; + var codeStyleOption = (IOption)codeStyleOptionObj!; var option = options.GetOption(new OptionKey(codeStyleOption, codeStyleOption.IsPerLanguage ? compilation.Language : null)); if (option is null) { @@ -184,9 +184,13 @@ static bool TryGetSeverityFromCodeStyleOption( } var notification = notificationProperty.GetValue(option); - var reportDiagnostic = (ReportDiagnostic)notification.GetType().GetProperty("Severity").GetValue(notification); - var codeStyleSeverity = ToSeverity(reportDiagnostic); + var reportDiagnosticValue = notification?.GetType().GetProperty("Severity")?.GetValue(notification); + if (reportDiagnosticValue is null) + { + continue; + } + var codeStyleSeverity = ToSeverity((ReportDiagnostic)reportDiagnosticValue); if (codeStyleSeverity > severity) { severity = codeStyleSeverity; diff --git a/src/Formatters/EndOfLineFormatter.cs b/src/Formatters/EndOfLineFormatter.cs index 732d31ebdb..58094c6919 100644 --- a/src/Formatters/EndOfLineFormatter.cs +++ b/src/Formatters/EndOfLineFormatter.cs @@ -38,7 +38,7 @@ internal override Task FormatFileAsync( var lineEndingSpan = new TextSpan(line.End, line.EndIncludingLineBreak - line.End); // Check for end of file - if (lineEndingSpan.Length == 0) + if (lineEndingSpan.IsEmpty) { break; } diff --git a/src/Formatters/FinalNewlineFormatter.cs b/src/Formatters/FinalNewlineFormatter.cs index f59c8482de..75f2803b30 100644 --- a/src/Formatters/FinalNewlineFormatter.cs +++ b/src/Formatters/FinalNewlineFormatter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -35,7 +34,7 @@ internal override async Task FormatFileAsync( endOfLine = Environment.NewLine; } - var lastLine = sourceText.Lines.Last(); + var lastLine = sourceText.Lines[^1]; var hasFinalNewline = lastLine.Span.IsEmpty; if (insertFinalNewline && !hasFinalNewline) @@ -49,12 +48,12 @@ internal override async Task FormatFileAsync( // In the case of empty files where there is a single empty line, there is nothing to remove. while (sourceText.Lines.Count > 1 && hasFinalNewline) { - var lineBeforeLast = sourceText.Lines[sourceText.Lines.Count - 2]; + var lineBeforeLast = sourceText.Lines[^2]; var finalNewlineSpan = new TextSpan(lineBeforeLast.End, lineBeforeLast.EndIncludingLineBreak - lineBeforeLast.End); var removeNewlineChange = new TextChange(finalNewlineSpan, string.Empty); sourceText = sourceText.WithChanges(removeNewlineChange); - lastLine = sourceText.Lines.Last(); + lastLine = sourceText.Lines[^1]; hasFinalNewline = lastLine.Span.IsEmpty; } } diff --git a/src/Formatters/UnnecessaryImportsFormatter.cs b/src/Formatters/UnnecessaryImportsFormatter.cs index 3170aade48..294da7498c 100644 --- a/src/Formatters/UnnecessaryImportsFormatter.cs +++ b/src/Formatters/UnnecessaryImportsFormatter.cs @@ -49,6 +49,10 @@ internal override async Task FormatFileAsync( } var formattedDocument = await RemoveUnnecessaryImportsHelper.RemoveUnnecessaryImportsAsync(document, cancellationToken).ConfigureAwait(false); + if (formattedDocument is null) + { + return sourceText; + } var isSameVersion = await IsSameDocumentAndVersionAsync(document, formattedDocument, cancellationToken).ConfigureAwait(false); if (isSameVersion) diff --git a/src/MSBuild/LooseVersionAssemblyLoader.cs b/src/MSBuild/LooseVersionAssemblyLoader.cs index 6d7a6c5be3..635f6bcdcf 100644 --- a/src/MSBuild/LooseVersionAssemblyLoader.cs +++ b/src/MSBuild/LooseVersionAssemblyLoader.cs @@ -69,10 +69,13 @@ public static void Register(string searchPath) } catch { - // We were unable to load the assembly from the file path. It is likely that - // a different version of the assembly has already been loaded into the context. - // Be forgiving and attempt to load assembly by name without specifying a version. - return context.LoadFromAssemblyName(new AssemblyName(assemblyName.Name)); + if (assemblyName.Name != null) + { + // We were unable to load the assembly from the file path. It is likely that + // a different version of the assembly has already been loaded into the context. + // Be forgiving and attempt to load assembly by name without specifying a version. + return context.LoadFromAssemblyName(new AssemblyName(assemblyName.Name)); + } } } } diff --git a/src/Program.cs b/src/Program.cs index 8827118bf5..ec0706babd 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -29,7 +29,7 @@ internal class Program private static async Task Main(string[] args) { var rootCommand = FormatCommand.CreateCommandLineOptions(); - rootCommand.Handler = CommandHandler.Create(typeof(Program).GetMethod(nameof(Run))); + rootCommand.Handler = CommandHandler.Create(typeof(Program).GetMethod(nameof(Run))!); // Parse the incoming args so we can give warnings when deprecated options are used. s_parseResult = rootCommand.Parse(args); @@ -74,7 +74,7 @@ public static async Task Run( currentDirectory = Environment.CurrentDirectory; - string workspaceDirectory; + string? workspaceDirectory; string workspacePath; WorkspaceType workspaceType; diff --git a/src/Reflection/RemoveUnnecessaryImportsHelper.cs b/src/Reflection/RemoveUnnecessaryImportsHelper.cs index 2ad93f9451..dd67adb0a0 100644 --- a/src/Reflection/RemoveUnnecessaryImportsHelper.cs +++ b/src/Reflection/RemoveUnnecessaryImportsHelper.cs @@ -13,7 +13,7 @@ internal static class RemoveUnnecessaryImportsHelper private static readonly Type? s_abstractRemoveUnnecessaryImportsCodeFixProviderType = s_microsoftCodeAnalysisFeaturesAssembly?.GetType("Microsoft.CodeAnalysis.RemoveUnnecessaryImports.AbstractRemoveUnnecessaryImportsCodeFixProvider"); private static readonly MethodInfo? s_removeUnnecessaryImportsAsyncMethod = s_abstractRemoveUnnecessaryImportsCodeFixProviderType?.GetMethod("RemoveUnnecessaryImportsAsync", BindingFlags.Static | BindingFlags.NonPublic); - public static async Task RemoveUnnecessaryImportsAsync(Document document, CancellationToken cancellationToken) + public static async Task RemoveUnnecessaryImportsAsync(Document document, CancellationToken cancellationToken) { if (s_removeUnnecessaryImportsAsyncMethod is null) { diff --git a/src/ReportWriter.cs b/src/ReportWriter.cs index c2bcc0b5bf..e827dd3605 100644 --- a/src/ReportWriter.cs +++ b/src/ReportWriter.cs @@ -15,7 +15,7 @@ public static void Write(string reportPath, IEnumerable formatted var reportFilePath = GetReportFilePath(reportPath); var reportFolderPath = Path.GetDirectoryName(reportFilePath); - if (!Directory.Exists(reportFolderPath)) + if (reportFolderPath != null && !Directory.Exists(reportFolderPath)) { Directory.CreateDirectory(reportFolderPath); } diff --git a/src/Utilities/Index.cs b/src/Utilities/Index.cs new file mode 100644 index 0000000000..d498d630b5 --- /dev/null +++ b/src/Utilities/Index.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +namespace System +{ + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// + internal readonly struct Index : IEquatable + { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Non-negative number required."); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Non-negative number required."); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Non-negative number required."); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + var offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return $"^{((uint)Value).ToString()}"; + + return ((uint)Value).ToString(); + } + } +} diff --git a/src/Workspaces/MSBuildWorkspaceLoader.cs b/src/Workspaces/MSBuildWorkspaceLoader.cs index ab01ddbc20..32d78ba9e5 100644 --- a/src/Workspaces/MSBuildWorkspaceLoader.cs +++ b/src/Workspaces/MSBuildWorkspaceLoader.cs @@ -67,7 +67,7 @@ static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWarnings, I { if (!logWorkspaceWarnings) { - if (diagnostics.Count > 0) + if (diagnostics.IsEmpty) { logger.LogWarning(Resources.Warnings_were_encountered_while_loading_the_workspace_Set_the_verbosity_option_to_the_diagnostic_level_to_log_warnings); } diff --git a/tests/Analyzers/AnalyzerAssemblyGenerator.cs b/tests/Analyzers/AnalyzerAssemblyGenerator.cs index 69130119f5..1e1ea0b4ec 100644 --- a/tests/Analyzers/AnalyzerAssemblyGenerator.cs +++ b/tests/Analyzers/AnalyzerAssemblyGenerator.cs @@ -88,24 +88,23 @@ public static async Task GenerateAssemblyAsync(params SyntaxTree[] tre references.AddRange(netstandardMetaDataReferences); var compilation = CSharpCompilation.Create(assemblyName, trees, references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - using (var ms = new MemoryStream()) + + using var ms = new MemoryStream(); + var result = compilation.Emit(ms); + if (!result.Success) { - var result = compilation.Emit(ms); - if (!result.Success) - { - var failures = result.Diagnostics.Where(diagnostic => - diagnostic.IsWarningAsError || - diagnostic.Severity == DiagnosticSeverity.Error) - .Select(diagnostic => $"{diagnostic.Id}: {diagnostic.GetMessage()}"); + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error) + .Select(diagnostic => $"{diagnostic.Id}: {diagnostic.GetMessage()}"); - throw new Exception(string.Join(Environment.NewLine, failures)); - } - else - { - ms.Seek(0, SeekOrigin.Begin); - var assembly = Assembly.Load(ms.ToArray()); - return assembly; - } + throw new Exception(string.Join(Environment.NewLine, failures)); + } + else + { + ms.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms.ToArray()); + return assembly; } } } diff --git a/tests/Analyzers/FilterDiagnosticsTests.cs b/tests/Analyzers/FilterDiagnosticsTests.cs index 2adce2a1fb..ff2e0286c9 100644 --- a/tests/Analyzers/FilterDiagnosticsTests.cs +++ b/tests/Analyzers/FilterDiagnosticsTests.cs @@ -54,7 +54,7 @@ public async Task TestFilterError() Assert.Empty(analyzers); } - private async Task> GetAnalyzersAsync() + private static async Task> GetAnalyzersAsync() { var assemblies = new[] { diff --git a/tests/Utilities/MSBuildFixture.cs b/tests/Utilities/MSBuildFixture.cs index d0771c952c..667f2b7eed 100644 --- a/tests/Utilities/MSBuildFixture.cs +++ b/tests/Utilities/MSBuildFixture.cs @@ -11,9 +11,9 @@ namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities /// /// This test fixture ensures that MSBuild is loaded. /// - public class MSBuildFixture : IDisposable + public sealed class MSBuildFixture : IDisposable { - private static int s_registered = 0; + private static int s_registered; public void RegisterInstance(ITestOutputHelper output) { diff --git a/tests/Utilities/TestProjectsPathFixture.cs b/tests/Utilities/TestProjectsPathFixture.cs index 567424e2d5..e2a9272749 100644 --- a/tests/Utilities/TestProjectsPathFixture.cs +++ b/tests/Utilities/TestProjectsPathFixture.cs @@ -9,9 +9,9 @@ namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities /// /// This test fixture sets the to the dotnet-format test projects folder path. /// - public class TestProjectsPathFixture : IDisposable + public sealed class TestProjectsPathFixture : IDisposable { - private static int s_registered = 0; + private static int s_registered; private static string s_currentDirectory; public void SetCurrentDirectory()