diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 8fd0aa6a53a19..8aaae5856c32f 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -15,8 +15,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; @@ -26,17 +24,11 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue; -internal sealed class CSharpEditAndContinueAnalyzer(Action? testFaultInjector = null) : AbstractEditAndContinueAnalyzer(testFaultInjector) +[ExportLanguageService(typeof(IEditAndContinueAnalyzer), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpEditAndContinueAnalyzer() : AbstractEditAndContinueAnalyzer { - [ExportLanguageServiceFactory(typeof(IEditAndContinueAnalyzer), LanguageNames.CSharp), Shared] - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class Factory() : ILanguageServiceFactory - { - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - => new CSharpEditAndContinueAnalyzer(testFaultInjector: null); - } - #region Syntax Analysis private enum BlockPart diff --git a/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 5f2f298878bf1..d0e3089111a38 100644 --- a/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -122,7 +122,7 @@ private static async Task AnalyzeDocumentAsync( EditAndContinueCapabilities capabilities = EditAndContinueTestVerifier.Net5RuntimeCapabilities, ImmutableArray newActiveStatementSpans = default) { - var analyzer = new CSharpEditAndContinueAnalyzer(); + var analyzer = oldProject.Services.GetRequiredService(); var baseActiveStatements = AsyncLazy.Create(activeStatementMap ?? ActiveStatementsMap.Empty); var lazyCapabilities = AsyncLazy.Create(capabilities); return await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, newActiveStatementSpans.NullToEmpty(), lazyCapabilities, CancellationToken.None); @@ -749,13 +749,14 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) var baseActiveStatements = AsyncLazy.Create(ActiveStatementsMap.Empty); var capabilities = AsyncLazy.Create(EditAndContinueTestVerifier.Net5RuntimeCapabilities); - var analyzer = new CSharpEditAndContinueAnalyzer(node => + var analyzer = Assert.IsType(oldProject.Services.GetRequiredService()); + analyzer.GetTestAccessor().FaultInjector = node => { if (node is CompilationUnitSyntax) { throw outOfMemory ? new OutOfMemoryException() : new NullReferenceException("NullRef!"); } - }); + }; var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, [], capabilities, CancellationToken.None); diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestVerifier.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestVerifier.cs index 2c38995e0dce1..8dcdd79f7b572 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestVerifier.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestVerifier.cs @@ -6,17 +6,13 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Differencing; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests; -internal sealed class CSharpEditAndContinueTestVerifier(Action? faultInjector = null) : EditAndContinueTestVerifier +internal sealed class CSharpEditAndContinueTestVerifier(Action? faultInjector = null) : EditAndContinueTestVerifier(faultInjector) { - private readonly CSharpEditAndContinueAnalyzer _analyzer = new(faultInjector); - - public override AbstractEditAndContinueAnalyzer Analyzer => _analyzer; public override string LanguageName => LanguageNames.CSharp; public override string ProjectFileExtension => ".csproj"; public override TreeComparer TopSyntaxComparer => SyntaxComparer.TopLevel; diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 09d4cd4e4559f..2b9b2305cfe91 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.Differencing; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Roslyn.Test.Utilities; using Xunit; diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 54dd440d1f2ae..8d4e109e4f1d6 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -17,6 +17,8 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; +using Microsoft.CodeAnalysis.Host.Mef; +using System.Linq; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests; @@ -27,11 +29,6 @@ public abstract class EditingTestBase : CSharpTestBase namespace System.Runtime.CompilerServices { class CreateNewOnMetadataUpdateAttribute : Attribute {} } "; - internal static CSharpEditAndContinueAnalyzer CreateAnalyzer() - { - return new CSharpEditAndContinueAnalyzer(testFaultInjector: null); - } - internal enum MethodKind { Regular, @@ -184,7 +181,8 @@ internal static Match GetMethodMatch(string src1, string src2, Metho internal static IEnumerable> GetMethodMatches(string src1, string src2, MethodKind kind = MethodKind.Regular) { var methodMatch = GetMethodMatch(src1, src2, kind); - return EditAndContinueTestVerifier.GetMethodMatches(CreateAnalyzer(), methodMatch); + var analyzer = EditAndContinueTestVerifier.CreateAnalyzer(faultInjector: null, LanguageNames.CSharp); + return EditAndContinueTestVerifier.GetMethodMatches(analyzer, methodMatch); } public static MatchingPairs ToMatchingPairs(Match match) diff --git a/src/Features/CSharpTest/EditAndContinue/StatementMatchingTests.cs b/src/Features/CSharpTest/EditAndContinue/StatementMatchingTests.cs index 6e6ebef35347a..cc2bd04c9dc14 100644 --- a/src/Features/CSharpTest/EditAndContinue/StatementMatchingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/StatementMatchingTests.cs @@ -7,11 +7,13 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests; +[UseExportProvider] public class StatementMatchingTests : EditingTestBase { #region Known Matches diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index b52c11867bb62..723c288737090 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -69,12 +69,7 @@ internal abstract class AbstractEditAndContinueAnalyzer : IEditAndContinueAnalyz SymbolDisplayMiscellaneousOptions.UseSpecialTypes); // used by tests to validate correct handlign of unexpected exceptions - private readonly Action? _testFaultInjector; - - protected AbstractEditAndContinueAnalyzer(Action? testFaultInjector) - { - _testFaultInjector = testFaultInjector; - } + private Action? _testFaultInjector; private static TraceLog Log => EditAndContinueService.AnalysisLog; @@ -573,7 +568,7 @@ public async Task AnalyzeDocumentAsync( { // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. - Log.Write("Syntax errors found in '{0}'", filePath); + Log.Write($"Syntax errors found in '{filePath}'"); return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, [], syntaxError, analysisStopwatch.Elapsed, hasChanges); } @@ -584,7 +579,7 @@ public async Task AnalyzeDocumentAsync( // a) comparing texts is cheaper than diffing trees // b) we need to ignore errors in unchanged documents - Log.Write("Document unchanged: '{0}'", filePath); + Log.Write($"Document unchanged: '{filePath}'"); return DocumentAnalysisResults.Unchanged(newDocument.Id, filePath, analysisStopwatch.Elapsed); } @@ -592,7 +587,7 @@ public async Task AnalyzeDocumentAsync( // These features may not be handled well by the analysis below. if (ExperimentalFeaturesEnabled(newTree)) { - Log.Write("Experimental features enabled in '{0}'", filePath); + Log.Write($"Experimental features enabled in '{filePath}'"); return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, [new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default)], syntaxError: null, analysisStopwatch.Elapsed, hasChanges); } @@ -676,7 +671,7 @@ public async Task AnalyzeDocumentAsync( } else { - Log.Write("Capabilities required by '{0}': {1}", filePath, capabilities.GrantedCapabilities); + Log.Write($"Capabilities required by '{filePath}': {capabilities.GrantedCapabilities}"); } var hasBlockingRudeEdits = diagnostics.HasBlockingRudeEdits(); @@ -728,7 +723,7 @@ static void LogRudeEdits(ArrayBuilder diagnostics, SourceTex lineText = null; } - Log.Write("Rude edit {0}:{1} '{2}' line {3}: '{4}'", diagnostic.Kind, diagnostic.SyntaxKind, filePath, lineNumber, lineText); + Log.Write($"Rude edit {diagnostic.Kind}:{diagnostic.SyntaxKind} '{filePath}' line {lineNumber}: '{lineText}'"); } } } @@ -819,7 +814,7 @@ private void AnalyzeUnchangedActiveMemberBodies( // Guard against invalid active statement spans (in case PDB was somehow out of sync with the source). if (oldBody == null || newBody == null) { - Log.Write("Invalid active statement span: [{0}..{1})", oldStatementSpan.Start, oldStatementSpan.End); + Log.Write($"Invalid active statement span: {oldStatementSpan}", LogMessageSeverity.Warning); continue; } @@ -872,7 +867,7 @@ private void AnalyzeUnchangedActiveMemberBodies( } else { - Log.Write("Invalid active statement span: [{0}..{1})", oldStatementSpan.Start, oldStatementSpan.End); + Log.Write($"Invalid active statement span: {oldStatementSpan}", LogMessageSeverity.Warning); } // we were not able to determine the active statement location (PDB data might be invalid) @@ -6779,19 +6774,23 @@ private static bool HasGetHashCodeSignature(IMethodSymbol method) internal TestAccessor GetTestAccessor() => new(this); - internal readonly struct TestAccessor(AbstractEditAndContinueAnalyzer abstractEditAndContinueAnalyzer) + internal readonly struct TestAccessor(AbstractEditAndContinueAnalyzer analyzer) { - private readonly AbstractEditAndContinueAnalyzer _abstractEditAndContinueAnalyzer = abstractEditAndContinueAnalyzer; + internal Action? FaultInjector + { + get => analyzer._testFaultInjector = FaultInjector; + set => analyzer._testFaultInjector = value; + } internal void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) - => _abstractEditAndContinueAnalyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); + => analyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); internal DeclarationBodyMap IncludeLambdaBodyMaps( DeclarationBodyMap bodyMap, ArrayBuilder memberBodyActiveNodes, ref Dictionary? lazyActiveOrMatchedLambdas) { - return _abstractEditAndContinueAnalyzer.IncludeLambdaBodyMaps(bodyMap, memberBodyActiveNodes, ref lazyActiveOrMatchedLambdas); + return analyzer.IncludeLambdaBodyMaps(bodyMap, memberBodyActiveNodes, ref lazyActiveOrMatchedLambdas); } } diff --git a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs index 71169e100204b..abc9476572103 100644 --- a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs +++ b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs @@ -413,14 +413,14 @@ await TryGetMatchingSourceTextAsync(sourceText, sourceFilePath, currentDocument: if (debugInfoReaderProvider == null) { - EditAndContinueService.Log.Write("Source file of project '{0}' doesn't match output PDB: PDB '{1}' (assembly: '{2}') not found", projectName, compilationOutputs.PdbDisplayPath, compilationOutputs.AssemblyDisplayPath); + EditAndContinueService.Log.Write($"Source file of project '{projectName}' doesn't match output PDB: PDB '{compilationOutputs.PdbDisplayPath}' (assembly: '{compilationOutputs.AssemblyDisplayPath}') not found", LogMessageSeverity.Warning); } return debugInfoReaderProvider; } catch (Exception e) { - EditAndContinueService.Log.Write("Source file of project '{0}' doesn't match output PDB: error opening PDB '{1}' (assembly: '{2}'): {3}", projectName, compilationOutputs.PdbDisplayPath, compilationOutputs.AssemblyDisplayPath, e.Message); + EditAndContinueService.Log.Write($"Source file of project '{projectName}' doesn't match output PDB: error opening PDB '{compilationOutputs.PdbDisplayPath}' (assembly: '{compilationOutputs.AssemblyDisplayPath}'): {e.Message}", LogMessageSeverity.Warning); return null; } } @@ -453,14 +453,14 @@ private static bool IsMatchingSourceText(SourceText sourceText, ImmutableArray Ordinal.ToString(); } -internal readonly record struct UpdateId(DebuggingSessionId SessionId, int Ordinal); +internal readonly record struct UpdateId(DebuggingSessionId SessionId, int Ordinal) +{ + public override string ToString() + => $"{SessionId}.{Ordinal}"; +} diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index 7e4f13cce471c..4857b5fefa284 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs @@ -24,7 +24,9 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; /// Implements core of Edit and Continue orchestration: management of edit sessions and connecting EnC related services. /// [Export(typeof(IEditAndContinueService)), Shared] -internal sealed class EditAndContinueService : IEditAndContinueService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class EditAndContinueService() : IEditAndContinueService { [ExportWorkspaceService(typeof(IEditAndContinueWorkspaceService)), Shared] [method: ImportingConstructor] @@ -48,7 +50,7 @@ private sealed class VoidSessionTracker : IEditAndContinueSessionTracker internal static readonly TraceLog Log; internal static readonly TraceLog AnalysisLog; - private Func _compilationOutputsProvider; + private Func _compilationOutputsProvider = GetCompilationOutputs; /// /// List of active debugging sessions (small number of simoultaneously active sessions is expected). @@ -56,17 +58,10 @@ private sealed class VoidSessionTracker : IEditAndContinueSessionTracker private readonly List _debuggingSessions = []; private static int s_debuggingSessionId; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public EditAndContinueService() - { - _compilationOutputsProvider = GetCompilationOutputs; - } - static EditAndContinueService() { - Log = new(2048, "EnC", "Trace.log"); - AnalysisLog = new(1024, "EnC", "Analysis.log"); + Log = new(2048, "Trace.log"); + AnalysisLog = new(1024, "Analysis.log"); var logDir = GetLogDirectory(); if (logDir != null) @@ -171,7 +166,7 @@ public async ValueTask StartDebuggingSessionAsync( _debuggingSessions.Add(session); } - Log.Write("Session #{0} started.", sessionId.Ordinal); + Log.Write($"Session #{sessionId} started."); return sessionId; } @@ -200,7 +195,7 @@ public void EndDebuggingSession(DebuggingSessionId sessionId) debuggingSession.EndSession(out var telemetryData); - Log.Write("Session #{0} ended.", debuggingSession.Id.Ordinal); + Log.Write($"Session #{debuggingSession.Id} ended."); } public void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState) diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index b2ca1a49e750c..8d9d37d78eced 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -472,7 +472,12 @@ private static async ValueTask> foreach (var generatorDiagnostic in generatorDiagnostics) { - EditAndContinueService.Log.Write("Source generator failed: {0}", generatorDiagnostic); + EditAndContinueService.Log.Write($"Source generator failed: {generatorDiagnostic}", generatorDiagnostic.Severity switch + { + DiagnosticSeverity.Warning => LogMessageSeverity.Warning, + DiagnosticSeverity.Error => LogMessageSeverity.Error, + _ => LogMessageSeverity.Info + }); } return await project.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); @@ -802,7 +807,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution try { - log.Write("EmitSolutionUpdate {0}.{1}: '{2}'", updateId.SessionId.Ordinal, updateId.Ordinal, solution.FilePath); + log.Write($"Found {updateId.SessionId} potentially changed document(s) in project {updateId.Ordinal} '{solution.FilePath}'"); using var _1 = ArrayBuilder.GetInstance(out var deltas); using var _2 = ArrayBuilder<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)>.GetInstance(out var nonRemappableRegions); @@ -826,7 +831,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution var oldProject = oldSolution.GetProject(newProject.Id); if (oldProject == null) { - log.Write("EnC state of {0} '{1}' queried: project not loaded", newProject.Name, newProject.FilePath); + log.Write($"EnC state of {newProject.Name} '{newProject.FilePath}' queried: project not loaded"); // TODO (https://github.com/dotnet/roslyn/issues/1204): // @@ -848,7 +853,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution continue; } - log.Write("Found {0} potentially changed document(s) in project {1} '{2}'", changedOrAddedDocuments.Count, newProject.Name, newProject.FilePath); + log.Write($"Found {changedOrAddedDocuments.Count} potentially changed document(s) in project {newProject.Name} '{newProject.FilePath}'"); var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(newProject, cancellationToken).ConfigureAwait(false); if (mvidReadError != null) @@ -865,7 +870,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution if (mvid == Guid.Empty) { - log.Write("Emitting update of {0} '{1}': project not built", newProject.Name, newProject.FilePath); + log.Write($"Emitting update of {newProject.Name} '{newProject.FilePath}': project not built"); continue; } @@ -901,18 +906,18 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution // only remember the first syntax error we encounter: syntaxError ??= changedDocumentAnalysis.SyntaxError; - log.Write("Changed document '{0}' has syntax error: {1}", changedDocumentAnalysis.FilePath, changedDocumentAnalysis.SyntaxError); + log.Write($"Changed document '{changedDocumentAnalysis.FilePath}' has syntax error: {changedDocumentAnalysis.SyntaxError}"); } else if (changedDocumentAnalysis.HasChanges) { - log.Write("Document changed, added, or deleted: '{0}'", changedDocumentAnalysis.FilePath); + log.Write($"Document changed, added, or deleted: '{changedDocumentAnalysis.FilePath}'"); } Telemetry.LogAnalysisTime(changedDocumentAnalysis.ElapsedTime); } var projectSummary = GetProjectAnalysisSummary(changedDocumentAnalyses); - log.Write("Project summary for {0} '{1}': {2}", newProject.Name, newProject.FilePath, projectSummary); + log.Write($"Project summary for {newProject.Name} '{newProject.FilePath}': {projectSummary}"); if (projectSummary == ProjectAnalysisSummary.NoChanges) { @@ -990,7 +995,7 @@ async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cance } } - log.Write("Emitting update of '{0}' {1}", newProject.Name, newProject.FilePath); + log.Write($"Emitting update of {newProject.Name} '{newProject.FilePath}': project not built"); var newCompilation = await newProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(newCompilation); @@ -1139,7 +1144,7 @@ async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cance bool LogException(Exception e) { - log.Write("Exception while emitting update: {0}", e.ToString()); + log.Write($"Exception while emitting update: {e}", LogMessageSeverity.Error); return true; } } diff --git a/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs b/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs index b6945e2ed9696..3b6caa83edbb3 100644 --- a/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs +++ b/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; namespace Microsoft.CodeAnalysis.EditAndContinue; @@ -30,22 +31,22 @@ public static SolutionUpdate Blocked( bool hasEmitErrors) => new( new(syntaxError != null || hasEmitErrors ? ModuleUpdateStatus.Blocked : ModuleUpdateStatus.RestartRequired, []), - ImmutableArray<(Guid, ImmutableArray<(ManagedModuleMethodId, NonRemappableRegion)>)>.Empty, - [], + nonRemappableRegions: [], + projectBaselines: [], diagnostics, documentsWithRudeEdits, syntaxError); internal void Log(TraceLog log, UpdateId updateId) { - log.Write("Solution update {0}.{1} status: {2}", updateId.SessionId.Ordinal, updateId.Ordinal, ModuleUpdates.Status); + log.Write($"Solution update {updateId} status: {ModuleUpdates.Status}"); foreach (var moduleUpdate in ModuleUpdates.Updates) { - log.Write("Module update: capabilities=[{0}], types=[{1}], methods=[{2}]", - moduleUpdate.RequiredCapabilities, - moduleUpdate.UpdatedTypes, - moduleUpdate.UpdatedMethods); + log.Write("Module update: " + + $"capabilities=[{string.Join(",", moduleUpdate.RequiredCapabilities)}], " + + $"types=[{string.Join(",", moduleUpdate.UpdatedTypes.Select(token => token.ToString("X8")))}], " + + $"methods=[{string.Join(",", moduleUpdate.UpdatedMethods.Select(token => token.ToString("X8")))}]"); } foreach (var projectDiagnostics in Diagnostics) @@ -54,7 +55,7 @@ internal void Log(TraceLog log, UpdateId updateId) { if (diagnostic.Severity == DiagnosticSeverity.Error) { - log.Write("Project {0} update error: {1}", projectDiagnostics.ProjectId, diagnostic); + log.Write($"Project {projectDiagnostics.ProjectId.DebugName} update error: {diagnostic}", LogMessageSeverity.Error); } } } @@ -63,7 +64,7 @@ internal void Log(TraceLog log, UpdateId updateId) { foreach (var rudeEdit in documentWithRudeEdits.Diagnostics) { - log.Write("Document {0} rude edit: {1} {2}", documentWithRudeEdits.DocumentId, rudeEdit.Kind, rudeEdit.SyntaxKind); + log.Write($"Document {documentWithRudeEdits.DocumentId.DebugName} rude edit: {rudeEdit.Kind} {rudeEdit.SyntaxKind}", LogMessageSeverity.Error); } } } diff --git a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs index 49772254899dd..330a395909a92 100644 --- a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs +++ b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs @@ -6,8 +6,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,111 +13,34 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; +internal enum LogMessageSeverity +{ + Info, + Warning, + Error +} + /// /// Fixed size rolling tracing log. /// /// /// Recent entries are captured in a memory dump. -/// If DEBUG is defined, all entries written to or -/// are print to output. /// -internal sealed class TraceLog(int logSize, string id, string fileName) +internal sealed class TraceLog(int logSize, string fileName) { - internal readonly struct Arg - { - // To display enums in Expression Evaluator we need to remember the type of the enum. - // The debugger currently does not support evaluating expressions that involve Type instances nor lambdas, - // so we need to manually special case the types of enums we care about displaying. - - private enum EnumType - { - ProjectAnalysisSummary, - RudeEditKind, - ModuleUpdateStatus, - EditAndContinueCapabilities, - } - - private static readonly StrongBox s_ProjectAnalysisSummary = new(EnumType.ProjectAnalysisSummary); - private static readonly StrongBox s_RudeEditKind = new(EnumType.RudeEditKind); - private static readonly StrongBox s_ModuleUpdateStatus = new(EnumType.ModuleUpdateStatus); - private static readonly StrongBox s_EditAndContinueCapabilities = new(EnumType.EditAndContinueCapabilities); - - public readonly object? Object; - public readonly int Int32; - public readonly ImmutableArray Tokens; - - public Arg(object? value) - { - Int32 = -1; - Object = value ?? ""; - Tokens = default; - } - - public Arg(ImmutableArray tokens) - { - Int32 = -1; - Object = null; - Tokens = tokens; - } - - private Arg(int value, StrongBox enumKind) - { - Int32 = value; - Object = enumKind; - Tokens = default; - } - - public object? GetDebuggerDisplay() - => (!Tokens.IsDefault) ? string.Join(",", Tokens.Select(token => token.ToString("X8"))) : - (Object is ImmutableArray array) ? string.Join(",", array) : - (Object is null) ? Int32 : - (Object is StrongBox { Value: var enumType }) ? enumType switch - { - EnumType.ProjectAnalysisSummary => (ProjectAnalysisSummary)Int32, - EnumType.RudeEditKind => (RudeEditKind)Int32, - EnumType.ModuleUpdateStatus => (ModuleUpdateStatus)Int32, - EnumType.EditAndContinueCapabilities => (EditAndContinueCapabilities)Int32, - _ => throw ExceptionUtilities.UnexpectedValue(enumType) - } : - Object; - - public static implicit operator Arg(string? value) => new(value); - public static implicit operator Arg(int value) => new(value); - public static implicit operator Arg(bool value) => new(value ? "true" : "false"); - public static implicit operator Arg(ProjectId value) => new(value.DebugName); - public static implicit operator Arg(DocumentId value) => new(value.DebugName); - public static implicit operator Arg(Diagnostic value) => new(value.ToString()); - public static implicit operator Arg(ProjectAnalysisSummary value) => new((int)value, s_ProjectAnalysisSummary); - public static implicit operator Arg(RudeEditKind value) => new((int)value, s_RudeEditKind); - public static implicit operator Arg(ModuleUpdateStatus value) => new((int)value, s_ModuleUpdateStatus); - public static implicit operator Arg(EditAndContinueCapabilities value) => new((int)value, s_EditAndContinueCapabilities); - public static implicit operator Arg(ImmutableArray tokens) => new(tokens); - public static implicit operator Arg(ImmutableArray items) => new(items); - } - - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal readonly struct Entry(string format, Arg[]? args) - { - public readonly string MessageFormat = format; - public readonly Arg[]? Args = args; - - internal string GetDebuggerDisplay() - => (MessageFormat == null) ? "" : string.Format(MessageFormat, Args?.Select(a => a.GetDebuggerDisplay()).ToArray() ?? []); - } - internal sealed class FileLogger(string logDirectory, TraceLog traceLog) { private readonly string _logDirectory = logDirectory; private readonly TraceLog _traceLog = traceLog; - public void Append(Entry entry) + public void Append(string entry) { string? path = null; try { path = Path.Combine(_logDirectory, _traceLog._fileName); - File.AppendAllLines(path, [entry.GetDebuggerDisplay()]); + File.AppendAllLines(path, [entry]); } catch (Exception e) { @@ -214,8 +135,7 @@ public async ValueTask WriteDocumentChangeAsync(Document? oldDocument, Document? } } - private readonly Entry[] _log = new Entry[logSize]; - private readonly string _id = id; + private readonly string[] _log = new string[logSize]; private readonly string _fileName = fileName; private int _currentLine; @@ -226,37 +146,19 @@ public void SetLogDirectory(string? logDirectory) FileLog = (logDirectory != null) ? new FileLogger(logDirectory, this) : null; } - private void AppendInMemory(Entry entry) + private void AppendInMemory(string entry) { var index = Interlocked.Increment(ref _currentLine); _log[(index - 1) % _log.Length] = entry; } private void AppendFileLoggingErrorInMemory(string? path, Exception e) - => AppendInMemory(new Entry("Error writing log file '{0}': {1}", [new Arg(path), new Arg(e.Message)])); + => AppendInMemory($"Error writing log file '{path}': {e.Message}"); - private void Append(Entry entry) + public void Write(string message, LogMessageSeverity severity = LogMessageSeverity.Info) { - AppendInMemory(entry); - FileLog?.Append(entry); - } - - public void Write(string str) - => Write(str, args: null); - - public void Write(string format, params Arg[]? args) - => Append(new Entry(format, args)); - - [Conditional("DEBUG")] - public void DebugWrite(string str) - => DebugWrite(str, args: null); - - [Conditional("DEBUG")] - public void DebugWrite(string format, params Arg[]? args) - { - var entry = new Entry(format, args); - Append(entry); - Debug.WriteLine(entry.ToString(), _id); + AppendInMemory(message); + FileLog?.Append(message); } internal TestAccessor GetTestAccessor() @@ -264,8 +166,7 @@ internal TestAccessor GetTestAccessor() internal readonly struct TestAccessor(TraceLog traceLog) { - private readonly TraceLog _traceLog = traceLog; - - internal Entry[] Entries => _traceLog._log; + internal string[] Entries => traceLog._log; } } + diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index 09e1958cf7f5f..d12eb72226d49 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -44,19 +44,19 @@ public static bool SupportsEditAndContinue(this Project project, TraceLog? log = { if (project.FilePath == null) { - log?.Write("Project '{0}' (id '{1}') doesn't support EnC: no file path", project.Name, project.Id); + log?.Write($"Project '{project.Name}' ('{project.Id.DebugName}') doesn't support EnC: no file path"); return false; } if (project.Services.GetService() == null) { - log?.Write("Project '{0}' doesn't support EnC: no EnC service", project.FilePath); + log?.Write($"Project '{project.FilePath}' doesn't support EnC: no EnC service"); return false; } if (!project.CompilationOutputInfo.HasEffectiveGeneratedFilesOutputDirectory) { - log?.Write("Project '{0}' doesn't support EnC: no generated files output directory", project.FilePath); + log?.Write($"Project '{project.FilePath}' doesn't support EnC: no generated files output directory"); return false; } diff --git a/src/Features/Test/EditAndContinue/TraceLogTests.cs b/src/Features/Test/EditAndContinue/TraceLogTests.cs deleted file mode 100644 index 72f344f4bc8c0..0000000000000 --- a/src/Features/Test/EditAndContinue/TraceLogTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Linq; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests -{ - public class TraceLogTests - { - [Fact] - public void Write() - { - var log = new TraceLog(5, "log", "File.log"); - - var projectId = ProjectId.CreateFromSerialized(Guid.Parse("5E40F37C-5AB3-495E-A3F2-4A244D177674"), debugName: "MyProject"); - var diagnostic = Diagnostic.Create(EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile), Location.None, "file", "error"); - - log.Write("a"); - log.Write("b {0} {1} 0x{2:X8}", 1, "x", 255); - log.Write("c"); - log.Write("d str={0} projectId={1} summary={2} diagnostic=`{3}`", (string?)null, projectId, ProjectAnalysisSummary.RudeEdits, diagnostic); - log.Write("e"); - log.Write("f"); - - AssertEx.Equal( - [ - "f", - "b 1 x 0x000000FF", - "c", - $"d str= projectId=MyProject summary=RudeEdits diagnostic=`{diagnostic}`", - "e" - ], log.GetTestAccessor().Entries.Select(e => e.GetDebuggerDisplay())); - } - } -} diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestVerifier.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestVerifier.cs index 3cb4688c41dc8..58c16e739857f 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestVerifier.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestVerifier.cs @@ -18,6 +18,8 @@ using Roslyn.Utilities; using Xunit; using static Microsoft.CodeAnalysis.EditAndContinue.AbstractEditAndContinueAnalyzer; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { @@ -44,7 +46,12 @@ internal abstract class EditAndContinueTestVerifier EditAndContinueCapabilities.GenericUpdateMethod | EditAndContinueCapabilities.GenericAddFieldToExistingType; - public abstract AbstractEditAndContinueAnalyzer Analyzer { get; } + public AbstractEditAndContinueAnalyzer Analyzer { get; } + + protected EditAndContinueTestVerifier(Action? faultInjector) + { + Analyzer = CreateAnalyzer(faultInjector, LanguageName); + } public abstract ImmutableArray GetDeclarators(ISymbol method); public abstract string LanguageName { get; } @@ -52,6 +59,19 @@ internal abstract class EditAndContinueTestVerifier public abstract TreeComparer TopSyntaxComparer { get; } public abstract string? TryGetResource(string keyword); + internal static AbstractEditAndContinueAnalyzer CreateAnalyzer(Action? faultInjector, string languageName) + { + var exportProvider = FeaturesTestCompositions.Features.ExportProviderFactory.CreateExportProvider(); + + var analyzer = (AbstractEditAndContinueAnalyzer)exportProvider + .GetExports() + .Single(e => e.Metadata.Language == languageName && e.Metadata.ServiceType == typeof(IEditAndContinueAnalyzer).AssemblyQualifiedName) + .Value; + + analyzer.GetTestAccessor().FaultInjector = faultInjector; + return analyzer; + } + private void VerifyDocumentActiveStatementsAndExceptionRegions( ActiveStatementsDescription description, SyntaxTree oldTree, diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 5b72758a44be3..9dc1d8c1099d7 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -16,26 +16,13 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue + Friend NotInheritable Class VisualBasicEditAndContinueAnalyzer Inherits AbstractEditAndContinueAnalyzer - - Private NotInheritable Class Factory - Implements ILanguageServiceFactory - - - - Public Sub New() - End Sub - - Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService - Return New VisualBasicEditAndContinueAnalyzer(testFaultInjector:=Nothing) - End Function - End Class - - ' Public for testing purposes - Public Sub New(Optional testFaultInjector As Action(Of SyntaxNode) = Nothing) - MyBase.New(testFaultInjector) + + + Public Sub New() End Sub #Region "Syntax Analysis" diff --git a/src/Features/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/Features/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index dcdf8673f37fe..25da7ef68dae2 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -28,11 +28,6 @@ Namespace System.Runtime.CompilerServices End Class End Namespace " - - Friend Shared Function CreateAnalyzer() As VisualBasicEditAndContinueAnalyzer - Return New VisualBasicEditAndContinueAnalyzer() - End Function - Public Enum MethodKind Regular Async @@ -249,7 +244,8 @@ End Namespace src2 As String, Optional kind As MethodKind = MethodKind.Regular) As IEnumerable(Of KeyValuePair(Of SyntaxNode, SyntaxNode)) Dim methodMatch = GetMethodMatch(src1, src2, kind) - Return EditAndContinueTestVerifier.GetMethodMatches(CreateAnalyzer(), methodMatch) + Dim analyzer = EditAndContinueTestVerifier.CreateAnalyzer(faultInjector:=Nothing, LanguageNames.VisualBasic) + Return EditAndContinueTestVerifier.GetMethodMatches(analyzer, methodMatch) End Function Public Shared Function ToMatchingPairs(match As Match(Of SyntaxNode)) As MatchingPairs diff --git a/src/Features/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestVerifier.vb b/src/Features/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestVerifier.vb index 2390a592d9ac0..a414ba2176483 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestVerifier.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestVerifier.vb @@ -3,30 +3,21 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.EditAndContinue -Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Differencing +Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests +Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue Friend NotInheritable Class VisualBasicEditAndContinueTestVerifier Inherits EditAndContinueTestVerifier - Private ReadOnly _analyzer As VisualBasicEditAndContinueAnalyzer - - Public Sub New(Optional faultInjector As Action(Of SyntaxNode) = Nothing) - _analyzer = New VisualBasicEditAndContinueAnalyzer(faultInjector) + Public Sub New() + MyBase.New(faultInjector:=Nothing) End Sub - Public Overrides ReadOnly Property Analyzer As AbstractEditAndContinueAnalyzer - Get - Return _analyzer - End Get - End Property - Public Overrides ReadOnly Property LanguageName As String Get Return LanguageNames.VisualBasic diff --git a/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 93213d9de10e3..fb17fa1e9bf77 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -120,7 +120,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests End Sub Private Shared Async Function AnalyzeDocumentAsync(oldProject As Project, newDocument As Document, Optional activeStatementMap As ActiveStatementsMap = Nothing) As Task(Of DocumentAnalysisResults) - Dim analyzer = New VisualBasicEditAndContinueAnalyzer() + Dim analyzer = oldProject.Services.GetRequiredService(Of IEditAndContinueAnalyzer) Dim baseActiveStatements = AsyncLazy.Create(If(activeStatementMap, ActiveStatementsMap.Empty)) Dim capabilities = AsyncLazy.Create(EditAndContinueTestVerifier.Net5RuntimeCapabilities) Return Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of ActiveStatementLineSpan).Empty, capabilities, CancellationToken.None)