From bee9f73c68953a04c7b808344f1b0d7c58935965 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Sat, 2 Mar 2019 10:52:21 -0800 Subject: [PATCH 1/2] =?UTF-8?q?added=20ability=20to=20clear=20all=20diagno?= =?UTF-8?q?stics=20reproted=20from=20a=20IDiagnosticUpd=E2=80=A6=20(#33805?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added ability to clear all diagnostics reproted from a IDiagnosticUpdateSource previously IDiagnosticUpdateSource has to clear each diagnostics it reported group by group. that was fine for IDiagnosticUpdateSource that supports incremental update, but some source such as EditAndContinue doesn't support incremental update since their errors (emit errors) come and go as a bulk (whole project). when they update, they need to update everything. so tracking things in group for incremental update is unnecessary for such source. the new API (Source.Cleared) make it easy for source to clear all diagnostics at once it produced. * addressing PR feedbacks --- .../EditAndContinueDiagnosticUpdateSource.cs | 1 + .../Diagnostics/DiagnosticServiceTests.cs | 92 ++++++++++++++++--- .../AbstractSquiggleProducerTests.cs | 1 + .../AbstractHostDiagnosticUpdateSource.cs | 1 + .../DefaultDiagnosticAnalyzerService.cs | 1 + .../DiagnosticAnalyzerService_UpdateSource.cs | 13 +++ .../Portable/Diagnostics/DiagnosticService.cs | 73 +++++++++++++-- ...Service_UpdateSourceRegistrationService.cs | 2 + .../Diagnostics/IDiagnosticUpdateSource.cs | 5 + .../ExternalErrorDiagnosticUpdateSource.cs | 1 + .../ExternalDiagnosticUpdateSourceTests.vb | 1 + .../EditAndContinueTestHelper.vb | 1 + .../Portable/Diagnostics/DiagnosticData.cs | 1 - 13 files changed, 174 insertions(+), 19 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs index c6ba95855dbfd..18ea30e5f1b62 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs @@ -40,6 +40,7 @@ public EditAndContinueDiagnosticUpdateSource(IDiagnosticUpdateSourceRegistration public bool SupportGetDiagnostics => false; public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared { add { } remove { } } public ImmutableArray GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) { diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs index 5e1f343a24466..c931f18a067e8 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs @@ -20,17 +20,17 @@ public void TestGetDiagnostics1() { using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic)) { - var set = new ManualResetEvent(false); + var mutex = new ManualResetEvent(false); var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty); var source = new TestDiagnosticUpdateSource(false, null); var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider); diagnosticService.Register(source); - diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); }; + diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); }; var id = Tuple.Create(workspace, document); - var diagnostic = RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id); + var diagnostic = RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id); var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None); Assert.Equal(diagnostic, data1.Single()); @@ -51,7 +51,7 @@ public void TestGetDiagnostics2() { using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic)) { - var set = new ManualResetEvent(false); + var mutex = new ManualResetEvent(false); var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty); var document2 = document.Project.AddDocument("TestDocument2", string.Empty); @@ -59,19 +59,19 @@ public void TestGetDiagnostics2() var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider); diagnosticService.Register(source); - diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); }; + diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); }; var id = Tuple.Create(workspace, document); - RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id); + RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id); var id2 = Tuple.Create(workspace, document.Project, document); - RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id2); + RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id2); - RaiseDiagnosticEvent(set, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2)); + RaiseDiagnosticEvent(mutex, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2)); var id3 = Tuple.Create(workspace, document.Project); - RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, null, id3); - RaiseDiagnosticEvent(set, source, workspace, null, null, Tuple.Create(workspace)); + RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, null, id3); + RaiseDiagnosticEvent(mutex, source, workspace, null, null, Tuple.Create(workspace)); var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None); Assert.Equal(5, data1.Count()); @@ -90,13 +90,75 @@ public void TestGetDiagnostics2() } } + [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] + public void TestCleared() + { + using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic)) + { + var mutex = new ManualResetEvent(false); + var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty); + var document2 = document.Project.AddDocument("TestDocument2", string.Empty); + + var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider); + + var source1 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null); + diagnosticService.Register(source1); + + var source2 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null); + diagnosticService.Register(source2); + + diagnosticService.DiagnosticsUpdated += MarkSet; + + // add bunch of data to the service for both sources + RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document)); + RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document.Project, document)); + RaiseDiagnosticEvent(mutex, source1, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2)); + + RaiseDiagnosticEvent(mutex, source2, workspace, document.Project.Id, null, Tuple.Create(workspace, document.Project)); + RaiseDiagnosticEvent(mutex, source2, workspace, null, null, Tuple.Create(workspace)); + + // confirm data is there. + var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None); + Assert.Equal(5, data1.Count()); + + diagnosticService.DiagnosticsUpdated -= MarkSet; + + // confirm clear for a source + mutex.Reset(); + var count = 0; + diagnosticService.DiagnosticsUpdated += MarkCalled; + + source1.RaiseDiagnosticsClearedEvent(); + + mutex.WaitOne(); + + // confirm there are 2 data left + var data2 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None); + Assert.Equal(2, data2.Count()); + + void MarkCalled(object sender, DiagnosticsUpdatedArgs args) + { + // event is serialized. no concurrent call + if (++count == 3) + { + mutex.Set(); + } + } + + void MarkSet(object sender, DiagnosticsUpdatedArgs args) + { + mutex.Set(); + } + } + } + private static DiagnosticData RaiseDiagnosticEvent(ManualResetEvent set, TestDiagnosticUpdateSource source, TestWorkspace workspace, ProjectId project, DocumentId document, object id) { set.Reset(); var diagnostic = CreateDiagnosticData(workspace, project, document); - source.RaiseUpdateEvent( + source.RaiseDiagnosticsUpdatedEvent( DiagnosticsUpdatedArgs.DiagnosticsCreated(id, workspace, workspace.CurrentSolution, project, document, ImmutableArray.Create(diagnostic))); set.WaitOne(); @@ -126,16 +188,22 @@ public TestDiagnosticUpdateSource(bool support, DiagnosticData[] diagnosticData) public bool SupportGetDiagnostics { get { return _support; } } public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared; public ImmutableArray GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) { return _support ? _diagnosticData : ImmutableArray.Empty; } - public void RaiseUpdateEvent(DiagnosticsUpdatedArgs args) + public void RaiseDiagnosticsUpdatedEvent(DiagnosticsUpdatedArgs args) { DiagnosticsUpdated?.Invoke(this, args); } + + public void RaiseDiagnosticsClearedEvent() + { + DiagnosticsCleared?.Invoke(this, EventArgs.Empty); + } } } } diff --git a/src/EditorFeatures/TestUtilities/Squiggles/AbstractSquiggleProducerTests.cs b/src/EditorFeatures/TestUtilities/Squiggles/AbstractSquiggleProducerTests.cs index 10775140a1579..1f3331d335f4f 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/AbstractSquiggleProducerTests.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/AbstractSquiggleProducerTests.cs @@ -89,6 +89,7 @@ public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args) } public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared { add { } remove { } } public bool SupportGetDiagnostics => false; diff --git a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index 9eecd44fdc285..6a2729d875c7e 100644 --- a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -28,6 +28,7 @@ public ImmutableArray GetDiagnostics(Workspace workspace, Projec } public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared { add { } remove { } } public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args) { diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index baf725521cf52..b497fd2347816 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -40,6 +40,7 @@ public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) } public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared { add { } remove { } } // this only support push model, pull model will be provided by DiagnosticService by caching everything this one pushed public bool SupportGetDiagnostics => false; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs index cecc609ead01b..9553d1430f705 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs @@ -43,6 +43,19 @@ public event EventHandler DiagnosticsUpdated } } + public event EventHandler DiagnosticsCleared + { + add + { + // don't do anything. this update source doesn't use cleared event + } + + remove + { + // don't do anything. this update source doesn't use cleared event + } + } + internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args) { // all diagnostics events are serialized. diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs index b8fcf82da1e14..9c26b1353558d 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs @@ -55,11 +55,8 @@ public event EventHandler DiagnosticsUpdated } } - private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args) + private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args) { - Contract.ThrowIfNull(sender); - var source = (IDiagnosticUpdateSource)sender; - var ev = _eventMap.GetEventHandlers>(DiagnosticsUpdatedEventName); if (!RequireRunningEventTasks(source, ev)) { @@ -75,7 +72,35 @@ private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args) return; } - ev.RaiseEvent(handler => handler(sender, args)); + ev.RaiseEvent(handler => handler(source, args)); + }).CompletesAsyncOperation(eventToken); + } + + private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source) + { + var ev = _eventMap.GetEventHandlers>(DiagnosticsUpdatedEventName); + if (!RequireRunningEventTasks(source, ev)) + { + return; + } + + var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName); + _eventQueue.ScheduleTask(() => + { + using (var pooledObject = SharedPools.Default>().GetPooledObject()) + { + var removed = pooledObject.Object; + if (!ClearDiagnosticsReportedBySource(source, removed)) + { + // there is no change, nothing to raise events for. + return; + } + + foreach (var args in removed) + { + ev.RaiseEvent(handler => handler(source, args)); + } + } }).CompletesAsyncOperation(eventToken); } @@ -150,10 +175,46 @@ private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArg } } + private bool ClearDiagnosticsReportedBySource(IDiagnosticUpdateSource source, List removed) + { + // we expect source who uses this ability to have small number of diagnostics. + lock (_gate) + { + Debug.Assert(_updateSources.Contains(source)); + + // 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to + // distinguish them. so we separate diagnostics per workspace map. + if (!_map.TryGetValue(source, out var workspaceMap)) + { + return false; + } + + foreach (var (workspace, map) in workspaceMap) + { + foreach (var (id, data) in map) + { + removed.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved(id, data.Workspace, solution: null, data.ProjectId, data.DocumentId)); + } + } + + // all diagnostics from the source is cleared + _map.Remove(source); + return true; + } + } + private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e) { AssertIfNull(e.Diagnostics); - RaiseDiagnosticsUpdated(sender, e); + + // all events are serialized by async event handler + RaiseDiagnosticsUpdated((IDiagnosticUpdateSource)sender, e); + } + + private void OnCleared(object sender, EventArgs e) + { + // all events are serialized by async event handler + RaiseDiagnosticsCleared((IDiagnosticUpdateSource)sender); } public IEnumerable GetDiagnostics( diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs index 9a60783de5ed2..3af973c48eacc 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs @@ -28,7 +28,9 @@ public void Register(IDiagnosticUpdateSource source) } _updateSources = _updateSources.Add(source); + source.DiagnosticsUpdated += OnDiagnosticsUpdated; + source.DiagnosticsCleared += OnCleared; } } } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs index fc770270d8903..30b11285d702f 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs @@ -16,6 +16,11 @@ internal interface IDiagnosticUpdateSource /// event EventHandler DiagnosticsUpdated; + /// + /// Raise this when all diagnostics reported from this update source has cleared + /// + event EventHandler DiagnosticsCleared; + /// /// Return true if the source supports GetDiagnostics API otherwise, return false so that the engine can cache data from DiagnosticsUpdated in memory /// diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 4ae7e4c35806e..fee8e439c23a5 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -76,6 +76,7 @@ internal ExternalErrorDiagnosticUpdateSource( public event EventHandler BuildStarted; public event EventHandler DiagnosticsUpdated; + public event EventHandler DiagnosticsCleared { add { } remove { } } public bool IsInProgress => BuildInprogressState != null; diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 4e5e775fe46bc..afcf6d08c87ec 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -346,6 +346,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Property Public Event DiagnosticsUpdated As EventHandler(Of DiagnosticsUpdatedArgs) Implements IDiagnosticUpdateSource.DiagnosticsUpdated + Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared Public Function GetDiagnostics(workspace As Microsoft.CodeAnalysis.Workspace, projectId As ProjectId, documentId As DocumentId, id As Object, includeSuppressedDiagnostics As Boolean, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticData) Implements IDiagnosticUpdateSource.GetDiagnostics Return If(includeSuppressedDiagnostics, _data, _data.WhereAsArray(Function(d) Not d.IsSuppressed)) diff --git a/src/VisualStudio/Core/Test/EditAndContinue/EditAndContinueTestHelper.vb b/src/VisualStudio/Core/Test/EditAndContinue/EditAndContinueTestHelper.vb index 7e8a37df14742..adfaac06a2c8d 100644 --- a/src/VisualStudio/Core/Test/EditAndContinue/EditAndContinueTestHelper.vb +++ b/src/VisualStudio/Core/Test/EditAndContinue/EditAndContinueTestHelper.vb @@ -43,6 +43,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.EditAndContinue End Property Public Event DiagnosticsUpdated As EventHandler(Of DiagnosticsUpdatedArgs) Implements IDiagnosticUpdateSource.DiagnosticsUpdated + Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared Public Function GetDiagnostics(workspace As Microsoft.CodeAnalysis.Workspace, projectId As ProjectId, documentId As DocumentId, id As Object, includeSuppressedDiagnostics As Boolean, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticData) Implements IDiagnosticUpdateSource.GetDiagnostics Return If(includeSuppressedDiagnostics, _data, _data.WhereAsArray(Function(d) Not d.IsSuppressed)) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index 9b775ac2cef41..e557e50cbc1a1 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -444,7 +444,6 @@ private static ImmutableDictionary GetProperties( return additionalProperties == null ? properties : properties.AddRange(additionalProperties); - throw new NotImplementedException(); } /// From 880d35aa5ae0227c161769e9415cb766dca856d3 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sun, 3 Mar 2019 00:46:20 +0100 Subject: [PATCH 2/2] Text on a InterpolatedVerbatimStringStartToken token (#33747) --- .../CSharp/Portable/Syntax/SyntaxKindFacts.cs | 2 + .../Emit/CodeGen/CodeGenInterpolatedString.cs | 39 +++++++++++++++++++ .../Test/Syntax/Syntax/SyntaxFactoryTests.cs | 8 ++++ 3 files changed, 49 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index 43b840b58fb67..654f4c7c1630e 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -1630,6 +1630,8 @@ public static string GetText(SyntaxKind kind) return "$\""; case SyntaxKind.InterpolatedStringEndToken: return "\""; + case SyntaxKind.InterpolatedVerbatimStringStartToken: + return "$@\""; case SyntaxKind.UnderscoreToken: return "_"; case SyntaxKind.VarKeyword: diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInterpolatedString.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInterpolatedString.cs index d7c679a0841d6..8a12283eabc9f 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInterpolatedString.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInterpolatedString.cs @@ -1,12 +1,51 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { public class InterpolatedStringTests : CSharpTestBase { + [Fact, WorkItem(33713, "https://github.com/dotnet/roslyn/issues/33713")] + public void AlternateVerbatimString() + { + var source = @" +class C +{ + static void Main() + { + int i = 42; + var s = @$""{i} +{i}""; + System.Console.Write(s); + var s2 = $@""""; + } +}"; + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: @"42 +42"); + + var tree = comp.SyntaxTrees.Single(); + var interpolatedStrings = tree.GetRoot().DescendantNodes().OfType().ToArray(); + var token1 = interpolatedStrings[0].StringStartToken; + Assert.Equal("@$\"", token1.Text); + Assert.Equal("@$\"", token1.ValueText); + + var token2 = interpolatedStrings[1].StringStartToken; + Assert.Equal("$@\"", token2.Text); + Assert.Equal("$@\"", token2.ValueText); + + foreach (var token in tree.GetRoot().DescendantTokens().Where(t => t.Kind() != SyntaxKind.EndOfFileToken)) + { + Assert.False(string.IsNullOrEmpty(token.Text)); + Assert.False(string.IsNullOrEmpty(token.ValueText)); + } + } + [Fact] public void ConstInterpolations() { diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxFactoryTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxFactoryTests.cs index 27ee71b4d3bf9..49190473cec84 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxFactoryTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxFactoryTests.cs @@ -15,6 +15,14 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class SyntaxFactoryTests : CSharpTestBase { + [Fact, WorkItem(33713, "https://github.com/dotnet/roslyn/issues/33713")] + public void AlternateVerbatimString() + { + var token = SyntaxFactory.Token(SyntaxKind.InterpolatedVerbatimStringStartToken); + Assert.Equal("$@\"", token.Text); + Assert.Equal("$@\"", token.ValueText); + } + [Fact] public void SyntaxTree() {