/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb
698 строк
24 KB
Tomáš Matoušek
EnC support for project level changes (#79239)
17 июл 2025, 00:59
Не верифицирован
17 июл 2025, 00:59
c5325ab
Код
Авторство
О чём код?
' 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. Imports System.Collections.Immutable Imports System.IO Imports System.Threading Imports Microsoft.CodeAnalysis.Contracts.EditAndContinue Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Text Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests <[UseExportProvider]> Public Class VisualBasicEditAndContinueAnalyzerTests #Region "Helpers" Private Shared Function CreateWorkspace() As TestWorkspace Return New TestWorkspace() End Function Private Shared Function AddDefaultTestProject(solution As Solution, source As String) As Solution Return solution. AddTestProject("proj", LanguageNames.VisualBasic). AddTestDocument(source, path:=Path.Combine(TempRoot.Root, "test.vb")).Project.Solution End Function Private Shared Sub TestSpans(source As String, hasLabel As Func(Of SyntaxNode, Boolean)) Dim tree = SyntaxFactory.ParseSyntaxTree(ClearSource(source)) For Each expected In GetExpectedPositionsAndSpans(source) Dim expectedSpan = expected.Value Dim expectedText As String = source.Substring(expectedSpan.Start, expectedSpan.Length) Dim node = tree.GetRoot().FindToken(expected.Key).Parent While Not hasLabel(node) node = node.Parent End While Dim actualSpan = VisualBasicEditAndContinueAnalyzer.TryGetDiagnosticSpanImpl(node.Kind, node, EditKind.Update).Value Dim actualText = source.Substring(actualSpan.Start, actualSpan.Length) Assert.True(expectedSpan = actualSpan, vbCrLf & "Expected span: '" & expectedText & "' " & expectedSpan.ToString() & vbCrLf & "Actual span: '" & actualText & "' " & actualSpan.ToString()) Next End Sub Private Const s_startTag As String = "<span>" Private Const s_endTag As String = "</span>" Private Const s_startSpanMark As String = "[|" Private Const s_endSpanMark As String = "|]" Private Const s_positionMark As Char = "$"c Private Shared Function ClearSource(source As String) As String Return source. Replace(s_startTag, New String(" "c, s_startTag.Length)). Replace(s_endTag, New String(" "c, s_endTag.Length)). Replace(s_startSpanMark, New String(" "c, s_startSpanMark.Length)). Replace(s_endSpanMark, New String(" "c, s_startSpanMark.Length)). Replace(s_positionMark, " "c) End Function Private Shared Iterator Function GetExpectedPositionsAndSpans(source As String) As IEnumerable(Of KeyValuePair(Of Integer, TextSpan)) Dim i As Integer = 0 While True Dim start As Integer = source.IndexOf(s_startTag, i, StringComparison.Ordinal) If start = -1 Then Exit While End If start += s_startTag.Length Dim [end] As Integer = source.IndexOf(s_endTag, start + 1, StringComparison.Ordinal) Dim length = [end] - start Dim position = source.IndexOf(s_positionMark, start, length) Dim span As TextSpan If position < 0 Then position = start span = New TextSpan(start, length) Else position += 1 span = TextSpan.FromBounds(source.IndexOf(s_startSpanMark, start, length, StringComparison.Ordinal) + s_startSpanMark.Length, source.IndexOf(s_endSpanMark, start, length, StringComparison.Ordinal)) End If Yield KeyValuePair.Create(position, span) i = [end] + 1 End While End Function Private Shared Sub TestErrorSpansAllKinds(hasLabel As Func(Of SyntaxKind, Boolean)) Dim unhandledKinds As List(Of SyntaxKind) = New List(Of SyntaxKind)() For Each k In [Enum].GetValues(GetType(SyntaxKind)).Cast(Of SyntaxKind)().Where(hasLabel) Dim span As TextSpan? Try span = VisualBasicEditAndContinueAnalyzer.TryGetDiagnosticSpanImpl(k, Nothing, EditKind.Update) #Disable Warning IDE0059 ' Unnecessary assignment of a value - https://github.com/dotnet/roslyn/issues/45896 Catch e1 As NullReferenceException #Enable Warning IDE0059 ' Unnecessary assignment of a value ' expected, we passed null node Continue For End Try ' unexpected If span Is Nothing Then unhandledKinds.Add(k) End If Next AssertEx.Equal(Array.Empty(Of SyntaxKind)(), unhandledKinds) End Sub Private Shared Async Function AnalyzeDocumentAsync(oldProject As Project, newDocument As Document, Optional activeStatementMap As ActiveStatementsMap = Nothing) As Task(Of DocumentAnalysisResults) Dim analyzer = oldProject.Services.GetRequiredService(Of IEditAndContinueAnalyzer) Dim baseActiveStatements = AsyncLazy.Create(If(activeStatementMap, ActiveStatementsMap.Empty)) Dim capabilities = AsyncLazy.Create(EditAndContinueTestVerifier.Net5RuntimeCapabilities) Dim log = New TraceLog("Test") Return Await analyzer.AnalyzeDocumentAsync(newDocument.Id, oldProject, newDocument.Project, baseActiveStatements, ImmutableArray(Of ActiveStatementLineSpan).Empty, capabilities, log, CancellationToken.None) End Function #End Region <Fact> Public Sub ErrorSpans_TopLevel() Dim source = " <span>Option Strict Off</span> <span>Imports Z = Goo.Bar</span> <<span>Assembly: A(1,2,3,4)</span>, <span>B</span>> <span>Namespace N.M</span> End Namespace <A, B> Structure S(Of <span>T</span> As {New, Class, I}) Inherits B End Structure Structure S(Of <span>T</span> As {New, Class, I}) End Structure Structure S(Of <span>T</span> As {New, Class, I}) End Structure <A, B> <span>Public MustInherit Partial Class C</span> End Class <span>Interface I</span> Implements J, K, L End Interface <A> <span>Enum E1</span> End Enum <span>Enum E2</span> As UShort End Enum <span>Public Enum E3</span> Q <A>R = 3 End Enum <A> <span>Public Delegate Sub D1(Of T As Struct)()</span> <span>Delegate Function D2()</span> As C(Of T) <span>Delegate Function D2</span> As C(Of T) <span>Delegate Sub D2</span> As C(Of T) <Attrib> <span>Public MustInherit Class Z</span> <span>Dim f0 As Integer</span> <span>Dim WithEvents EClass As New EventClass</span> <A><span>Dim f1 = 1</span> <A><span>Dim f2 As Integer = 1</span> Private <span>f3()</span>, <span>f4?</span>, <span>f5</span> As Integer, <span>f6</span> As New C() <span>Public Shared ReadOnly f As Integer</span> <A><span>Function M1()</span> As Integer End Function <span>Function M2()</span> As Integer Implements I.Goo End Function <span>Function M3()</span> As Integer Handles I.E End Function <span>Private Function M4(Of S, T)()</span> As Integer Handles I.E End Function <span>MustOverride Function M5</span> Sub M6(<A><span>Optional p1 As Integer = 2131</span>, <span>p2</span> As Integer, <span>p3</span>, <Out><span>ByRef p3</span>, <span>ParamArray x() As Integer</span>) End Sub <A><span>Event E1</span> As A <A><span>Private Event E1</span> As A <A><span>Property P</span> As Integer <A><span>Public MustOverride Custom Event E3</span> As A <A><span>AddHandler(value As Action)</span> End AddHandler <A><span>RemoveHandler(value As Action)</span> End RemoveHandler <A><span>RaiseEvent</span> End RaiseEvent End Event <A><span>Property P</span> As Integer <A><span>Get</span> End Get <A><span>Private Set(value As Integer)</span> End Set End Property <A><span>Public Shared Narrowing Operator CType(d As Z)</span> As Integer End Operator End Class " TestSpans(source, Function(node) SyntaxComparer.TopLevel.HasLabel(node)) End Sub <Fact> Public Sub ErrorSpans_StatementLevel_Update() Dim source = " Class C Sub M() <span>While expr</span> <span>Continue While</span> <span>Exit While</span> End While <span>Do</span> <span>Continue Do</span> <span>Exit Do</span> Loop While expr <span>Do</span> Loop <span>Do Until expr</span> Loop <span>Do While expr</span> Loop <span>For Each a In b</span> <span>Continue For</span> <span>Exit For</span> Next <span>For i = 1 To 10 Step 2</span> Next <span>Using expr</span> End Using <span>SyncLock expr</span> End SyncLock <span>With z</span> <span>.M()</span> End With <span>label:</span> <span>F()</span> <span>Static a As Integer = 1</span> <span>Dim b = 2</span> <span>Const c = 4</span> Dim <span>c</span>, <span>d</span> As New C() <span>GoTo label</span> <span>Stop</span> <span>End</span> <span>Exit Sub</span> <span>Return</span> <span>Return 1</span> <span>Throw</span> <span>Throw expr</span> <span>Try</span> <span>Exit Try</span> <span>Catch</span> End Try <span>Try</span> <span>Catch e As E</span> End Try <span>Try</span> <span>Catch e As E When True</span> End Try <span>Try</span> <span>Finally</span> End Try <span>If expr Then</span> End If <span>If expr</span> <span>ElseIf expr</span> <span>Else</span> End If <span>If True Then</span> M1() <span>Else</span> M2() <span>Select expr</span> <span>Case 1</span> End Select <span>Select expr</span> <span>Case 1</span> <span>GoTo 1</span> End Select <span>Select expr</span> <span>Case 1</span> <span>GoTo label</span> <span>Case 4 To 9</span> <span>Exit Select</span> <span>Case = 2</span> <span>Case < 2</span> <span>Case > 2</span> <span>Case Else</span> <span>Return</span> End Select <span>On Error Resume Next</span> <span>On Error Goto 0</span> <span>On Error Goto -1</span> <span>On Error GoTo label</span> <span>Resume</span> <span>Resume Next</span> <span>AddHandler e, AddressOf M</span> <span>RemoveHandler e, AddressOf M</span> <span>RaiseEvent e()</span> <span>Error 1</span> <span>Mid(a, 1, 2) = """"</span> <span>a = b</span> <span>Call M()</span> <span>Dim intArray(10, 10, 10) As Integer</span> <span>ReDim Preserve intArray(10, 10, 20)</span> <span>ReDim Preserve intArray(10, 10, 15)</span> <span>ReDim intArray(10, 10, 10)</span> <span>Erase intArray</span> F(<span>Function(x)</span> x) F(<span>Sub(x)</span> M()) F(<span>[|Aggregate|] z $In {1, 2, 3}</span> Into <span>Max(z)</span>) F(<span>[|From|] z $In {1, 2, 3}</span>? Order By <span>z Descending</span>, <span>z Ascending</span>, <span>z</span> <span>[|Take While|] z $> 0</span>) F(From z1 In {1, 2, 3} <span>[|Join|] z2 $In {1, 2, 3}</span> On z1 Equals z2 <span>[|Select|] z1 $+ z2</span>) F(From z1 In {1, 2, 3} Join z2 In {1, 2, 3} On <span>$z1 [|Equals|] z2</span> Select z1 + z2) F(From z1 In {1, 2, 3} Join z2 In {1, 2, 3} On <span>z1 [|Equals|] $z2</span> Select z1 + z2) F(From a In b <span>[|Let|] x = $expr</span> Select expr) F(From a In b <span>[|Group|] $a1 By b2 Into z1</span> Select d1) F(From a In b <span>[|Group|] a1 By $b2 Into z2</span> Select d2) F(From a In b Group a1 By b2 Into <span>z3</span> Select d3) F(From cust In A <span>[|Group Join|] ord In $B On z4 Equals y4 Into CustomerOrders = Group, OrderTotal = Sum(ord.Total + 4)</span> Select 1) F(From cust In A <span>Group Join ord In B On $z5 [|Equals|] y5 Into CustomerOrders = Group, OrderTotal = Sum(ord.Total + 3)</span> Select 1) F(From cust In A <span>Group Join ord In B On z6 [|Equals|] $y6 Into CustomerOrders = Group, OrderTotal = Sum(ord.Total + 2)</span> Select 1) F(From cust In A Group Join ord In B On z7 Equals y7 Into CustomerOrders = Group, OrderTotal = <span>Sum(ord.Total + 1)</span> Select 1) End Sub End Class " TestSpans(source, AddressOf SyntaxComparer.Statement.HasLabel) source = " Class C Iterator Function M() As IEnumerable(Of Integer) <span>Yield 1</span> End Function End Class " TestSpans(source, AddressOf SyntaxComparer.Statement.HasLabel) source = " Class C Async Function M() As Task(Of Integer) <span>Await</span> expr End Function End Class " TestSpans(source, AddressOf SyntaxComparer.Statement.HasLabel) End Sub ''' <summary> ''' Verifies that <see cref="VisualBasicEditAndContinueAnalyzer.TryGetDiagnosticSpanImpl"/> handles all <see cref="SyntaxKind"/> s. ''' </summary> <Fact> Public Sub ErrorSpansAllKinds() TestErrorSpansAllKinds(Function(kind) SyntaxComparer.Statement.HasLabel(kind, ignoreVariableDeclarations:=False)) TestErrorSpansAllKinds(Function(kind) SyntaxComparer.TopLevel.HasLabel(kind, ignoreVariableDeclarations:=False)) End Sub <Fact> Public Async Function AnalyzeDocumentAsync_InsignificantChangesInMethodBody() As Task Dim source1 = " Class C Sub Main() System.Console.WriteLine(1) End Sub End Class " Dim source2 = " Class C Sub Main() System.Console.WriteLine(1) End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.First() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim oldText = Await oldDocument.GetTextAsync() Dim oldSyntaxRoot = Await oldDocument.GetSyntaxRootAsync() Dim newDocument = newSolution.GetDocument(documentId) Dim newText = Await newDocument.GetTextAsync() Dim newSyntaxRoot = Await newDocument.GetSyntaxRootAsync() Dim oldStatementSource = "System.Console.WriteLine(1)" Dim oldStatementPosition = source1.IndexOf(oldStatementSource, StringComparison.Ordinal) Dim oldStatementTextSpan = New TextSpan(oldStatementPosition, oldStatementSource.Length) Dim oldStatementSpan = oldText.Lines.GetLinePositionSpan(oldStatementTextSpan) Dim oldStatementSyntax = oldSyntaxRoot.FindNode(oldStatementTextSpan) Dim baseActiveStatements = New ActiveStatementsMap( ImmutableDictionary.CreateRange( { KeyValuePair.Create(newDocument.FilePath, ImmutableArray.Create( New ActiveStatement( New ActiveStatementId(0), ActiveStatementFlags.LeafFrame, New SourceFileSpan(newDocument.FilePath, oldStatementSpan), instructionId:=Nothing))) }), ActiveStatementsMap.Empty.InstructionMap) Dim result = Await AnalyzeDocumentAsync(oldProject, newDocument, baseActiveStatements) Assert.True(result.HasChanges) Dim syntaxMaps = result.SemanticEdits(0).SyntaxMaps Assert.True(syntaxMaps.HasMap) Dim newStatementSpan = result.ActiveStatements(0).Span Dim newStatementTextSpan = newText.Lines.GetTextSpan(newStatementSpan) Dim newStatementSyntax = newSyntaxRoot.FindNode(newStatementTextSpan) Dim oldStatementSyntaxMapped = syntaxMaps.MatchingNodes(newStatementSyntax) Assert.Same(oldStatementSyntax, oldStatementSyntaxMapped) End Using End Function <Fact> Public Async Function AnalyzeDocumentAsync_SyntaxError_NoChange1() As Task Dim source = " Class C Public Shared Sub Main() System.Console.WriteLine(1 ' syntax error End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source) Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim result = Await AnalyzeDocumentAsync(oldProject, oldDocument) Assert.False(result.HasChanges) Assert.False(result.AnalysisBlocked) Assert.False(result.HasBlockingRudeEdits) Assert.Null(result.SyntaxError) End Using End Function <Fact> Public Async Function AnalyzeDocumentAsync_SyntaxError_NoChange2() As Task Dim source1 = " Class C Public Shared Sub Main() System.Console.WriteLine(1 ' syntax error End Sub End Class " Dim source2 = " Class C Public Shared Sub Main() System.Console.WriteLine(1 ' syntax error End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) Assert.False(result.HasChanges) Assert.False(result.AnalysisBlocked) Assert.False(result.HasBlockingRudeEdits) Assert.Null(result.SyntaxError) End Using End Function <Fact> Public Async Function AnalyzeDocumentAsync_SemanticError_NoChange() As Task Dim source = " Class C Public Shared Sub Main() System.Console.WriteLine(1) Bar() End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source) Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim result = Await AnalyzeDocumentAsync(oldProject, oldDocument) Assert.False(result.HasChanges) Assert.False(result.AnalysisBlocked) Assert.False(result.HasBlockingRudeEdits) Assert.Null(result.SyntaxError) End Using End Function <Fact, WorkItem("https://github.com/dotnet/roslyn/issues/10683")> Public Async Function AnalyzeDocumentAsync_SemanticErrorInMethodBody_Change() As Task Dim source1 = " Class C Public Shared Sub Main() System.Console.WriteLine(1) Bar() End Sub End Class " Dim source2 = " Class C Public Shared Sub Main() System.Console.WriteLine(2) Bar() End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) ' no declaration errors (error in method body is only reported when emitting) Assert.False(result.AnalysisBlocked) Assert.False(result.HasBlockingRudeEdits) Assert.Null(result.SyntaxError) End Using End Function <Fact, WorkItem("https://github.com/dotnet/roslyn/issues/10683")> Public Async Function AnalyzeDocumentAsync_SemanticErrorInDeclaration_Change() As Task Dim source1 = " Class C Public Shared Sub Main(x As Bar) System.Console.WriteLine(1) End Sub End Class " Dim source2 = " Class C Public Shared Sub Main(x As Bar) System.Console.WriteLine(2) End Sub End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim documentId = oldProject.Documents.Single().Id Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) Assert.True(result.HasChanges) ' No errors reported: EnC analyzer is resilient against semantic errors. ' They will be reported by 1) compiler diagnostic analyzer 2) when emitting delta - if still present. Assert.False(result.AnalysisBlocked) Assert.False(result.HasBlockingRudeEdits) Assert.Null(result.SyntaxError) End Using End Function <Fact> Public Async Function AnalyzeDocumentAsync_AddingNewFile() As Task Dim source1 = " Namespace N Class C Public Shared Sub Main() End Sub End Class End Namespace " Dim source2 = " Class D End Class " Using workspace = CreateWorkspace() Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim newDocId = DocumentId.CreateNewId(oldProject.Id) Dim newSolution = oldSolution.AddDocument(newDocId, "goo.vb", SourceText.From(source2), filePath:=Path.Combine(TempRoot.Root, "goo.vb")) Dim newProject = newSolution.Projects.Single() Dim changes = newProject.GetChanges(oldProject) Assert.Equal(2, newProject.Documents.Count()) Assert.Equal(0, changes.GetChangedDocuments().Count()) Assert.Equal(1, changes.GetAddedDocuments().Count()) Dim changedDocuments = changes.GetChangedDocuments().Concat(changes.GetAddedDocuments()) Dim result = New List(Of DocumentAnalysisResults)() For Each changedDocumentId In changedDocuments result.Add(Await AnalyzeDocumentAsync(oldProject, newProject.GetDocument(changedDocumentId))) Next Assert.True(result.IsSingle()) Assert.Empty(result.Single().RudeEdits) End Using End Function End Class End Namespace