diff --git a/BuildAndTest.proj b/BuildAndTest.proj index 385739580af0a..f2fac6687f7a7 100644 --- a/BuildAndTest.proj +++ b/BuildAndTest.proj @@ -7,57 +7,51 @@ $(MSBuildThisFileDirectory)Roslyn.sln $(MSBuildThisFileDirectory)src\Samples\Samples.sln + true Debug $(RunTestArgs) -xml $(RunTestArgs) -test64 $(RunTestArgs) -testVsi + $(RunTestArgs) -trait:Feature=NetCore $(RunTestArgs) -trait:$(Trait) $(RunTestArgs) -notrait:$(NoTrait) + false *.UnitTests.dll *.IntegrationTests.dll $(MSBuildThisFileDirectory)Binaries\$(Configuration)\ $(RunTestArgs) -log:"$(OutputDirectory)\runtests.log" $(OutputDirectory)\CoreClrTest - - RestorePackages=false; - TreatWarningsAsErrors=true; - DeployExtension=false; - @@ -81,6 +75,7 @@ @@ -115,7 +110,7 @@ - + $(NuGetPackageRoot)\roslyntools.microsoft.vsixexpinstaller\$(RoslynToolsMicrosoftVSIXExpInstallerVersion)\tools\VsixExpInstaller.exe diff --git a/README.md b/README.md index 09374138191bd..192a511459271 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ |**dev16**|[![Build Status](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_debug_unit32/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_debug_unit32/)|[![Build Status](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_debug_unit64/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_debug_unit64/)|[![Build Status](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_release_unit32/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_release_unit32/)|[![Build Status](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_release_unit64/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_release_unit64/)|[![Build Status](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_determinism/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_determinism/)|[![Build Status](https://ci.dot.net/buildStatus/icon?job=dotnet_roslyn/dev16/windows_debug_vs-integration)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_debug_vs-integration/)|[![Build Status](https://ci.dot.net/buildStatus/icon?job=dotnet_roslyn/dev16/windows_release_vs-integration)](https://ci.dot.net/job/dotnet_roslyn/job/dev16/job/windows_release_vs-integration/)| ### Linux/Mac - Unit Tests -|Branch|Ubuntu14|Ubuntu16|MacOSX| +|Branch|Ubuntu14|Ubuntu16|macOS| |:--:|:--:|:--:|:--:| |**master**|[![BuildStatus](https://ci.dot.net/job/dotnet_roslyn/job/master/job/ubuntu_14_debug/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/master/job/ubuntu_14_debug/)|[![BuildStatus](https://ci.dot.net/job/dotnet_roslyn/job/master/job/ubuntu_16_debug/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/master/job/ubuntu_16_debug/)|[![BuildStatus](https://ci.dot.net/job/dotnet_roslyn/job/master/job/mac_debug/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/master/job/mac_debug/)| |**dev15.0.x**|[![BuildStatus](https://ci.dot.net/job/dotnet_roslyn/job/dev15.0.x/job/linux_debug/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev15.0.x/job/linux_debug/)||[![BuildStatus](https://ci.dot.net/job/dotnet_roslyn/job/dev15.0.x/job/mac_debug/badge/icon)](https://ci.dot.net/job/dotnet_roslyn/job/dev15.0.x/job/mac_debug/)| diff --git a/build/Targets/Imports.targets b/build/Targets/Imports.targets index 6aaa705f52acc..6a54279474a86 100644 --- a/build/Targets/Imports.targets +++ b/build/Targets/Imports.targets @@ -133,6 +133,8 @@ true false false + + $(NuGetPackageRoot)\Microsoft.DotNet.IBCMerge\$(MicrosoftDotNetIBCMerge)\lib\net45\ibcmerge.exe @@ -423,16 +425,24 @@ - + + + + ConsoleToMSBuild="true" + Condition="Exists('$(IbcMergePath)')"> @@ -440,22 +450,18 @@ - - - + Condition="'$(_IsAnyPortableUnitTest)' == 'true' AND '$(DeveloperBuild)' == 'true' AND '$(OS)' == 'Windows_NT'"> + + + + + + + + + + + - + add to 'defines' - If defines.ContainsKey(symbolName) Then - defines = defines.Remove(symbolName) - End If - defines = defines.Add(symbolName, value) + defines = defines.SetItem(symbolName, value) ElseIf tokens.Current.Kind = SyntaxKind.CommaToken OrElse tokens.Current.Kind = SyntaxKind.ColonToken OrElse tokens.Current.Kind = SyntaxKind.EndOfFileToken Then ' We have no value being assigned, so we'll just assign it to true - If defines.ContainsKey(symbolName) Then - defines = defines.Remove(symbolName) - End If - defines = defines.Add(symbolName, InternalSyntax.CConst.Create(True)) + defines = defines.SetItem(symbolName, InternalSyntax.CConst.Create(True)) ElseIf tokens.Current.Kind = SyntaxKind.BadToken Then GetErrorStringForRemainderOfConditionalCompilation(tokens, parsedTokensAsString) diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index 3e3947ff04456..19c0e78b86ca6 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -245,7 +245,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Debug.Assert(tree IsNot VisualBasicSyntaxTree.Dummy) Debug.Assert(tree.IsMyTemplate) - Interlocked.CompareExchange(_lazyMyTemplate, tree, VisualBasicSyntaxTree.Dummy) + Interlocked.CompareExchange(Of SyntaxTree)(_lazyMyTemplate, tree, VisualBasicSyntaxTree.Dummy) Else ' we need to make one. Dim text As String = EmbeddedResources.VbMyTemplateText @@ -260,7 +260,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Throw ExceptionUtilities.Unreachable End If - If Interlocked.CompareExchange(_lazyMyTemplate, tree, VisualBasicSyntaxTree.Dummy) Is VisualBasicSyntaxTree.Dummy Then + If Interlocked.CompareExchange(Of SyntaxTree)(_lazyMyTemplate, tree, VisualBasicSyntaxTree.Dummy) Is VisualBasicSyntaxTree.Dummy Then ' set global cache s_myTemplateCache(parseOptions) = tree End If diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index 32e5fa5769498..815b55c770ffa 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -134,7 +134,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit typesByName.Add(type.Name, type) End If Next - Interlocked.CompareExchange(Me._lazyTopLevelTypes, typesByName, Nothing) + Interlocked.CompareExchange(Of IReadOnlyDictionary(Of String, Cci.INamespaceTypeDefinition))(Me._lazyTopLevelTypes, typesByName, Nothing) End If Return Me._lazyTopLevelTypes End Function diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceLambdaSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceLambdaSymbol.vb index 8b2c68f7619ea..15b986a2f43a5 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceLambdaSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceLambdaSymbol.vb @@ -55,7 +55,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Get If Me._lazyAnonymousDelegateSymbol Is ErrorTypeSymbol.UnknownResultType Then Dim newValue As NamedTypeSymbol = MakeAssociatedAnonymousDelegate() - Dim oldValue As NamedTypeSymbol = Interlocked.CompareExchange(Me._lazyAnonymousDelegateSymbol, newValue, ErrorTypeSymbol.UnknownResultType) + Dim oldValue As NamedTypeSymbol = Interlocked.CompareExchange(Of NamedTypeSymbol)(Me._lazyAnonymousDelegateSymbol, newValue, ErrorTypeSymbol.UnknownResultType) Debug.Assert(oldValue Is ErrorTypeSymbol.UnknownResultType OrElse oldValue Is newValue) End If Return Me._lazyAnonymousDelegateSymbol diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceMethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceMethodSymbol.vb index b97582748a043..6a1a6a0d13c40 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceMethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceMethodSymbol.vb @@ -1296,7 +1296,7 @@ lReportErrorOnTwoTokens: meParameter = Nothing Else If _lazyMeParameter Is Nothing Then - Interlocked.CompareExchange(_lazyMeParameter, New MeParameterSymbol(Me), Nothing) + Interlocked.CompareExchange(Of ParameterSymbol)(_lazyMeParameter, New MeParameterSymbol(Me), Nothing) End If meParameter = _lazyMeParameter diff --git a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedMethodBase.vb b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedMethodBase.vb index cf7dfb5a7b6b2..5613d3dca11c5 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedMethodBase.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedMethodBase.vb @@ -178,7 +178,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols meParameter = Nothing Else If _lazyMeParameter Is Nothing Then - Interlocked.CompareExchange(_lazyMeParameter, New MeParameterSymbol(Me), Nothing) + Interlocked.CompareExchange(Of ParameterSymbol)(_lazyMeParameter, New MeParameterSymbol(Me), Nothing) End If meParameter = _lazyMeParameter diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index f2fce5f8fc94a..84f612cb5eb24 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -8290,6 +8290,29 @@ End Module parsedArgs.Errors.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("langversion", "1000").WithLocation(1, 1)) End Sub + + + Public Sub MissingCompilerAssembly() + Dim dir = Temp.CreateDirectory() + Dim vbcPath = dir.CopyFile(GetType(Vbc).Assembly.Location).Path + + ' Missing Microsoft.CodeAnalysis.VisualBasic.dll. + Dim result = ProcessUtilities.Run(vbcPath, arguments:="/nologo /t:library unknown.vb", workingDirectory:=dir.Path) + Assert.Equal(1, result.ExitCode) + Assert.Equal( + $"Could not load file or assembly '{GetType(VisualBasicCompilation).Assembly.FullName}' or one of its dependencies. The system cannot find the file specified.", + result.Output.Trim()) + + ' Missing System.Collections.Immutable.dll. + dir.CopyFile(GetType(Compilation).Assembly.Location) + dir.CopyFile(GetType(VisualBasicCompilation).Assembly.Location) + result = ProcessUtilities.Run(vbcPath, arguments:="/nologo /t:library unknown.vb", workingDirectory:=dir.Path) + Assert.Equal(1, result.ExitCode) + Assert.Equal( + $"Could not load file or assembly '{GetType(ImmutableArray).Assembly.FullName}' or one of its dependencies. The system cannot find the file specified.", + result.Output.Trim()) + End Sub + Private Function MakeTrivialExe(Optional directory As String = Nothing) As String Return Temp.CreateFile(directory:=directory, prefix:="", extension:=".vb").WriteAllText(" Class Program diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.vb index eecd8ec2417cb..89023dcc6d729 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.vb @@ -7,8 +7,6 @@ Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Test.Utilities.VBInstrumentationChecker Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests -Imports Roslyn.Test.Utilities -Imports Xunit Namespace Microsoft.CodeAnalysis.VisualBasic.DynamicAnalysis.UnitTests @@ -2368,6 +2366,173 @@ End Class AssertInstrumented(verifier, "D.M") End Sub + + Public Sub TestPartialMethodsWithImplementation() + Dim testSource = + 0 + Console.WriteLine("Method1: x > 0") + Method1(0) + ElseIf x < 0 + Console.WriteLine("Method1: x < 0") + End If + End Sub +End Class + +Module Program + Public Sub Main() + Test() + Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload() + End Sub + + Sub Test() + Console.WriteLine("Test") + Dim c = new Class1() + c.Method2(1) + End Sub +End Module +]]> + + + Dim source = + <%= testSource %> + <%= InstrumentationHelperSource %> + + + Dim checker = New VBInstrumentationChecker() + checker.Method(1, 1, "New", expectBodySpan:=False) + checker.Method(2, 1, "Private Sub Method1(x as Integer)"). + True("Console.WriteLine(""Method1: x = {0}"", x)"). + True("Console.WriteLine(""Method1: x > 0"")"). + True("Method1(0)"). + False("Console.WriteLine(""Method1: x < 0"")"). + True("x < 0"). + True("x > 0") + checker.Method(3, 1, "Public Sub Method2(x as Integer)"). + True("Console.WriteLine(""Method2: x = {0}"", x)"). + True("Method1(x)") + checker.Method(4, 1, "Public Sub Main()"). + True("Test()"). + True("Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload()") + checker.Method(5, 1, "Sub Test()"). + True("Console.WriteLine(""Test"")"). + True("new Class1()"). + True("c.Method2(1)") + checker.Method(8, 1). + True(). + False(). + True(). + True(). + True(). + True(). + True(). + True(). + True(). + True(). + True() + + Dim expectedOutput = "Test +Method2: x = 1 +Method1: x = 1 +Method1: x > 0 +Method1: x = 0 +" + XCDataToString(checker.ExpectedOutput) + + Dim verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.ReleaseExe) + checker.CompleteCheck(verifier.Compilation, testSource) + verifier.VerifyDiagnostics() + + verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.DebugExe) + checker.CompleteCheck(verifier.Compilation, testSource) + verifier.VerifyDiagnostics() + End Sub + + + Public Sub TestPartialMethodsWithoutImplementation() + Dim testSource = + + + + Dim source = + <%= testSource %> + <%= InstrumentationHelperSource %> + + + Dim checker = New VBInstrumentationChecker() + checker.Method(1, 1, "New", expectBodySpan:=False) + checker.Method(2, 1, "Public Sub Method2(x as Integer)"). + True("Console.WriteLine(""Method2: x = {0}"", x)") + checker.Method(3, 1, "Public Sub Main()"). + True("Test()"). + True("Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload()") + checker.Method(4, 1, "Sub Test()"). + True("Console.WriteLine(""Test"")"). + True("new Class1()"). + True("c.Method2(1)") + checker.Method(7, 1). + True(). + False(). + True(). + True(). + True(). + True(). + True(). + True(). + True(). + True(). + True() + + Dim expectedOutput = "Test +Method2: x = 1 +" + XCDataToString(checker.ExpectedOutput) + + Dim verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.ReleaseExe) + checker.CompleteCheck(verifier.Compilation, testSource) + verifier.VerifyDiagnostics() + + verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.DebugExe) + checker.CompleteCheck(verifier.Compilation, testSource) + verifier.VerifyDiagnostics() + End Sub + Private Shared Sub AssertNotInstrumented(verifier As CompilationVerifier, qualifiedMethodName As String) AssertInstrumented(verifier, qualifiedMethodName, expected:=False) End Sub @@ -2394,6 +2559,14 @@ End Class emitOptions:=EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))) End Function + Private Overloads Function CompileAndVerify(source As XElement, Optional expectedOutput As String = Nothing, Optional options As VisualBasicCompilationOptions = Nothing) As CompilationVerifier + Return CompileAndVerify(source, + LatestVbReferences, + expectedOutput, + options:=If(options, TestOptions.ReleaseExe).WithDeterministic(True), + emitOptions:=EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))) + End Function + Private Overloads Function CompileAndVerify(source As String, Optional expectedOutput As String = Nothing, Optional options As VisualBasicCompilationOptions = Nothing) As CompilationVerifier Return CompileAndVerify(source, LatestVbReferences, diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTupleTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTupleTests.vb index 3c2dee5be373d..c78d3704c08d5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTupleTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTupleTests.vb @@ -1,6 +1,7 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.PDB @@ -9,16 +10,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.PDB Public Sub Local() - Dim source = - - - +" Dim comp = CreateCompilationWithMscorlib(source, references:={ValueTupleRef, SystemRuntimeFacadeRef}, options:=TestOptions.DebugDll) comp.VerifyPdb("C.F", @@ -33,9 +31,9 @@ End Class - - - + + + @@ -46,6 +44,88 @@ End Class ) End Sub + + Public Sub VariablesAndConstantsInUnreachableCode() + Dim source = " +Imports System +Imports System.Collections.Generic + +Class C(Of T) + Enum E + A + End Enum + + Sub F() + Dim v1 As C(Of (a As Integer, b As Integer)).E = Nothing + Const c1 As C(Of (a As Integer, b As Integer)).E = Nothing + + Throw New Exception() + + Dim v2 As C(Of (a As Integer, b As Integer)).E = Nothing + Const c2 As C(Of (a As Integer, b As Integer)).E = Nothing + + Do + Dim v3 As C(Of (a As Integer, b As Integer)).E = Nothing + Const c3 As C(Of (a As Integer, b As Integer)).E = Nothing + Loop + End Sub +End Class +" + Dim c = CreateCompilationWithMscorlib(source, references:={ValueTupleRef, SystemRuntimeFacadeRef}, options:=TestOptions.DebugDll) + + Dim v = CompileAndVerify(c) + v.VerifyIL("C(Of T).F()", " +{ + // Code size 9 (0x9) + .maxstack 1 + .locals init (C(Of (a As Integer, b As Integer)).E V_0, //v1 + C(Of (a As Integer, b As Integer)).E V_1, //v2 + C(Of (a As Integer, b As Integer)).E V_2) //v3 + IL_0000: nop + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + IL_0003: newobj ""Sub System.Exception..ctor()"" + IL_0008: throw +} +") + + c.VerifyPdb( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj b/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj index 3ba2d014bb6af..c12ac6254f277 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj @@ -107,6 +107,8 @@ + + diff --git a/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IFieldReferenceExpression.vb b/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IFieldReferenceExpression.vb new file mode 100644 index 0000000000000..016cd8a89dcb6 --- /dev/null +++ b/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IFieldReferenceExpression.vb @@ -0,0 +1,36 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Semantics +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Roslyn.Test.Utilities + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics + + Partial Public Class IOperationTests + Inherits SemanticModelTestBase + + + Public Sub FieldReference_Attribute() + Dim source = 'BIND:"Conditional(field)" + Private Sub M() + End Sub +End Class]]>.Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of AttributeSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + End Class +End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IParameterReferenceExpression.vb b/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IParameterReferenceExpression.vb new file mode 100644 index 0000000000000..65581f7e4b429 --- /dev/null +++ b/src/Compilers/VisualBasic/Test/Semantic/IOperation/IOperationTests_IParameterReferenceExpression.vb @@ -0,0 +1,631 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Semantics +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Roslyn.Test.Utilities + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics + + Partial Public Class IOperationTests + Inherits SemanticModelTestBase + + + Public Sub ParameterReference_TupleExpression() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of TupleExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_AnonymousObjectCreation() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of AnonymousObjectCreationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_QueryExpression() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of QueryExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_QueryExpressionAggregateClause() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of QueryExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_QueryExpressionOrderByClause() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of QueryExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_QueryExpressionGroupByClause() + Dim source = .Value + + Dim expectedOperationTree = , )(keySelector As System.Func(Of System.String, ), resultSelector As System.Func(Of , System.Collections.Generic.IEnumerable(Of System.String), )) As System.Collections.Generic.IEnumerable(Of )) (OperationKind.InvocationExpression, Type: System.Collections.Generic.IEnumerable(Of )) (Syntax: 'Group By w ... nto Count()') + Instance Receiver: IConversionExpression (ConversionKind.Basic, Implicit) (OperationKind.ConversionExpression, Type: System.Collections.Generic.IEnumerable(Of System.String)) (Syntax: 'y In x') + IOperation: (OperationKind.None) (Syntax: 'y In x') + Children(1): IOperation: (OperationKind.None) (Syntax: 'x') + Children(1): IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.String()) (Syntax: 'x') + Arguments(2): IArgument (ArgumentKind.DefaultValue, Matching Parameter: keySelector) (OperationKind.Argument) (Syntax: 'x') + IConversionExpression (ConversionKind.Basic, Implicit) (OperationKind.ConversionExpression, Type: System.Func(Of System.String, )) (Syntax: 'x') + IOperation: (OperationKind.None) (Syntax: 'x') + Children(1): IOperation: (OperationKind.None) (Syntax: 'Group By w ... nto Count()') + Children(2): IOperation: (OperationKind.None) (Syntax: 'w = x') + Children(1): IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.String()) (Syntax: 'x') + IOperation: (OperationKind.None) (Syntax: 'z = y') + Children(1): IOperation: (OperationKind.None) (Syntax: 'y') + IArgument (ArgumentKind.DefaultValue, Matching Parameter: resultSelector) (OperationKind.Argument) (Syntax: 'Group By w ... nto Count()') + IConversionExpression (ConversionKind.Basic, Implicit) (OperationKind.ConversionExpression, Type: System.Func(Of , System.Collections.Generic.IEnumerable(Of System.String), )) (Syntax: 'Group By w ... nto Count()') + IOperation: (OperationKind.None) (Syntax: 'Group By w ... nto Count()') + Children(1): IOperation: (OperationKind.None) (Syntax: 'Group By w ... nto Count()') + Children(3): IOperation: (OperationKind.None) (Syntax: 'w') + IOperation: (OperationKind.None) (Syntax: 'z') + IOperation: (OperationKind.None) (Syntax: 'Count()') + Children(1): IOperation: (OperationKind.None) (Syntax: 'Count()') + Children(1): IInvocationExpression ( Function System.Collections.Generic.IEnumerable(Of System.String).Count() As System.Int32) (OperationKind.InvocationExpression, Type: System.Int32) (Syntax: 'Count()') + Instance Receiver: IParameterReferenceExpression: $VB$ItAnonymous (OperationKind.ParameterReferenceExpression, Type: System.Collections.Generic.IEnumerable(Of System.String)) (Syntax: 'Group By w ... nto Count()') +]]>.Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of QueryExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_ObjectAndCollectionInitializer() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of ObjectCreationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_NameOfExpression() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of NameOfExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_LateBoundIndexerAccess() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of InvocationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_LateBoundMemberAccess() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of InvocationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_LateBoundInvocation() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of InvocationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_InterpolatedStringExpression() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of InterpolatedStringExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_MidAssignmentStatement() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of AssignmentStatementSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_MisplacedCaseStatement() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = .Value + + VerifyOperationTreeAndDiagnosticsForTest(Of CaseStatementSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_RedimStatement() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of ReDimStatementSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_EraseStatement() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of EraseStatementSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_UnstructuredExceptionHandlingStatement() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of MethodBlockSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_LateAddressOfOperator() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of UnaryExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_NullableIsTrueOperator() + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of MultiLineIfBlockSyntax)(source, expectedOperationTree, expectedDiagnostics) + End Sub + + + Public Sub ParameterReference_NoPiaObjectCreation() + Dim sources0 = + + + + + +Public Interface I + Property P As Integer +End Interface + +Public Class C + Public Sub New(o As Object) + End Sub +End Class +]]> + + Dim sources1 = + + + Dim compilation0 = CreateCompilationWithMscorlib(sources0) + compilation0.AssertTheseDiagnostics() + + ' No errors for /r:_.dll + Dim compilation1 = CreateCompilationWithReferences( + sources1, + references:={MscorlibRef, SystemRef, compilation0.EmitToImageReference(embedInteropTypes:=True)}) + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = .Value + + VerifyOperationTreeAndDiagnosticsForTest(Of ObjectCreationExpressionSyntax)(compilation1, "a.vb", expectedOperationTree, expectedDiagnostics) + End Sub + End Class +End Namespace diff --git a/src/Compilers/VisualBasic/vbc/Program.cs b/src/Compilers/VisualBasic/vbc/Program.cs index 0422f7a3c2079..7aef6f564b99a 100644 --- a/src/Compilers/VisualBasic/vbc/Program.cs +++ b/src/Compilers/VisualBasic/vbc/Program.cs @@ -1,19 +1,30 @@ // 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; using System.IO; using Microsoft.CodeAnalysis.CommandLine; -using Roslyn.Utilities; -using System; namespace Microsoft.CodeAnalysis.VisualBasic.CommandLine { public class Program { public static int Main(string[] args) - => Main(args, Array.Empty()); + { + try + { + return MainCore(args); + } + catch (FileNotFoundException e) + { + // Catch exception from missing compiler assembly. + // Report the exception message and terminate the process. + Console.WriteLine(e.Message); + return CommonCompiler.Failed; + } + } - public static int Main(string[] args, string[] extraArgs) - => DesktopBuildClient.Run(args, extraArgs, RequestLanguage.VisualBasicCompile, Vbc.Run, new DesktopAnalyzerAssemblyLoader()); + private static int MainCore(string[] args) + => DesktopBuildClient.Run(args, RequestLanguage.VisualBasicCompile, Vbc.Run, new DesktopAnalyzerAssemblyLoader()); public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, string tempDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader) => Vbc.Run(args, new BuildPaths(clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir, tempDir: tempDir), textWriter, analyzerLoader); diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs index df00e9a350471..3c674d113aea5 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs @@ -4554,5 +4554,54 @@ void M() } }"); } + + [WorkItem(19218, "https://github.com/dotnet/roslyn/issues/19218")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)] + public async Task TestChangeCaseWithUsingsInNestedNamespace() + { + await TestInRegularAndScriptAsync( +@"namespace VS +{ + interface IVsStatusbar + { + } +} + +namespace Outer +{ + using System; + + class C + { + void M() + { + // Note: IVsStatusBar is cased incorrectly. + [|IVsStatusBar|] b; + } + } +} +", +@"namespace VS +{ + interface IVsStatusbar + { + } +} + +namespace Outer +{ + using System; + using VS; + + class C + { + void M() + { + IVsStatusbar b; + } + } +} +"); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests_NuGet.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests_NuGet.cs index 53155eedcb87e..28f2e728a387f 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests_NuGet.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests_NuGet.cs @@ -54,7 +54,7 @@ public async Task TestSearchPackageSingleName() // Make a loose mock for the installer service. We don't care what this test // calls on it. var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.TryInstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), "NuGetPackage", It.IsAny(), It.IsAny(), It.IsAny())) .Returns(true); @@ -83,7 +83,7 @@ public async Task TestSearchPackageMultipleNames() // Make a loose mock for the installer service. We don't care what this test // calls on it. var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.TryInstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), "NuGetPackage", It.IsAny(), It.IsAny(), It.IsAny())) .Returns(true); @@ -112,7 +112,7 @@ public async Task TestMissingIfPackageAlreadyInstalled() // Make a loose mock for the installer service. We don't care what this test // calls on it. var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.IsInstalled(It.IsAny(), It.IsAny(), "NuGetPackage")) .Returns(true); @@ -135,7 +135,7 @@ public async Task TestOptionsOffered() // Make a loose mock for the installer service. We don't care what this test // calls on it. var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.GetInstalledVersions("NuGetPackage")) .Returns(ImmutableArray.Create("1.0", "2.0")); @@ -177,7 +177,7 @@ await TestSmartTagTextAsync( public async Task TestInstallGetsCalledNoVersion() { var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.TryInstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), "NuGetPackage", /*versionOpt*/ null, It.IsAny(), It.IsAny())) .Returns(true); @@ -205,7 +205,7 @@ class C public async Task TestInstallGetsCalledWithVersion() { var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.GetInstalledVersions("NuGetPackage")) .Returns(ImmutableArray.Create("1.0")); @@ -235,7 +235,7 @@ class C public async Task TestFailedInstallRollsBackFile() { var installerServiceMock = new Mock(MockBehavior.Loose); - installerServiceMock.SetupGet(i => i.IsEnabled).Returns(true); + installerServiceMock.Setup(i => i.IsEnabled(It.IsAny())).Returns(true); installerServiceMock.SetupGet(i => i.PackageSources).Returns(NugetPackageSources); installerServiceMock.Setup(s => s.GetInstalledVersions("NuGetPackage")) .Returns(ImmutableArray.Create("1.0")); diff --git a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj index e127ecce924f6..52d3cdfc1212c 100644 --- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj +++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj @@ -294,7 +294,6 @@ - @@ -307,8 +306,8 @@ - - + + diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs index 9b5a317f94b7d..9cbed1ce3a764 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Options; using Roslyn.Test.Utilities; using Xunit; @@ -556,7 +555,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } } @@ -574,7 +573,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } } @@ -600,7 +599,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } } @@ -618,7 +617,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } } @@ -643,7 +642,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } @@ -658,7 +657,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } @@ -681,7 +680,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } @@ -696,7 +695,7 @@ public class @class { } - public static void Add(object t) + public static void Add(object @class) { } @@ -2180,8 +2179,8 @@ int Main(int i) switch (1) { case 0: - const int {|Rename:V|} = 1 + 1; - var f = Main(V); + const int {|Rename:I|} = 1 + 1; + var f = Main(I); Console.WriteLine(f); } } @@ -2432,8 +2431,8 @@ class C static void Main(string[] args) { var set = new HashSet(); - var {|Rename:v|} = set.ToString(); - set.Add(v); + var {|Rename:item|} = set.ToString(); + set.Add(item); } }", options: ImplicitTypingEverywhere()); @@ -2530,8 +2529,8 @@ class Program static void Main(string[] args) { var d = new Dictionary(); - var {|Rename:exception|} = new Exception(); - d.Add(""a"", exception); + var {|Rename:value|} = new Exception(); + d.Add(""a"", value); } }", ignoreTrivia: false, options: ImplicitTypingEverywhere()); @@ -3091,8 +3090,8 @@ class Complex int real; int imaginary; public static Complex operator +(Complex a, Complex b) { - var {|Rename:v|} = b.real + 1; - return a.Add(v); + var {|Rename:b1|} = b.real + 1; + return a.Add(b1); } private Complex Add(int b) @@ -3161,7 +3160,7 @@ public struct DBBool @"using System; public struct DBBool { - private const int {|Rename:V|} = 1; + private const int {|Rename:Value|} = 1; public static readonly DBBool dbFalse = new DBBool(-1); int value; @@ -3170,7 +3169,7 @@ public struct DBBool this.value = value; } - public static implicit operator DBBool(bool x) => x ? new DBBool(V) : dbFalse; + public static implicit operator DBBool(bool x) => x ? new DBBool(Value) : dbFalse; }"; await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false); @@ -3574,8 +3573,8 @@ class TestClass { static void Test(string[] args) { - var {|Rename:v|} = $""{DateTime.Now.ToString()}Text{args[0]}""; - Console.WriteLine(v); + var {|Rename:value|} = $""{DateTime.Now.ToString()}Text{args[0]}""; + Console.WriteLine(value); } }"; @@ -3603,9 +3602,9 @@ class TestClass { static void Test(string[] args) { - var {|Rename:v|} = $""Text{{s}}""; - Console.WriteLine(v); - Console.WriteLine(v); + var {|Rename:value|} = $""Text{{s}}""; + Console.WriteLine(value); + Console.WriteLine(value); } }"; @@ -3773,8 +3772,8 @@ class C { public async Task M() { - FormattableString {|Rename:v|} = $""""; - var f = FormattableString.Invariant(v); + FormattableString {|Rename:formattable|} = $""""; + var f = FormattableString.Invariant(formattable); } } }"; @@ -4254,5 +4253,81 @@ void M() await TestInRegularAndScriptAsync(code, expected, index: 1, ignoreTrivia: false); } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task TestPickNameBasedOnArgument1() + { + await TestInRegularAndScriptAsync( +@"class C +{ + public C(string a, string b) + { + new TextSpan([|int.Parse(a)|], int.Parse(b)); + } +} + +struct TextSpan +{ + public TextSpan(int start, int length) + { + + } +}", +@"class C +{ + public C(string a, string b) + { + int {|Rename:start|} = int.Parse(a); + new TextSpan(start, int.Parse(b)); + } +} + +struct TextSpan +{ + public TextSpan(int start, int length) + { + + } +}"); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task TestPickNameBasedOnArgument2() + { + await TestInRegularAndScriptAsync( +@"class C +{ + public C(string a, string b) + { + new TextSpan(int.Parse(a), [|int.Parse(b)|]); + } +} + +struct TextSpan +{ + public TextSpan(int start, int length) + { + + } +}", +@"class C +{ + public C(string a, string b) + { + int {|Rename:length|} = int.Parse(b); + new TextSpan(int.Parse(a), length); + } +} + +struct TextSpan +{ + public TextSpan(int start, int length) + { + + } +}"); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index 5b99c51236817..f66cd2e8496bf 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -16,9 +16,7 @@ public EnumAndCompletionListTagCompletionProviderTests(CSharpTestWorkspaceFixtur } internal override CompletionProvider CreateCompletionProvider() - { - return new EnumAndCompletionListTagCompletionProvider(); - } + => new EnumAndCompletionListTagCompletionProvider(); [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task NullableEnum() @@ -518,5 +516,105 @@ enum E "; await VerifyNoItemsExistAsync(markup); } + + [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumInitializer1() + { + var markup = +@"using System; + +[Flags] +internal enum ProjectTreeWriterOptions +{ + None, + Tags, + FilePath, + Capabilities, + Visibility, + AllProperties = FilePath | Visibility | $$ +}"; + await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); + } + + [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumInitializer2() + { + var markup = +@"using System; + +[Flags] +internal enum ProjectTreeWriterOptions +{ + None, + Tags, + FilePath, + Capabilities, + Visibility, + AllProperties = FilePath | $$ Visibility +}"; + await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); + } + + [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumInitializer3() + { + var markup = +@"using System; + +[Flags] +internal enum ProjectTreeWriterOptions +{ + None, + Tags, + FilePath, + Capabilities, + Visibility, + AllProperties = FilePath | $$ | Visibility +}"; + await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); + } + + [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumInitializer4() + { + var markup = +@"using System; + +[Flags] +internal enum ProjectTreeWriterOptions +{ + None, + Tags, + FilePath, + Capabilities, + Visibility, + AllProperties = FilePath ^ $$ +}"; + await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); + } + + [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumInitializer5() + { + var markup = +@"using System; + +[Flags] +internal enum ProjectTreeWriterOptions +{ + None, + Tags, + FilePath, + Capabilities, + Visibility, + AllProperties = FilePath & $$ +}"; + await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); + } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/GlobalAssemblyCacheCompletionHelperTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/GlobalAssemblyCacheCompletionHelperTests.cs deleted file mode 100644 index 890aa50a1db85..0000000000000 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/GlobalAssemblyCacheCompletionHelperTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.Completion.FileSystem; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntelliSense.CompletionSetSources -{ - public class GlobalAssemblyCacheCompletionHelperTests - { - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public void ExistingReference() - { - var code = "System.Windows"; - VerifyPresence(code, "System.Windows.Forms"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public void FullReferenceIdentity() - { - var code = "System,"; - VerifyPresence(code, typeof(System.Diagnostics.Process).Assembly.FullName); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public void FullReferenceIdentityDescription() - { - var code = "System"; - var completions = GetItems(code); - var systemsColl = from completion in completions - where completion.DisplayText == "System" - select completion; - - Assert.True(systemsColl.Any( - completion => CommonCompletionItem.GetDescription(completion).Text == typeof(System.Diagnostics.Process).Assembly.FullName)); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public void NothingOnForwardSlash() - { - var code = "System.Windows/"; - VerifyAbsence(code); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public void NothingOnBackSlash() - { - var code = @"System.Windows\"; - VerifyAbsence(code); - } - - private static void VerifyPresence(string pathSoFar, string completionItem) - { - var completions = GetItems(pathSoFar); - Assert.True(completions.Any(c => c.DisplayText == completionItem)); - } - - private static void VerifyAbsence(string pathSoFar) - { - var completions = GetItems(pathSoFar); - Assert.True(completions == null || !completions.Any(), "Expected null or non-empty completions"); - } - - private static IEnumerable GetItems(string pathSoFar) - { - var helper = new GlobalAssemblyCacheCompletionHelper(null, new TextSpan()); - - return helper.GetItems(pathSoFar, documentPath: null); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs new file mode 100644 index 0000000000000..f0ef0ce6d1106 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs @@ -0,0 +1,69 @@ +// 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; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders +{ + [Trait(Traits.Feature, Traits.Features.Completion)] + public class LoadDirectiveCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + public LoadDirectiveCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture) + { + } + + internal override CompletionProvider CreateCompletionProvider() + { + return new LoadDirectiveCompletionProvider(); + } + + protected override bool CompareItems(string actualItem, string expectedItem) + { + return actualItem.Equals(expectedItem, StringComparison.OrdinalIgnoreCase); + } + + protected override Task VerifyWorkerAsync( + string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, + SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, + int? glyph, int? matchPriority, bool? hasSuggestionItem) + { + return BaseVerifyWorkerAsync( + code, position, expectedItemOrNull, expectedDescriptionOrNull, + sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, + glyph, matchPriority, hasSuggestionItem); + } + + [Fact] + public async Task IsCommitCharacterTest() + { + var commitCharacters = new[] { '"', '\\' }; + await VerifyCommitCharactersAsync("#load \"$$", textTypedSoFar: "", validChars: commitCharacters); + } + + [Fact] + public void IsTextualTriggerCharacterTest() + { + var validMarkupList = new[] + { + "#load \"$$/", + "#load \"$$\\", + "#load \"$$,", + "#load \"$$A", + "#load \"$$!", + "#load \"$$(", + }; + + foreach (var markup in validMarkupList) + { + VerifyTextualTriggerCharacter(markup, shouldTriggerWithTriggerOnLettersEnabled: true, shouldTriggerWithTriggerOnLettersDisabled: true); + } + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/ReferenceDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs similarity index 55% rename from src/EditorFeatures/CSharpTest/Completion/ReferenceDirectiveCompletionProviderTests.cs rename to src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs index 1004329dbd9d5..67d4b98cac117 100644 --- a/src/EditorFeatures/CSharpTest/Completion/ReferenceDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs @@ -6,13 +6,14 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntelliSense.CompletionSetSources +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders { + [Trait(Traits.Feature, Traits.Features.Completion)] public class ReferenceDirectiveCompletionProviderTests : AbstractCSharpCompletionProviderTests { public ReferenceDirectiveCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture) @@ -40,50 +41,33 @@ protected override Task VerifyWorkerAsync( glyph, matchPriority, hasSuggestionItem); } - private async Task VerifyItemsExistInScriptAndInteractiveAsync(string code, params string[] expected) - { - foreach (var ex in expected) - { - await VerifyItemExistsAsync(code, ex, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script); - } - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [Fact] public async Task IsCommitCharacterTest() { - var commitCharacters = new[] { '"', '\\', ',' }; + var commitCharacters = PathUtilities.IsUnixLikePlatform ? new[] { '"', '/' } : new[] { '"', '\\', '/', ',' }; await VerifyCommitCharactersAsync("#r \"$$", textTypedSoFar: "", validChars: commitCharacters); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [Fact] public void IsTextualTriggerCharacterTest() { var validMarkupList = new[] { + "#r \"$$/", "#r \"$$\\", "#r \"$$,", - "#r \"$$A" - }; - - foreach (var markup in validMarkupList) - { - VerifyTextualTriggerCharacter(markup, shouldTriggerWithTriggerOnLettersEnabled: true, shouldTriggerWithTriggerOnLettersDisabled: true); - } - - var invalidMarkupList = new[] - { - "#r \"$$/", + "#r \"$$A", "#r \"$$!", "#r \"$$(", }; - foreach (var markup in invalidMarkupList) + foreach (var markup in validMarkupList) { - VerifyTextualTriggerCharacter(markup, shouldTriggerWithTriggerOnLettersEnabled: false, shouldTriggerWithTriggerOnLettersDisabled: false); + VerifyTextualTriggerCharacter(markup, shouldTriggerWithTriggerOnLettersEnabled: true, shouldTriggerWithTriggerOnLettersDisabled: true); } } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [ConditionalFact(typeof(WindowsOnly))] public async Task SendEnterThroughToEditorTest() { await VerifySendEnterThroughToEnterAsync("#r \"System$$", "System", sendThroughEnterOption: EnterKeyRule.Never, expected: false); @@ -91,57 +75,34 @@ public async Task SendEnterThroughToEditorTest() await VerifySendEnterThroughToEnterAsync("#r \"System$$", "System", sendThroughEnterOption: EnterKeyRule.Always, expected: false); // note: GAC completion helper uses its own EnterKeyRule } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task RootDrives() - { - // ensure drives are listed without the trailing backslash - var drive = Environment.GetLogicalDrives().First().TrimEnd('\\'); - await VerifyItemsExistInScriptAndInteractiveAsync( - "#r \"$$", - drive); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task RelativeDirectories() - { - await VerifyItemsExistInScriptAndInteractiveAsync( - "#r \"$$", - ".", - ".."); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task GACReference() + [ConditionalFact(typeof(WindowsOnly))] + public async Task GacReference() { - await VerifyItemsExistInScriptAndInteractiveAsync( - "#r \"$$", - "System.Windows.Forms"); + await VerifyItemExistsAsync("#r \"$$", "System.Windows.Forms", expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task GACReferenceFullyQualified() + [ConditionalFact(typeof(WindowsOnly))] + public async Task GacReferenceFullyQualified() { - await VerifyItemsExistInScriptAndInteractiveAsync( - "#r \"System.Windows.Forms,$$", - "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + await VerifyItemExistsAsync( + "#r \"System.Windows.Forms,$$", + "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [ConditionalFact(typeof(WindowsOnly))] public async Task FileSystemReference() { - string systemDir = Path.GetFullPath(Environment.SystemDirectory); - DirectoryInfo windowsDir = System.IO.Directory.GetParent(systemDir); - string windowsDirPath = windowsDir.FullName; - string windowsRoot = System.IO.Directory.GetDirectoryRoot(systemDir); + var systemDir = Path.GetFullPath(Environment.SystemDirectory); + var windowsDir = Directory.GetParent(systemDir); + var windowsDirPath = windowsDir.FullName; + var windowsRoot = Directory.GetDirectoryRoot(systemDir); // we need to get the exact casing from the file system: var normalizedWindowsPath = Directory.GetDirectories(windowsRoot, windowsDir.Name).Single(); var windowsFolderName = Path.GetFileName(normalizedWindowsPath); var code = "#r \"" + windowsRoot + "$$"; - await VerifyItemsExistInScriptAndInteractiveAsync( - code, - windowsFolderName); + await VerifyItemExistsAsync(code, windowsFolderName, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script); } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/LoadDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/LoadDirectiveCompletionProviderTests.cs deleted file mode 100644 index 44a4914fc15af..0000000000000 --- a/src/EditorFeatures/CSharpTest/Completion/LoadDirectiveCompletionProviderTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion -{ - public class LoadDirectiveCompletionProviderTests : AbstractCSharpCompletionProviderTests - { - public LoadDirectiveCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture) - { - } - - internal override CompletionProvider CreateCompletionProvider() - { - return new LoadDirectiveCompletionProvider(); - } - - protected override bool CompareItems(string actualItem, string expectedItem) - { - return actualItem.Equals(expectedItem, StringComparison.OrdinalIgnoreCase); - } - - protected override Task VerifyWorkerAsync( - string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, - SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, - int? glyph, int? matchPriority, bool? hasSuggestionItem) - { - return BaseVerifyWorkerAsync( - code, position, expectedItemOrNull, expectedDescriptionOrNull, - sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, - glyph, matchPriority, hasSuggestionItem); - } - - private async Task VerifyItemExistsInScriptAsync(string markup, string expected) - { - await VerifyItemExistsAsync(markup, expected, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: false); - } - - private async Task VerifyItemIsAbsentInInteractiveAsync(string markup, string expected) - { - await VerifyItemIsAbsentAsync(markup, expected, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: false); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NetworkPath() - { - await VerifyItemExistsInScriptAsync( - @"#load ""$$", - @"\\"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NetworkPathAfterInitialBackslash() - { - await VerifyItemExistsInScriptAsync( - @"#load ""\$$", - @"\\"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task UpOneDirectoryNotShownAtRoot() - { - // after so many ".." we should be at the root drive an should no longer suggest the parent. we can determine - // our current directory depth by counting the number of backslashes present in the current working directory - // and after that many references to "..", we are at the root. - int depth = Directory.GetCurrentDirectory().Count(c => c == Path.DirectorySeparatorChar); - var pathToRoot = string.Concat(Enumerable.Repeat(@"..\", depth)); - - await VerifyItemExistsInScriptAsync( - @"#load ""$$", - ".."); - await VerifyItemIsAbsentInInteractiveAsync( - @"#load """ + pathToRoot + "$$", - ".."); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateMethodTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateMethodTests.cs index 3532565a413de..5866588f098b2 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateMethodTests.cs @@ -7262,7 +7262,7 @@ private object Method() parseOptions: TestOptions.Regular); } - [Fact/*(Skip = "https://github.com/dotnet/roslyn/issues/15508")*/, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)] [WorkItem(14136, "https://github.com/dotnet/roslyn/issues/14136")] public async Task TestDeconstruction4() { @@ -7379,5 +7379,67 @@ private ref int Bar() } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)] + [WorkItem(18969, "https://github.com/dotnet/roslyn/issues/18969")] + public async Task TestTupleElement1() + { + await TestAsync( +@"using System; + +class C +{ + public void M1() + { + (int x, string y) t = ([|Method|](), null); + } +}", +@"using System; + +class C +{ + public void M1() + { + (int x, string y) t = (Method(), null); + } + + private int Method() + { + throw new NotImplementedException(); + } +}", +parseOptions: TestOptions.Regular); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)] + [WorkItem(18969, "https://github.com/dotnet/roslyn/issues/18969")] + public async Task TestTupleElement2() + { + await TestAsync( +@"using System; + +class C +{ + public void M1() + { + (int x, string y) t = (0, [|Method|]()); + } +}", +@"using System; + +class C +{ + public void M1() + { + (int x, string y) t = (0, Method()); + } + + private string Method() + { + throw new NotImplementedException(); + } +}", +parseOptions: TestOptions.Regular); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs index bd45b420d589c..e3e978a102ef5 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs @@ -599,5 +599,180 @@ async ValueTask TestAsync() }"; await TestInRegularAndScriptAsync(initial, expected); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(14133, "https://github.com/dotnet/roslyn/issues/14133")] + public async Task AddAsyncInLocalFunction() + { + await TestInRegularAndScriptAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + void M2() + { + [|await M3Async();|] + } + } + + async Task M3Async() + { + return 1; + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task M2Async() + { + await M3Async(); + } + } + + async Task M3Async() + { + return 1; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(14133, "https://github.com/dotnet/roslyn/issues/14133")] + public async Task AddAsyncInLocalFunctionKeepVoidReturn() + { + await TestInRegularAndScriptAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + void M2() + { + [|await M3Async();|] + } + } + + async Task M3Async() + { + return 1; + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async void M2Async() + { + await M3Async(); + } + } + + async Task M3Async() + { + return 1; + } +}", +index: 1); + } + + [Theory] + [InlineData(0, "Task")] + [InlineData(1, "void", Skip = "https://github.com/dotnet/roslyn/issues/18396")] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(18307, "https://github.com/dotnet/roslyn/issues/18307")] + public async Task AddAsyncInLocalFunctionKeepsTrivia(int codeFixIndex, string expectedReturn) + { + await TestInRegularAndScriptAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + // Leading trivia + /*1*/ void /*2*/ M2/*3*/() /*4*/ + { + [|await M3Async();|] + } + } + + async Task M3Async() + { + return 1; + } +}", +$@"using System.Threading.Tasks; + +class C +{{ + public void M1() + {{ + // Leading trivia + /*1*/ async {expectedReturn} /*2*/ M2Async/*3*/() /*4*/ + {{ + await M3Async(); + }} + }} + + async Task M3Async() + {{ + return 1; + }} +}}", + index: codeFixIndex, + ignoreTrivia: false); + } + + [Theory] + [InlineData("", 0, "Task")] + [InlineData("", 1, "void", Skip = "https://github.com/dotnet/roslyn/issues/18396")] + [InlineData("public", 0, "Task")] + [InlineData("public", 1, "void")] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(18307, "https://github.com/dotnet/roslyn/issues/18307")] + public async Task AddAsyncKeepsTrivia(string modifiers, int codeFixIndex, string expectedReturn) + { + await TestInRegularAndScriptAsync( +$@"using System.Threading.Tasks; + +class C +{{ + // Leading trivia + {modifiers}/*1*/ void /*2*/ M2/*3*/() /*4*/ + {{ + [|await M3Async();|] + }} + + async Task M3Async() + {{ + return 1; + }} +}}", +$@"using System.Threading.Tasks; + +class C +{{ + // Leading trivia + {modifiers}/*1*/ async {expectedReturn} /*2*/ M2Async/*3*/() /*4*/ + {{ + await M3Async(); + }} + + async Task M3Async() + {{ + return 1; + }} +}}", + index: codeFixIndex, + ignoreTrivia: false); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodSynchronous/MakeMethodSynchronousTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodSynchronous/MakeMethodSynchronousTests.cs index 8942b2569cda3..a60f6c8bee688 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodSynchronous/MakeMethodSynchronousTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodSynchronous/MakeMethodSynchronousTests.cs @@ -548,5 +548,112 @@ async void BarAsync() } }", ignoreTrivia: false); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(14133, "https://github.com/dotnet/roslyn/issues/14133")] + public async Task RemoveAsyncInLocalFunction() + { + await TestInRegularAndScriptAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task [|M2Async|]() + { + } + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + void M2() + { + } + } +}"); + } + + [Theory] + [InlineData("Task", "C")] + [InlineData("Task", "int")] + [InlineData("Task", "void")] + [InlineData("void", "void")] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(18307, "https://github.com/dotnet/roslyn/issues/18307")] + public async Task RemoveAsyncInLocalFunctionKeepsTrivia(string asyncReturn, string expectedReturn) + { + await TestInRegularAndScriptAsync( +$@"using System; +using System.Threading.Tasks; + +class C +{{ + public void M1() + {{ + // Leading trivia + /*1*/ async {asyncReturn} /*2*/ [|M2Async|]/*3*/() /*4*/ + {{ + throw new NotImplementedException(); + }} + }} +}}", +$@"using System; +using System.Threading.Tasks; + +class C +{{ + public void M1() + {{ + // Leading trivia + /*1*/ {expectedReturn} /*2*/ M2/*3*/() /*4*/ + {{ + throw new NotImplementedException(); + }} + }} +}}"); + } + + [Theory] + [InlineData("", "Task", "C")] + [InlineData("", "Task", "int")] + [InlineData("", "Task", "void")] + [InlineData("", "void", "void")] + [InlineData("public", "Task", "C")] + [InlineData("public", "Task", "int")] + [InlineData("public", "Task", "void")] + [InlineData("public", "void", "void")] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeMethodAsynchronous)] + [WorkItem(18307, "https://github.com/dotnet/roslyn/issues/18307")] + public async Task RemoveAsyncKeepsTrivia(string modifiers, string asyncReturn, string expectedReturn) + { + await TestInRegularAndScriptAsync( +$@"using System; +using System.Threading.Tasks; + +class C +{{ + // Leading trivia + {modifiers}/*1*/ async {asyncReturn} /*2*/ [|M2Async|]/*3*/() /*4*/ + {{ + throw new NotImplementedException(); + }} +}}", +$@"using System; +using System.Threading.Tasks; + +class C +{{ + // Leading trivia + {modifiers}/*1*/ {expectedReturn} /*2*/ M2/*3*/() /*4*/ + {{ + throw new NotImplementedException(); + }} +}}"); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs index 9ec9e68a01925..614005d31d0d4 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs @@ -1,10 +1,12 @@ // 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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeFixes.NamingStyles; using Microsoft.CodeAnalysis.CSharp.Diagnostics.NamingStyles; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Roslyn.Test.Utilities; using Xunit; @@ -319,5 +321,27 @@ internal interface [|}|] ", new TestParameters(options: InterfaceNamesStartWithI)); } + + [WorkItem(16562, "https://github.com/dotnet/roslyn/issues/16562")] + public async Task TestRefactorNotify() + { + var markup = @"public class [|c|] { }"; + var testParameters = new TestParameters(options: ClassNamesArePascalCase); + + using (var workspace = CreateWorkspaceFromOptions(markup, testParameters)) + { + var actions = await GetCodeActionsAsync(workspace, testParameters); + + var previewOperations = await actions[0].GetPreviewOperationsAsync(CancellationToken.None); + Assert.Empty(previewOperations.OfType()); + + var commitOperations = await actions[0].GetOperationsAsync(CancellationToken.None); + Assert.Equal(2, commitOperations.Length); + + var symbolRenamedOperation = (TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.Operation)commitOperations[1]; + Assert.Equal("c", symbolRenamedOperation._symbol.Name); + Assert.Equal("C", symbolRenamedOperation._newName); + } + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.cs b/src/EditorFeatures/CSharpTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.cs index 3af44cb53b939..6afc81cf3de69 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.cs @@ -13,9 +13,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.RemoveUnuse public partial class RemoveUnusedVariableTest : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - { - return(null, new RemoveUnusedVariableCodeFixProvider()); - } + => (null, new CSharpRemoveUnusedVariableCodeFixProvider()); [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedVariable)] public async Task RemoveUnusedVariable() diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs index b4be75cb1764c..9848a492cf754 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs @@ -179,6 +179,25 @@ class Program index: 1); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] + public async Task UpgradeProjectFromCSharp7ToCSharp7_1_B() + { + await TestLanguageVersionUpgradedAsync( +@"public class Base { } +public class Derived : Base { } +public class Program +{ + public static void M(T x) where T: Base + { + System.Console.Write(x is [|Derived|] b0); + } +} +", + LanguageVersion.CSharp7_1, + new CSharpParseOptions(LanguageVersion.CSharp7), + index: 1); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] public async Task UpgradeAllProjectsToDefault() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 52366c79d5517..a67a29f425690 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -3184,6 +3184,41 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariableBody_Update_ExpressionActive() + { + string src1 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(0); + } + } +}"; + string src2 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(1); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEachBody_Update_InKeywordActive() { @@ -3219,6 +3254,41 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariableBody_Update_InKeywordActive() + { + string src1 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(0); + } + } +}"; + string src2 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(1); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEachBody_Update_VariableActive() { @@ -3254,6 +3324,41 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariableBody_Update_VariableActive() + { + string src1 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(0); + } + } +}"; + string src2 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(1); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEachBody_Update_ForeachKeywordActive() { @@ -3289,6 +3394,41 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariableBody_Update_ForeachKeywordActive() + { + string src1 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(0); + } + } +}"; + string src2 = @" +class Test +{ + private static (string, int) F() { return null; } + + static void Main(string[] args) + { + foreach ((string s, int i) in F()) + { + System.Console.Write(1); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEachVariable_Update() { @@ -3328,7 +3468,44 @@ static void Main(string[] args) } [Fact] - public void ForEach_Reorder_Leaf1() + public void ForEachDeconstructionVariable_Update() + { + string src1 = @" +class Test +{ + private static (int, (bool, double))[] F() { return new[] { (1, (true, 2.0)) }; } + + static void Main(string[] args) + { + foreach ((int i, (bool b, double d)) in F()) + { + System.Console.Write(0); + } + } +}"; + string src2 = @" +class Test +{ + private static (int, (bool, double))[] F() { return new[] { (1, (true, 2.0)) }; } + + static void Main(string[] args) + { + foreach ((int i, (var b, double d)) in F()) + { + System.Console.Write(1); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.ActiveStatementUpdate, "(int i, (var b, double d))"), + Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "foreach ( (int i, (var b, double d)) in F())", CSharpFeaturesResources.foreach_statement)); + } + + [Fact] + public void ForEach_Reorder_Leaf() { string src1 = @" class Test @@ -3377,19 +3554,19 @@ static void Main(string[] args) } [Fact] - public void ForEach_Update_Leaf1() + public void ForEachVariable_Reorder_Leaf() { string src1 = @" class Test { - public static int[] e1 = new int[1]; - public static int[] e2 = new int[1]; + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; static void Main(string[] args) { - foreach (var a in e1) + foreach ((var a1, var a2) in e1) { - foreach (var b in e1) + foreach ((int b1, bool b2) in e1) { foreach (var c in e1) { @@ -3402,16 +3579,16 @@ static void Main(string[] args) string src2 = @" class Test { - public static int[] e1 = new int[1]; - public static int[] e2 = new int[1]; + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; static void Main(string[] args) { - foreach (var b in e1) + foreach ((int b1, bool b2) in e1) { foreach (var c in e1) { - foreach (var a in e1) + foreach ((var a1, var a2) in e1) { System.Console.Write(); } @@ -3426,7 +3603,7 @@ static void Main(string[] args) } [Fact] - public void ForEach_Update_Leaf2() + public void ForEach_Update_Leaf() { string src1 = @" class Test @@ -3468,6 +3645,49 @@ static void Main(string[] args) Diagnostic(RudeEditKind.InsertAroundActiveStatement, "foreach (var a in e1)", CSharpFeaturesResources.foreach_statement)); } + [Fact] + public void ForEachVariable_Update_Leaf() + { + string src1 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + System.Console.Write(); + } +}"; + string src2 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + foreach ((int b1, bool b2) in e1) + { + foreach (var c in e1) + { + foreach ((var a1, var a2) in e1) + { + System.Console.Write(); + } + } + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.InsertAroundActiveStatement, "foreach (var c in e1)", CSharpFeaturesResources.foreach_statement), + Diagnostic(RudeEditKind.InsertAroundActiveStatement, "foreach ((int b1, bool b2) in e1)", CSharpFeaturesResources.foreach_statement), + Diagnostic(RudeEditKind.InsertAroundActiveStatement, "foreach ((var a1, var a2) in e1)", CSharpFeaturesResources.foreach_statement)); + } + [Fact] public void ForEach_Delete_Leaf1() { @@ -3514,6 +3734,52 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariable_Delete_Leaf1() + { + string src1 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + foreach ((var a1, var a2) in e1) + { + foreach ((int b1, bool b2) in e1) + { + foreach (var c in e1) + { + System.Console.Write(); + } + } + } + } +}"; + string src2 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + foreach ((var a1, var a2) in e1) + { + foreach ((int b1, bool b2) in e1) + { + System.Console.Write(); + } + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEach_Delete_Leaf2() { @@ -3560,6 +3826,52 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariable_Delete_Leaf2() + { + string src1 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + foreach ((var a1, var a2) in e1) + { + foreach ((int b1, bool b2) in e1) + { + foreach (var c in e1) + { + System.Console.Write(); + } + } + } + } +}"; + string src2 = @" +class Test +{ + public static (int, bool)[] e1 = new (int, bool)[1]; + public static (int, bool)[] e2 = new (int, bool)[1]; + + static void Main(string[] args) + { + foreach ((int b1, bool b2) in e1) + { + foreach (var c in e1) + { + System.Console.Write(); + } + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEach_Delete_Leaf3() { @@ -3606,6 +3918,52 @@ static void Main(string[] args) edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEachVariable_Delete_Leaf3() + { + string src1 = @" +class Test +{ + public static int[] e1 = new int[1]; + public static int[] e2 = new int[1]; + + static void Main(string[] args) + { + foreach ((var a1, var a2) in e1) + { + foreach ((int b1, bool b2) in e1) + { + foreach (var c in e1) + { + System.Console.Write(); + } + } + } + } +}"; + string src2 = @" +class Test +{ + public static int[] e1 = new int[1]; + public static int[] e2 = new int[1]; + + static void Main(string[] args) + { + foreach ((var a1, var a2) in e1) + { + foreach (var c in e1) + { + System.Console.Write(); + } + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void ForEach_Lambda1() { @@ -8182,8 +8540,7 @@ static void F(object o) var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "var (x, y)", CSharpFeaturesResources.deconstruction)); + edits.VerifyRudeDiagnostics(active); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index bf80853e2d617..3453a4230e427 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -2349,6 +2349,56 @@ public int M2() index: 1); } + [WorkItem(18556, "https://github.com/dotnet/roslyn/issues/18556")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestImplementInterfaceThroughExplicitProperty() + { + await TestActionCountAsync( +@"interface IA +{ + IB B { get; } +} +interface IB +{ + int M(); +} +class AB : IA, [|IB|] +{ + IB IA.B => null; +}", +count: 3); + await TestWithAllCodeStyleOptionsOffAsync( +@"interface IA +{ + IB B { get; } +} +interface IB +{ + int M(); +} +class AB : IA, [|IB|] +{ + IB IA.B => null; +}", +@"interface IA +{ + IB B { get; } +} +interface IB +{ + int M(); +} +class AB : IA, [|IB|] +{ + IB IA.B => null; + + public int M() + { + return ((IA)this).B.M(); + } +}", index: 1); + } + [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNoImplementThroughIndexer() diff --git a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs index 870815e7c612f..b700377c7cebf 100644 --- a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs @@ -704,5 +704,56 @@ public C(string s) } }", index: 2); } + + [WorkItem(19173, "https://github.com/dotnet/roslyn/issues/19173")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestMissingOnUnboundTypeWithExistingNullCheck() + { + await TestMissingAsync( +@" +class C +{ + public C(String [||]s) + { + if (s == null) + { + throw new System.Exception(); + } + } +}"); + } + + [WorkItem(19174, "https://github.com/dotnet/roslyn/issues/19174")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestRespectPredefinedTypePreferences() + { + await TestInRegularAndScript1Async( +@" +using System; + +class Program +{ + static void Main([||]String bar) + { + } +}", +@" +using System; + +class Program +{ + static void Main(String bar) + { + if (String.IsNullOrEmpty(bar)) + { + throw new ArgumentException(""message"", nameof(bar)); + } + } +}", index: 1, + parameters: new TestParameters( + options: Option( + CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, + CodeStyleOptions.FalseWithSuggestionEnforcement))); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Interactive/CodeActions/InteractiveIntroduceVariableTests.cs b/src/EditorFeatures/CSharpTest/Interactive/CodeActions/InteractiveIntroduceVariableTests.cs index e9020f4603ee6..9e0057e717c0e 100644 --- a/src/EditorFeatures/CSharpTest/Interactive/CodeActions/InteractiveIntroduceVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/Interactive/CodeActions/InteractiveIntroduceVariableTests.cs @@ -168,8 +168,8 @@ public static void Main() { for (int i = 0; i < 10; i++) { - int {|Rename:v|} = i + 1; - Console.WriteLine(v); + int {|Rename:value|} = i + 1; + Console.WriteLine(value); } } } diff --git a/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs b/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs index a6f091f146cdd..4b6e6b6be07aa 100644 --- a/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs +++ b/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs @@ -981,5 +981,40 @@ public void M() } }"); } + + [WorkItem(19253, "https://github.com/dotnet/roslyn/issues/19253")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] + public async Task TestKeepBlankLinesAfter() + { + await TestInRegularAndScript1Async( +@" +using System.Collections.Generic; + +class MyClass +{ + public void Main() + { + var list = [||]new List(); + list.Add(1); + + int horse = 1; + } +}", +@" +using System.Collections.Generic; + +class MyClass +{ + public void Main() + { + var list = new List + { + 1 + }; + + int horse = 1; + } +}", ignoreTrivia: false); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs b/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs index 395fbfce9252e..5d42f49353f05 100644 --- a/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs +++ b/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs @@ -534,6 +534,47 @@ public void M() } public string Value { get; set; } +}", ignoreTrivia: false); + } + + [WorkItem(19253, "https://github.com/dotnet/roslyn/issues/19253")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] + public async Task TestKeepBlankLinesAfter() + { + await TestInRegularAndScript1Async( +@" +class Foo +{ + public int Bar { get; set; } +} + +class MyClass +{ + public void Main() + { + var foo = [||]new Foo(); + foo.Bar = 1; + + int horse = 1; + } +}", +@" +class Foo +{ + public int Bar { get; set; } +} + +class MyClass +{ + public void Main() + { + var foo = new Foo + { + Bar = 1 + }; + + int horse = 1; + } }", ignoreTrivia: false); } } diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs index a14156a13a993..7ecdff7862988 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs @@ -1,6 +1,8 @@ // 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; +using System.Collections.Generic; +using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,11 +13,10 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.Text; using Roslyn.Test.Utilities; using Xunit; -using System.Composition; -using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.UnitTests.Workspaces { @@ -1003,5 +1004,51 @@ public void TestAdditionalFile_AddRemove_FromProject() Assert.Equal("original.config", workspace.CurrentSolution.GetProject(project1.Id).AdditionalDocuments.Single().Name); } } + + [Fact, WorkItem(209299, "https://devdiv.visualstudio.com/DevDiv/_workitems?id=209299")] + public async Task TestLinkedFilesStayInSync() + { + var originalText = "class Program1 { }"; + var updatedText = "class Program2 { }"; + + var input = $@" + + + { originalText } + + + + +"; + + using (var workspace = TestWorkspace.Create(input, exportProvider: s_exportProvider.Value)) + { + var eventArgs = new List(); + + workspace.WorkspaceChanged += (s, e) => + { + Assert.Equal(WorkspaceChangeKind.DocumentChanged, e.Kind); + eventArgs.Add(e); + }; + + var originalDocumentId = workspace.GetOpenDocumentIds().Single(id => !workspace.GetTestDocument(id).IsLinkFile); + var linkedDocumentId = workspace.GetOpenDocumentIds().Single(id => workspace.GetTestDocument(id).IsLinkFile); + + workspace.GetTestDocument(originalDocumentId).Update(SourceText.From("class Program2 { }")); + await WaitForWorkspaceOperationsToComplete(workspace); + + Assert.Equal(2, eventArgs.Count); + AssertEx.SetEqual(workspace.Projects.SelectMany(p => p.Documents).Select(d => d.Id), eventArgs.Select(e => e.DocumentId)); + + Assert.Equal(eventArgs[0].OldSolution, eventArgs[1].OldSolution); + Assert.Equal(eventArgs[0].NewSolution, eventArgs[1].NewSolution); + + Assert.Equal(originalText, (await eventArgs[0].OldSolution.GetDocument(originalDocumentId).GetTextAsync().ConfigureAwait(false)).ToString()); + Assert.Equal(originalText, (await eventArgs[1].OldSolution.GetDocument(originalDocumentId).GetTextAsync().ConfigureAwait(false)).ToString()); + + Assert.Equal(updatedText, (await eventArgs[0].NewSolution.GetDocument(originalDocumentId).GetTextAsync().ConfigureAwait(false)).ToString()); + Assert.Equal(updatedText, (await eventArgs[1].NewSolution.GetDocument(originalDocumentId).GetTextAsync().ConfigureAwait(false)).ToString()); + } + } } } diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index 18b9836a0c890..3a5b371a27e54 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -466,9 +466,7 @@ - - diff --git a/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs index c97332511ba64..6c70c68e4bf9c 100644 --- a/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs +++ b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs @@ -150,7 +150,24 @@ await AppendFixesAsync( { // sort the result to the order defined by the fixers var priorityMap = _fixerPriorityMap[document.Project.Language].Value; - result.Sort((d1, d2) => priorityMap.ContainsKey((CodeFixProvider)d1.Provider) ? (priorityMap.ContainsKey((CodeFixProvider)d2.Provider) ? priorityMap[(CodeFixProvider)d1.Provider] - priorityMap[(CodeFixProvider)d2.Provider] : -1) : 1); + result.Sort((d1, d2) => + { + if (priorityMap.TryGetValue((CodeFixProvider)d1.Provider, out int priority1)) + { + if (priorityMap.TryGetValue((CodeFixProvider)d2.Provider, out int priority2)) + { + return priority1 - priority2; + } + else + { + return -1; + } + } + else + { + return 1; + } + }); } // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/FileSystemCompletionHelper.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/FileSystemCompletionHelper.cs deleted file mode 100644 index e334b9a4a8896..0000000000000 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/FileSystemCompletionHelper.cs +++ /dev/null @@ -1,289 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem -{ - internal sealed class FileSystemCompletionHelper - { - private readonly ICurrentWorkingDirectoryDiscoveryService _fileSystemDiscoveryService; - private readonly Func _exclude; - private readonly Glyph _folderGlyph; - private readonly Glyph _fileGlyph; - - // absolute paths - private readonly ImmutableArray _searchPaths; - - private readonly ISet _allowableExtensions; - - private readonly Lazy _lazyGetDrives; - private readonly CompletionProvider _completionProvider; - private readonly TextSpan _textChangeSpan; - private readonly CompletionItemRules _itemRules; - - public FileSystemCompletionHelper( - CompletionProvider completionProvider, - TextSpan textChangeSpan, - ICurrentWorkingDirectoryDiscoveryService fileSystemDiscoveryService, - Glyph folderGlyph, - Glyph fileGlyph, - ImmutableArray searchPaths, - IEnumerable allowableExtensions, - Func exclude = null, - CompletionItemRules itemRules = null) - { - Debug.Assert(searchPaths.All(path => PathUtilities.IsAbsolute(path))); - - _completionProvider = completionProvider; - _textChangeSpan = textChangeSpan; - _searchPaths = searchPaths; - _allowableExtensions = allowableExtensions.Select(e => e.ToLowerInvariant()).ToSet(); - _fileSystemDiscoveryService = fileSystemDiscoveryService; - _folderGlyph = folderGlyph; - _fileGlyph = fileGlyph; - _exclude = exclude; - _itemRules = itemRules; - - _lazyGetDrives = new Lazy(() => - IOUtilities.PerformIO(Directory.GetLogicalDrives, Array.Empty())); - } - - public ImmutableArray GetItems(string pathSoFar, string documentPath) - { - if (_exclude != null && _exclude(pathSoFar)) - { - return ImmutableArray.Empty; - } - - return GetFilesAndDirectories(pathSoFar, documentPath); - } - - private CompletionItem CreateCurrentDirectoryItem() - { - return CommonCompletionItem.Create(".", rules: _itemRules); - } - - private CompletionItem CreateParentDirectoryItem() - { - return CommonCompletionItem.Create("..", rules: _itemRules); - } - - private CompletionItem CreateNetworkRoot(TextSpan textChangeSpan) - { - return CommonCompletionItem.Create("\\\\", rules: _itemRules); - } - - private ImmutableArray GetFilesAndDirectories(string path, string basePath) - { - var result = ArrayBuilder.GetInstance(); - var pathKind = PathUtilities.GetPathKind(path); - switch (pathKind) - { - case PathKind.Empty: - result.Add(CreateCurrentDirectoryItem()); - - if (!IsDriveRoot(_fileSystemDiscoveryService.WorkingDirectory)) - { - result.Add(CreateParentDirectoryItem()); - } - - result.Add(CreateNetworkRoot(_textChangeSpan)); - result.AddRange(GetLogicalDrives()); - result.AddRange(GetFilesAndDirectoriesInSearchPaths()); - break; - - case PathKind.Absolute: - case PathKind.RelativeToCurrentDirectory: - case PathKind.RelativeToCurrentParent: - case PathKind.RelativeToCurrentRoot: - { - var fullPath = FileUtilities.ResolveRelativePath( - path, - basePath, - _fileSystemDiscoveryService.WorkingDirectory); - - if (fullPath != null) - { - result.AddRange(GetFilesAndDirectoriesInDirectory(fullPath)); - - // although it is possible to type "." here, it doesn't make any sense to do so: - if (!IsDriveRoot(fullPath) && pathKind != PathKind.Absolute) - { - result.Add(CreateParentDirectoryItem()); - } - - if (path == "\\" && pathKind == PathKind.RelativeToCurrentRoot) - { - // The user has typed only "\". In this case, we want to add "\\" to - // the list. Also, the textChangeSpan needs to be backed up by one - // so that it will consume the "\" when "\\" is inserted. - result.Add(CreateNetworkRoot(TextSpan.FromBounds(_textChangeSpan.Start - 1, _textChangeSpan.End))); - } - } - else - { - // invalid path - result.Clear(); - } - } - - break; - - case PathKind.Relative: - - // although it is possible to type "." here, it doesn't make any sense to do so: - result.Add(CreateParentDirectoryItem()); - - foreach (var searchPath in _searchPaths) - { - var fullPath = PathUtilities.CombineAbsoluteAndRelativePaths(searchPath, path); - - // search paths are always absolute: - Debug.Assert(PathUtilities.IsAbsolute(fullPath)); - result.AddRange(GetFilesAndDirectoriesInDirectory(fullPath)); - } - - break; - - case PathKind.RelativeToDriveDirectory: - // these paths are not supported - break; - - default: - throw ExceptionUtilities.UnexpectedValue(pathKind); - } - - return result.ToImmutableAndFree(); - } - - private static bool IsDriveRoot(string fullPath) - { - return IOUtilities.PerformIO(() => new DirectoryInfo(fullPath).Parent == null); - } - - private IEnumerable GetFilesAndDirectoriesInDirectory(string fullDirectoryPath) - { - Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); - if (IOUtilities.PerformIO(() => Directory.Exists(fullDirectoryPath))) - { - var directoryInfo = IOUtilities.PerformIO(() => new DirectoryInfo(fullDirectoryPath)); - if (directoryInfo != null) - { - return from child in GetFileSystemInfos(directoryInfo) - where ShouldShow(child) - where CanAccess(child) - select this.CreateCompletion(child); - } - } - - return SpecializedCollections.EmptyEnumerable(); - } - - private CompletionItem CreateCompletion(FileSystemInfo child) - { - return CommonCompletionItem.Create( - child.Name, - glyph: child is DirectoryInfo ? _folderGlyph : _fileGlyph, - description: child.FullName.ToSymbolDisplayParts(), - rules: _itemRules); - } - - private bool ShouldShow(FileSystemInfo child) - { - // Get the attributes. If we can't, assume it's hidden. - var attributes = IOUtilities.PerformIO(() => child.Attributes, FileAttributes.Hidden); - - // Don't show hidden/system files. - if ((attributes & FileAttributes.Hidden) != 0 || - (attributes & FileAttributes.System) != 0) - { - return false; - } - - if (child is DirectoryInfo) - { - return true; - } - - if (child is FileInfo) - { - return - _allowableExtensions.Count == 0 || - _allowableExtensions.Contains(Path.GetExtension(child.Name).ToLowerInvariant()); - } - - return false; - } - - private bool CanAccess(FileSystemInfo info) - { - switch (info) - { - case DirectoryInfo d: return CanAccessDirectory(d); - case FileInfo f: return CanAccessFile(f); - } - - return false; - } - - private bool CanAccessFile(FileInfo file) - { - var accessControl = IOUtilities.PerformIO(file.GetAccessControl); - - // Quick and dirty check. If we can't even get the access control object, then we - // can't access the file. - if (accessControl == null) - { - return false; - } - - // TODO(cyrusn): Actually add checks here. - return true; - } - - private bool CanAccessDirectory(DirectoryInfo directory) - { - var accessControl = IOUtilities.PerformIO(directory.GetAccessControl); - - // Quick and dirty check. If we can't even get the access control object, then we - // can't access the file. - if (accessControl == null) - { - return false; - } - - // TODO(cyrusn): Do more checks here. - return true; - } - - private IEnumerable GetFilesAndDirectoriesInSearchPaths() - { - return _searchPaths.SelectMany(GetFilesAndDirectoriesInDirectory); - } - - private IEnumerable GetLogicalDrives() - { - // First, we may have a filename, so let's include all drives - return from d in _lazyGetDrives.Value - where d.Length > 0 && (d.Last() == Path.DirectorySeparatorChar || d.Last() == Path.AltDirectorySeparatorChar) - let text = d.Substring(0, d.Length - 1) - select CommonCompletionItem.Create(text, glyph: _folderGlyph, rules: _itemRules); - } - - private static FileSystemInfo[] GetFileSystemInfos(DirectoryInfo directoryInfo) - { - return IOUtilities.PerformIO(directoryInfo.GetFileSystemInfos, Array.Empty()); - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/PathCompletionUtilities.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/PathCompletionUtilities.cs deleted file mode 100644 index 2f6774440b78c..0000000000000 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FileSystem/PathCompletionUtilities.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem -{ - internal static class PathCompletionUtilities - { - internal static bool IsTriggerCharacter(SourceText text, int characterPosition) - { - // Bring up completion when the user types a quote (i.e.: #r "), or if they type a slash - // path separator character, or if they type a comma (#r "foo,version..."). - // - // Also, if they're starting a word. i.e. #r "c:\W - var ch = text[characterPosition]; - return ch == '"' || ch == '\\' || ch == ',' || - CommonCompletionUtilities.IsStartingNewWord(text, characterPosition, char.IsLetter, char.IsLetterOrDigit); - } - - internal static bool IsFilterCharacter(CompletionItem item, char ch, string textTypedSoFar) - { - // If the user types something that matches what is in the completion list, then just - // count it as a filter character. - - // For example, if they type "Program " and "Program Files" is in the list, the - // should be counted as a filter character and not a commit character. - return item.DisplayText.StartsWith(textTypedSoFar, StringComparison.OrdinalIgnoreCase); - } - - internal static bool IsCommitcharacter(CompletionItem item, char ch, string textTypedSoFar) - { - return ch == '"' || ch == '\\' || ch == ','; - } - - internal static bool SendEnterThroughToEditor(CompletionItem item, string textTypedSoFar) - { - return false; - } - - internal static string GetPathThroughLastSlash(string quotedPath, int quotedPathStart, int position) - { - Contract.ThrowIfTrue(quotedPath[0] != '"'); - - const int QuoteLength = 1; - - var positionInQuotedPath = position - quotedPathStart; - var path = quotedPath.Substring(QuoteLength, positionInQuotedPath - QuoteLength).Trim(); - var afterLastSlashIndex = AfterLastSlashIndex(path, path.Length); - - // We want the portion up to, and including the last slash if there is one. That way if - // the user pops up completion in the middle of a path (i.e. "C:\Win") then we'll - // consider the path to be "C:\" and we will show appropriate completions. - return afterLastSlashIndex >= 0 ? path.Substring(0, afterLastSlashIndex) : path; - } - - internal static TextSpan GetTextChangeSpan(string quotedPath, int quotedPathStart, int position) - { - // We want the text change to be from after the last slash to the end of the quoted - // path. If there is no last slash, then we want it from right after the start quote - // character. - var positionInQuotedPath = position - quotedPathStart; - - // Where we want to start tracking is right after the slash (if we have one), or else - // right after the string starts. - var afterLastSlashIndex = PathCompletionUtilities.AfterLastSlashIndex(quotedPath, positionInQuotedPath); - var afterFirstQuote = 1; - - var startIndex = Math.Max(afterLastSlashIndex, afterFirstQuote); - var endIndex = quotedPath.Length; - - // If the string ends with a quote, the we do not want to consume that. - if (EndsWithQuote(quotedPath)) - { - endIndex--; - } - - return TextSpan.FromBounds(startIndex + quotedPathStart, endIndex + quotedPathStart); - } - - internal static bool EndsWithQuote(string quotedPath) - { - return quotedPath.Length >= 2 && quotedPath[quotedPath.Length - 1] == '"'; - } - - /// - /// Returns the index right after the last slash that precedes 'position'. If there is no - /// slash in the string, -1 is returned. - /// - private static int AfterLastSlashIndex(string text, int position) - { - // Position might be out of bounds of the string (if the string is unterminated. Make - // sure it's within bounds. - position = Math.Min(position, text.Length - 1); - - int index; - if ((index = text.LastIndexOf('/', position)) >= 0 || - (index = text.LastIndexOf('\\', position)) >= 0) - { - return index + 1; - } - - return -1; - } - } -} diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 6aea6a5cfd0cb..c55a80cabbe01 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -600,28 +600,28 @@ private void ProcessNewTagTrees( var bufferToChanges = new Dictionary(); using (Logger.LogBlock(FunctionId.Tagger_TagSource_ProcessNewTags, cancellationToken)) { - foreach (var latestBuffer in newTagTrees.Keys) + foreach (var (latestBuffer, latestSpans) in newTagTrees) { var snapshot = spansToTag.First(s => s.SnapshotSpan.Snapshot.TextBuffer == latestBuffer).SnapshotSpan.Snapshot; - if (oldTagTrees.ContainsKey(latestBuffer)) + if (oldTagTrees.TryGetValue(latestBuffer, out var previousSpans)) { - var difference = ComputeDifference(snapshot, newTagTrees[latestBuffer], oldTagTrees[latestBuffer]); + var difference = ComputeDifference(snapshot, latestSpans, previousSpans); bufferToChanges[latestBuffer] = difference; } else { // It's a new buffer, so report all spans are changed - bufferToChanges[latestBuffer] = new DiffResult(added: newTagTrees[latestBuffer].GetSpans(snapshot).Select(t => t.Span), removed: null); + bufferToChanges[latestBuffer] = new DiffResult(added: latestSpans.GetSpans(snapshot).Select(t => t.Span), removed: null); } } - foreach (var oldBuffer in oldTagTrees.Keys) + foreach (var (oldBuffer, previousSpans) in oldTagTrees) { if (!newTagTrees.ContainsKey(oldBuffer)) { // This buffer disappeared, so let's notify that the old tags are gone - bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: oldTagTrees[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span)); + bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: previousSpans.GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span)); } } } diff --git a/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs b/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs new file mode 100644 index 0000000000000..75c0a5a0af3a0 --- /dev/null +++ b/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs @@ -0,0 +1,234 @@ +// 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; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.Completion; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Completion +{ + public class FileSystemCompletionHelperTests + { + private void AssertItemsEqual(ImmutableArray actual, params string[] expected) + { + AssertEx.Equal( + expected, + actual.Select(c => $"'{c.DisplayText}', {string.Join(", ", c.Tags)}, '{c.Properties["Description"]}'"), + itemInspector: c => $"@\"{c}\""); + + Assert.True(actual.All(i => i.Rules == TestFileSystemCompletionHelper.CompletionRules)); + } + + [ConditionalFact(typeof(WindowsOnly))] + public void GetItems_Windows1() + { + var fsc = new TestFileSystemCompletionHelper( + searchPaths: ImmutableArray.Create(@"X:\A", @"X:\B"), + baseDirectoryOpt: @"Z:\C", + allowableExtensions: ImmutableArray.Create(".abc", ".def"), + drives: new[] { @"X:\", @"Z:\" }, + directories: new[] + { + @"X:", + @"X:\A", + @"X:\A\1", + @"X:\A\2", + @"X:\A\3", + @"X:\B", + @"Z:", + @"Z:\C", + @"Z:\D", + }, + files: new[] + { + @"X:\A\1\file1.abc", + @"X:\A\2\file2.abc", + @"X:\B\file4.x", + @"X:\B\file5.abc", + @"X:\B\hidden.def", + @"Z:\C\file6.def", + @"Z:\C\file.7.def", + }); + + // Note backslashes in description are escaped + AssertItemsEqual(fsc.GetItems("", CancellationToken.None), + @"'file6.def', File, C#, 'Text|Z:\5CC\5Cfile6.def'", + @"'file.7.def', File, C#, 'Text|Z:\5CC\5Cfile.7.def'", + @"'X:', Folder, 'Text|X:'", + @"'Z:', Folder, 'Text|Z:'", + @"'\\', , 'Text|\5C\5C'", + @"'1', Folder, 'Text|X:\5CA\5C1'", + @"'2', Folder, 'Text|X:\5CA\5C2'", + @"'3', Folder, 'Text|X:\5CA\5C3'", + @"'file5.abc', File, C#, 'Text|X:\5CB\5Cfile5.abc'"); + + AssertItemsEqual(fsc.GetItems(@"X:\A\", CancellationToken.None), + @"'1', Folder, 'Text|X:\5CA\5C1'", + @"'2', Folder, 'Text|X:\5CA\5C2'", + @"'3', Folder, 'Text|X:\5CA\5C3'"); + + AssertItemsEqual(fsc.GetItems(@"X:\B\", CancellationToken.None), + @"'file5.abc', File, C#, 'Text|X:\5CB\5Cfile5.abc'"); + + AssertItemsEqual(fsc.GetItems(@"Z:\", CancellationToken.None), + @"'C', Folder, 'Text|Z:\5CC'", + @"'D', Folder, 'Text|Z:\5CD'"); + + AssertItemsEqual(fsc.GetItems(@"Z:", CancellationToken.None), + @"'Z:', Folder, 'Text|Z:'"); + + AssertItemsEqual(fsc.GetItems(@"\", CancellationToken.None), + @"'\\', , 'Text|\5C\5C'"); + } + + [ConditionalFact(typeof(WindowsOnly))] + public void GetItems_Windows_NoBaseDirectory() + { + var fsc = new TestFileSystemCompletionHelper( + searchPaths: ImmutableArray.Create(@"X:\A", @"X:\B"), + baseDirectoryOpt: null, + allowableExtensions: ImmutableArray.Create(".abc", ".def"), + drives: new[] { @"X:\" }, + directories: new[] + { + @"X:", + @"X:\A", + @"X:\A\1", + @"X:\A\2", + @"X:\A\3", + @"X:\B", + }, + files: new[] + { + @"X:\A\1\file1.abc", + @"X:\A\2\file2.abc", + @"X:\B\file4.x", + @"X:\B\file5.abc", + @"X:\B\hidden.def", + }); + + // Note backslashes in description are escaped + AssertItemsEqual(fsc.GetItems(@"", CancellationToken.None), + @"'X:', Folder, 'Text|X:'", + @"'\\', , 'Text|\5C\5C'", + @"'1', Folder, 'Text|X:\5CA\5C1'", + @"'2', Folder, 'Text|X:\5CA\5C2'", + @"'3', Folder, 'Text|X:\5CA\5C3'", + @"'file5.abc', File, C#, 'Text|X:\5CB\5Cfile5.abc'"); + } + + [ConditionalFact(typeof(WindowsOnly))] + public void GetItems_Windows_NoSearchPaths() + { + var fsc = new TestFileSystemCompletionHelper( + searchPaths: ImmutableArray.Empty, + baseDirectoryOpt: null, + allowableExtensions: ImmutableArray.Create(".abc", ".def"), + drives: new[] { @"X:\" }, + directories: new[] + { + @"X:", + @"X:\A", + @"X:\A\1", + @"X:\A\2", + @"X:\A\3", + @"X:\B", + }, + files: new[] + { + @"X:\A\1\file1.abc", + @"X:\A\2\file2.abc", + @"X:\B\file4.x", + @"X:\B\file5.abc", + @"X:\B\hidden.def", + }); + + // Note backslashes in description are escaped + AssertItemsEqual(fsc.GetItems(@"", CancellationToken.None), + @"'X:', Folder, 'Text|X:'", + @"'\\', , 'Text|\5C\5C'"); + } + + [ConditionalFact(typeof(WindowsOnly))] + public void GetItems_Windows_Network() + { + var fsc = new TestFileSystemCompletionHelper( + searchPaths: ImmutableArray.Empty, + baseDirectoryOpt: null, + allowableExtensions: ImmutableArray.Create(".cs"), + drives: Array.Empty(), + directories: new[] + { + @"\\server\share", + @"\\server\share\C", + @"\\server\share\D", + }, + files: new[] + { + @"\\server\share\C\b.cs", + @"\\server\share\C\c.cs", + @"\\server\share\D\e.cs", + }); + + AssertItemsEqual(fsc.GetItems(@"\\server\share\", CancellationToken.None), + @"'C', Folder, 'Text|\5C\5Cserver\5Cshare\5CC'", + @"'D', Folder, 'Text|\5C\5Cserver\5Cshare\5CD'"); + + AssertItemsEqual(fsc.GetItems(@"\\server\share\C\", CancellationToken.None), + @"'b.cs', File, C#, 'Text|\5C\5Cserver\5Cshare\5CC\5Cb.cs'", + @"'c.cs', File, C#, 'Text|\5C\5Cserver\5Cshare\5CC\5Cc.cs'"); + } + + [ConditionalFact(typeof(UnixLikeOnly))] + public void GetItems_Unix1() + { + var fsc = new TestFileSystemCompletionHelper( + searchPaths: ImmutableArray.Create(@"/A", @"/B"), + baseDirectoryOpt: @"/C", + allowableExtensions: ImmutableArray.Create(".abc", ".def"), + drives: Array.Empty(), + directories: new[] + { + @"/A", + @"/A/1", + @"/A/2", + @"/A/3", + @"/B", + @"/C", + @"/D", + }, + files: new[] + { + @"/A/1/file1.abc", + @"/A/2/file2.abc", + @"/B/file4.x", + @"/B/file5.abc", + @"/B/hidden.def", + @"/C/file6.def", + @"/C/file.7.def", + }); + + // Note backslashes in description are escaped + AssertItemsEqual(fsc.GetItems(@"", CancellationToken.None), + @"'file6.def', File, C#, 'Text|/C/file6.def'", + @"'file.7.def', File, C#, 'Text|/C/file.7.def'", + @"'/', Folder, 'Text|/'", + @"'1', Folder, 'Text|/A/1'", + @"'2', Folder, 'Text|/A/2'", + @"'3', Folder, 'Text|/A/3'", + @"'file5.abc', File, C#, 'Text|/B/file5.abc'"); + + AssertItemsEqual(fsc.GetItems(@"/", CancellationToken.None), + @"'A', Folder, 'Text|/A'", + @"'B', Folder, 'Text|/B'", + @"'C', Folder, 'Text|/C'", + @"'D', Folder, 'Text|/D'"); + + AssertItemsEqual(fsc.GetItems(@"/B/", CancellationToken.None), + @"'file5.abc', File, C#, 'Text|/B/file5.abc'"); + } + } +} diff --git a/src/EditorFeatures/Test/Completion/GlobalAssemblyCacheCompletionHelperTests.cs b/src/EditorFeatures/Test/Completion/GlobalAssemblyCacheCompletionHelperTests.cs new file mode 100644 index 0000000000000..ce5abe28ef17a --- /dev/null +++ b/src/EditorFeatures/Test/Completion/GlobalAssemblyCacheCompletionHelperTests.cs @@ -0,0 +1,44 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.Completion.FileSystem; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntelliSense.CompletionSetSources +{ + [Trait(Traits.Feature, Traits.Features.Completion)] + public class GlobalAssemblyCacheCompletionHelperTests + { + [ConditionalFact(typeof(WindowsOnly))] + public void ExistingReference() + { + var code = "System.Windows"; + VerifyPresence(code, "System.Windows.Forms"); + } + + [ConditionalFact(typeof(WindowsOnly))] + public void FullReferenceIdentity() + { + var code = "System,"; + VerifyPresence(code, typeof(System.Diagnostics.Process).Assembly.FullName); + } + + private static void VerifyPresence(string pathSoFar, string completionItem) + { + var completions = GetItems(pathSoFar); + Assert.True(completions.Any(c => c.DisplayText == completionItem)); + } + + private static IEnumerable GetItems(string pathSoFar) + { + var helper = new GlobalAssemblyCacheCompletionHelper(CompletionItemRules.Default); + return helper.GetItems(pathSoFar, CancellationToken.None); + } + } +} diff --git a/src/EditorFeatures/Test/Completion/TestFileSystemCompletionHelper.cs b/src/EditorFeatures/Test/Completion/TestFileSystemCompletionHelper.cs new file mode 100644 index 0000000000000..c1a8d9d180256 --- /dev/null +++ b/src/EditorFeatures/Test/Completion/TestFileSystemCompletionHelper.cs @@ -0,0 +1,62 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.Completion; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Completion +{ + internal sealed class TestFileSystemCompletionHelper : FileSystemCompletionHelper + { + internal static readonly CompletionItemRules CompletionRules = CompletionItemRules.Default; + + private readonly ImmutableArray _directories; + private readonly ImmutableArray _files; + private readonly ImmutableArray _drives; + + public TestFileSystemCompletionHelper( + ImmutableArray searchPaths, + string baseDirectoryOpt, + ImmutableArray allowableExtensions, + IEnumerable drives, + IEnumerable directories, + IEnumerable files) + : base(Glyph.OpenFolder, Glyph.CSharpFile, searchPaths, baseDirectoryOpt, allowableExtensions, CompletionRules) + { + Assert.True(drives.All(d => d.EndsWith(PathUtilities.DirectorySeparatorStr))); + Assert.True(directories.All(d => !d.EndsWith(PathUtilities.DirectorySeparatorStr))); + + _drives = ImmutableArray.CreateRange(drives); + _directories = ImmutableArray.CreateRange(directories); + _files = ImmutableArray.CreateRange(files); + } + + protected override string[] GetLogicalDrives() => + _drives.ToArray(); + + protected override bool IsVisibleFileSystemEntry(string fullPath) => + !fullPath.Contains("hidden"); + + protected override bool DirectoryExists(string fullPath) => + _directories.Contains(fullPath.TrimEnd(PathUtilities.DirectorySeparatorChar)); + + protected override IEnumerable EnumerateDirectories(string fullDirectoryPath) => + Enumerate(_directories, fullDirectoryPath); + + protected override IEnumerable EnumerateFiles(string fullDirectoryPath) => + Enumerate(_files, fullDirectoryPath); + + private IEnumerable Enumerate(ImmutableArray entries, string fullDirectoryPath) + { + var withTrailingSeparator = fullDirectoryPath.TrimEnd(PathUtilities.DirectorySeparatorChar) + PathUtilities.DirectorySeparatorChar; + return from d in entries + where d.StartsWith(withTrailingSeparator) + let nextSeparator = d.IndexOf(PathUtilities.DirectorySeparatorChar, withTrailingSeparator.Length) + select d.Substring(0, (nextSeparator >= 0) ? nextSeparator : d.Length); + } + } +} diff --git a/src/EditorFeatures/Test/EditorServicesTest.csproj b/src/EditorFeatures/Test/EditorServicesTest.csproj index 3e97cdf6e5b5e..2e67d5ab70e6b 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -224,6 +224,9 @@ + + + diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineTests.CSharpConflicts.vb b/src/EditorFeatures/Test2/Rename/RenameEngineTests.CSharpConflicts.vb index acc436b8b6db9..e7a15b31985d4 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineTests.CSharpConflicts.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineTests.CSharpConflicts.vb @@ -3585,6 +3585,31 @@ partial class {|current:$$C|} { } result.AssertLabeledSpansAre("current", type:=RelatedLocationType.NoConflict) End Using End Sub + + + + + Public Sub RenameMethodToFinalizeWithDestructorPresent() + Using result = RenameEngineResult.Create(_outputHelper, + + + +class C +{ + ~{|Conflict:C|}() { } + void $$[|M|]() + { + int x = 7; + int y = ~x; + } +} + + + , renameTo:="Finalize") + + result.AssertLabeledSpansAre("Conflict", type:=RelatedLocationType.UnresolvedConflict) + End Using + End Sub End Class End Namespace diff --git a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj index e72245fa212ed..af02f007d3e4b 100644 --- a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj +++ b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj @@ -287,6 +287,7 @@ + diff --git a/src/EditorFeatures/TestUtilities/TestExportProvider.cs b/src/EditorFeatures/TestUtilities/TestExportProvider.cs index 5451bfbcc8d90..47ded14219e30 100644 --- a/src/EditorFeatures/TestUtilities/TestExportProvider.cs +++ b/src/EditorFeatures/TestUtilities/TestExportProvider.cs @@ -72,6 +72,8 @@ private static Type[] GetNeutralAndCSharpAndVisualBasicTypes() typeof(CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator), typeof(CSharp.LanguageServices.CSharpContentTypeLanguageService), typeof(VisualBasic.LanguageServices.VisualBasicContentTypeLanguageService), + typeof(CodeAnalysis.CSharp.Execution.CSharpOptionsSerializationService), + typeof(CodeAnalysis.VisualBasic.Execution.VisualBasicOptionsSerializationService), typeof(TestExportProvider) }; diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs new file mode 100644 index 0000000000000..cbd71aef192f9 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs @@ -0,0 +1,34 @@ +// 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.Composition; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +{ + [ExportWorkspaceService(typeof(ISymbolRenamedCodeActionOperationFactoryWorkspaceService), TestWorkspace.WorkspaceName), Shared] + public class TestSymbolRenamedCodeActionOperationFactoryWorkspaceService : ISymbolRenamedCodeActionOperationFactoryWorkspaceService + { + public CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) + { + return new Operation(symbol, newName, startingSolution, updatedSolution); + } + + public class Operation : CodeActionOperation + { + public ISymbol _symbol; + public string _newName; + public Solution _startingSolution; + public Solution _updatedSolution; + + public Operation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) + { + _symbol = symbol; + _newName = newName; + _startingSolution = startingSolution; + _updatedSolution = updatedSolution; + } + } + } +} diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb index b3680cdeb1b2c..82c174427f336 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb @@ -28,8 +28,8 @@ Imports System.Collections.Generic Imports System.Linq Module Program Sub Main(args As String()) - Const {|Rename:V|} As Integer = 1 + 1 - Console.WriteLine(V) + Const {|Rename:Value|} As Integer = 1 + 1 + Console.WriteLine(Value) End Sub End Module", index:=2) @@ -51,8 +51,8 @@ Imports System.Collections.Generic Imports System.Linq Module Program Sub Main(args As String()) - Const {|Rename:V|} As Integer = 1 + 1 - Console.WriteLine(V) + Const {|Rename:Value|} As Integer = 1 + 1 + Console.WriteLine(Value) End Sub End Module", index:=3) @@ -826,9 +826,9 @@ End Class") End Sub End Class", "Class Foo - Private Const {|Rename:V|} As Integer = 42 + Private Const {|Rename:X|} As Integer = 42 Sub New() - MyClass.New(V) + MyClass.New(X) End Sub Sub New(x As Integer) End Sub @@ -1158,8 +1158,8 @@ Module Program If True Then If True Then - Const {|Rename:V|} As Integer = 1 - Console.WriteLine(V) + Const {|Rename:Value|} As Integer = 1 + Console.WriteLine(Value) Else Console.WriteLine(2) End If @@ -1198,8 +1198,8 @@ Module Program If True Then Console.WriteLine(1) Else - Const {|Rename:V|} As Integer = 2 - Console.WriteLine(V) + Const {|Rename:Value|} As Integer = 2 + Console.WriteLine(Value) End If Else Console.WriteLine(3) @@ -1234,8 +1234,8 @@ Module Program If True Then If True Then Console.WriteLine(1) Else Console.WriteLine(2) Else - Const {|Rename:V|} As Integer = 3 - Console.WriteLine(V) + Const {|Rename:Value|} As Integer = 3 + Console.WriteLine(Value) End If End Sub End Module @@ -1258,8 +1258,8 @@ End Module", Module Program Sub Main Dim a = Sub(x As Integer) - Dim {|Rename:v|} As Integer = x + 1 - Console.WriteLine(v) ' Introduce local + Dim {|Rename:value|} As Integer = x + 1 + Console.WriteLine(value) ' Introduce local End Sub End Sub End Module") @@ -1280,8 +1280,8 @@ Module Program Sub Main Dim a = Sub(x As Integer) If True Then - Dim {|Rename:v|} As Integer = x + 1 - Console.WriteLine(v) + Dim {|Rename:value|} As Integer = x + 1 + Console.WriteLine(value) Else Console.WriteLine() End If @@ -1307,8 +1307,8 @@ Module Program If True Then Console.WriteLine() Else - Dim {|Rename:v|} As Integer = x + 1 - Console.WriteLine(v) + Dim {|Rename:value|} As Integer = x + 1 + Console.WriteLine(value) End If End Sub End Sub @@ -1329,8 +1329,8 @@ End Module", Module Program Sub Main Dim a = Sub(x As Integer) - Dim {|Rename:v|} As Integer = x + 1 - If True Then Console.WriteLine(v) Else Console.WriteLine(v) + Dim {|Rename:value|} As Integer = x + 1 + If True Then Console.WriteLine(value) Else Console.WriteLine(value) End Sub End Sub End Module", @@ -1351,10 +1351,10 @@ End Module", "Module Program Sub Main(args As String()) Dim query = Sub(a) - Dim {|Rename:v|} As Object = a Or a + Dim {|Rename:arg1|} As Object = a Or a a = New With {Key .Key = Function(ByVal arg As Integer) As Integer Return arg - End Function}.Key.Invoke(v) + End Function}.Key.Invoke(arg1) End Sub End Sub End Module") @@ -2093,8 +2093,8 @@ End Class", Class C Private Shared Sub Main(args As String()) Dim hSet = New HashSet(Of String)() - Dim {|Rename:v|} As String = hSet.ToString() - hSet.Add(v) + Dim {|Rename:item|} As String = hSet.ToString() + hSet.Add(item) End Sub End Class") End Function @@ -2175,8 +2175,8 @@ Imports System.Collections.Generic Imports System.Linq Module Program Sub Main() - Dim {|Rename:enumerable1|} As IEnumerable(Of Char) = From x In """" - [Take](enumerable1) + Dim {|Rename:x1|} As IEnumerable(Of Char) = From x In """" + [Take](x1) End Sub Sub Take(x) End Sub @@ -2621,8 +2621,8 @@ Imports System Namespace N Class C Public Sub M() - Dim {|Rename:v|} As FormattableString = $"""" - Dim f = FormattableString.Invariant(v) + Dim {|Rename:formattable|} As FormattableString = $"""" + Dim f = FormattableString.Invariant(formattable) End Sub End Class End Namespace" @@ -2889,5 +2889,58 @@ End Class Await TestInRegularAndScriptAsync(code, expected, ignoreTrivia:=False) End Function + + + Public Async Function TestPickNameBasedOnArgument1() As Task + Await TestInRegularAndScriptAsync( +"class C + public sub new(a as string, b as string) + dim c = new TextSpan([|integer.Parse(a)|], integer.Parse(b)) + end sub +end class + +structure TextSpan + public sub new(start as integer, length as integer) + end sub +end structure", +"class C + public sub new(a as string, b as string) + Dim {|Rename:start|} As Integer = integer.Parse(a) + dim c = new TextSpan(start, integer.Parse(b)) + end sub +end class + +structure TextSpan + public sub new(start as integer, length as integer) + end sub +end structure") + End Function + + + + Public Async Function TestPickNameBasedOnArgument2() As Task + Await TestInRegularAndScriptAsync( +"class C + public sub new(a as string, b as string) + dim c = new TextSpan(integer.Parse(a), [|integer.Parse(b)|]) + end sub +end class + +structure TextSpan + public sub new(start as integer, length as integer) + end sub +end structure", +"class C + public sub new(a as string, b as string) + Dim {|Rename:length|} As Integer = integer.Parse(b) + dim c = new TextSpan(integer.Parse(a), length) + end sub +end class + +structure TextSpan + public sub new(start as integer, length as integer) + end sub +end structure") + End Function End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb index 92d7484321008..fff18f70367c7 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb @@ -44,7 +44,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeActions.AddImp ' Make a loose mock for the installer service. We don't care what this test ' calls on it. Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.TryInstallPackage(It.IsAny(Of Workspace), It.IsAny(Of DocumentId), It.IsAny(Of String), "NuGetPackage", It.IsAny(Of String), It.IsAny(Of Boolean), It.IsAny(Of CancellationToken))). Returns(True) @@ -71,7 +71,7 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa ' Make a loose mock for the installer service. We don't care what this test ' calls on it. Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.TryInstallPackage(It.IsAny(Of Workspace), It.IsAny(Of DocumentId), It.IsAny(Of String), "NuGetPackage", It.IsAny(Of String), It.IsAny(Of Boolean), It.IsAny(Of CancellationToken))). Returns(True) @@ -98,7 +98,7 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa ' Make a loose mock for the installer service. We don't care what this test ' calls on it. Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.TryInstallPackage(It.IsAny(Of Workspace), It.IsAny(Of DocumentId), It.IsAny(Of String), "NuGetPackage", It.IsAny(Of String), It.IsAny(Of Boolean), It.IsAny(Of CancellationToken))). Returns(False) @@ -123,7 +123,7 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa ' Make a loose mock for the installer service. We don't care what this test ' calls on it. Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.IsInstalled(It.IsAny(Of Workspace)(), It.IsAny(Of ProjectId)(), "NuGetPackage")). Returns(True) @@ -145,7 +145,7 @@ New TestParameters(fixProviderData:=New ProviderData(installerServiceMock.Object ' Make a loose mock for the installer service. We don't care what this test ' calls on it. Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.GetInstalledVersions("NuGetPackage")). Returns(ImmutableArray.Create("1.0", "2.0")) @@ -185,7 +185,7 @@ parameters:=New TestParameters(fixProviderData:=data)) Public Async Function TestInstallGetsCalledNoVersion() As Task Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.TryInstallPackage(It.IsAny(Of Workspace), It.IsAny(Of DocumentId), It.IsAny(Of String), "NuGetPackage", Nothing, It.IsAny(Of Boolean), It.IsAny(Of CancellationToken))). Returns(True) @@ -211,7 +211,7 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa Public Async Function TestInstallGetsCalledWithVersion() As Task Dim installerServiceMock = New Mock(Of IPackageInstallerService)(MockBehavior.Loose) - installerServiceMock.SetupGet(Function(i) i.IsEnabled).Returns(True) + installerServiceMock.Setup(Function(i) i.IsEnabled(It.IsAny(Of ProjectId))).Returns(True) installerServiceMock.SetupGet(Function(i) i.PackageSources).Returns(NugetPackageSources) installerServiceMock.Setup(Function(s) s.GetInstalledVersions("NuGetPackage")). Returns(ImmutableArray.Create("1.0")) diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateMethod/GenerateMethodTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateMethod/GenerateMethodTests.vb index 3d589a34251cd..d2210b700b9b4 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateMethod/GenerateMethodTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateMethod/GenerateMethodTests.vb @@ -3926,6 +3926,62 @@ Class Program End Class") End Function + + + Public Async Function TupleElement1() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Public Class Q + Sub Main() + Dim x As (Integer, String) = ([|Foo|](), """") + End Sub +End Class +", +" +Imports System + +Public Class Q + Sub Main() + Dim x As (Integer, String) = (Foo(), """") + End Sub + + Private Function Foo() As Integer + Throw New NotImplementedException() + End Function +End Class +") + End Function + + + + Public Async Function TupleElement2() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Public Class Q + Sub Main() + Dim x As (Integer, String) = (0, [|Foo|]()) + End Sub +End Class +", +" +Imports System + +Public Class Q + Sub Main() + Dim x As (Integer, String) = (0, Foo()) + End Sub + + Private Function Foo() As String + Throw New NotImplementedException() + End Function +End Class +") + End Function + Public Async Function MethodWithTupleWithNames() As Task Await TestInRegularAndScriptAsync( diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.vb index 582664dc59c2f..15fce1a1aa589 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveUnusedVariable/RemoveUnusedVariableTest.vb @@ -9,8 +9,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Remove Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (Nothing, - New RemoveUnusedVariableCodeFixProvider()) + Return (Nothing, New VisualBasicRemoveUnusedVariableCodeFixProvider()) End Function diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs index c933b603d5757..dd41414e39b75 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs @@ -1452,6 +1452,28 @@ .locals init (C.<>c__DisplayClass1_0 V_0, //CS$<>8__locals0 }); } + [Fact(Skip = "18273"), WorkItem(18273, "https://github.com/dotnet/roslyn/issues/18273")] + public void CapturedLocalInNestedLambda() + { + var source = @" +using System; + +class C +{ + void M() { } +}"; + var compilation0 = CreateStandardCompilation(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.M"); + + var testData = new CompilationTestData(); + context.CompileExpression("new Action(() => { int x; new Func(() => x).Invoke(); }).Invoke()", out var error, testData); + Assert.Null(error); + testData.GetMethodData("<>x.<>m0").VerifyIL(""); + }); + } + [Fact] public void NestedLambdas() { diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/BasicExpressionCompiler.vbproj b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/BasicExpressionCompiler.vbproj index 2c1e27c49f631..4b839e0102d4c 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/BasicExpressionCompiler.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/BasicExpressionCompiler.vbproj @@ -9,6 +9,7 @@ Library Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.ExpressionCompiler netstandard1.3 + $(NoWarn);40057 portable-net46 diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj index 973c1b53661b3..0b1a76f72bf1c 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj @@ -12,6 +12,7 @@ Library Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.ResultProvider net20 + $(NoWarn);40057 diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/Portable/BasicResultProvider.Portable.vbproj b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/Portable/BasicResultProvider.Portable.vbproj index 7344f2e4c6ae3..927ec6ffb56a3 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/Portable/BasicResultProvider.Portable.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/Portable/BasicResultProvider.Portable.vbproj @@ -11,6 +11,7 @@ netstandard1.3 portable-net45+win8 false + $(NoWarn);40057 diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj index a15b756b7d3c3..1dfc62d0c6efa 100644 --- a/src/Features/CSharp/Portable/CSharpFeatures.csproj +++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj @@ -62,7 +62,7 @@ - + diff --git a/src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/CSharpRemoveUnusedVariableCodeFixProvider.cs similarity index 74% rename from src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.cs rename to src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/CSharpRemoveUnusedVariableCodeFixProvider.cs index 9dddc12e4263b..6e20eff76d446 100644 --- a/src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/RemoveUnusedVariable/CSharpRemoveUnusedVariableCodeFixProvider.cs @@ -9,7 +9,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.RemoveUnusedVariable { [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnusedVariable), Shared] - internal partial class RemoveUnusedVariableCodeFixProvider : AbstractRemoveUnusedVariableCodeFixProvider + [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddImport)] + internal partial class CSharpRemoveUnusedVariableCodeFixProvider : AbstractRemoveUnusedVariableCodeFixProvider { private const string CS0168 = nameof(CS0168); private const string CS0219 = nameof(CS0219); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs index cb53f35a8b39a..f7a154963db18 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs @@ -91,7 +91,9 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) if (type.TypeKind != TypeKind.Enum) { - type = GetCompletionListType(type, semanticModel.GetEnclosingNamedType(position, cancellationToken), semanticModel.Compilation); + type = TryGetEnumTypeInEnumInitializer(semanticModel, token, type, cancellationToken) ?? + TryGetCompletionListType(type, semanticModel.GetEnclosingNamedType(position, cancellationToken), semanticModel.Compilation); + if (type == null) { return; @@ -104,7 +106,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) } // Does type have any aliases? - ISymbol alias = await type.FindApplicableAlias(position, semanticModel, cancellationToken).ConfigureAwait(false); + var alias = await type.FindApplicableAlias(position, semanticModel, cancellationToken).ConfigureAwait(false); var displayService = document.GetLanguageService(); var displayText = alias != null @@ -128,6 +130,47 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) } } + private ITypeSymbol TryGetEnumTypeInEnumInitializer( + SemanticModel semanticModel, SyntaxToken token, + ITypeSymbol type, CancellationToken cancellationToken) + { + // https://github.com/dotnet/roslyn/issues/5419 + // + // 14.3: "Within an enum member initializer, values of other enum members are always + // treated as having the type of their underlying type" + + // i.e. if we have "enum E { X, Y, Z = X | + // then we want to offer the enum after the |. However, the compiler will report this + // as an 'int' type, not the enum type. + + // See if we're after a common enum-combining operator. + if (token.Kind() == SyntaxKind.BarToken || + token.Kind() == SyntaxKind.AmpersandToken || + token.Kind() == SyntaxKind.CaretToken) + { + // See if the type we're looking at is the underlying type for the enum we're contained in. + var containingType = semanticModel.GetEnclosingNamedType(token.SpanStart, cancellationToken); + if (containingType?.TypeKind == TypeKind.Enum && + type.Equals(containingType.EnumUnderlyingType)) + { + // If so, walk back to the token before the operator token and see if it binds to a member + // of this enum. + var previousToken = token.GetPreviousToken(); + var symbol = semanticModel.GetSymbolInfo(previousToken.Parent, cancellationToken).Symbol; + + if (symbol?.Kind == SymbolKind.Field && + containingType.Equals(symbol.ContainingType)) + { + // If so, then offer this as a place for enum completion for the enum we're currently + // inside of. + return containingType; + } + } + } + + return null; + } + protected override Task GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) => SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken); @@ -136,7 +179,7 @@ protected override Task GetDescriptionWorkerAsync(Documen .WithMatchPriority(MatchPriority.Preselect) .WithSelectionBehavior(CompletionItemSelectionBehavior.HardSelection); - private INamedTypeSymbol GetCompletionListType(ITypeSymbol type, INamedTypeSymbol within, Compilation compilation) + private INamedTypeSymbol TryGetCompletionListType(ITypeSymbol type, INamedTypeSymbol within, Compilation compilation) { // PERF: None of the SpecialTypes include tags, // so we don't even need to load the documentation. diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index a49f7d2650dd7..a60a91374a1ba 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1,6 +1,5 @@ // 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -306,7 +305,8 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody break; case SyntaxKind.ForEachStatement: - statementPart = (int)GetStatementPart((ForEachStatementSyntax)node, position); + case SyntaxKind.ForEachVariableStatement: + statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node, position); break; case SyntaxKind.VariableDeclaration: @@ -352,7 +352,7 @@ private static TextSpan GetActiveSpan(BlockSyntax node, BlockPart part) } } - private static ForEachPart GetStatementPart(ForEachStatementSyntax node, int position) + private static ForEachPart GetStatementPart(CommonForEachStatementSyntax node, int position) { return position < node.OpenParenToken.SpanStart ? ForEachPart.ForEach : position < node.InKeyword.SpanStart ? ForEachPart.VariableDeclaration : @@ -381,6 +381,27 @@ private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart p } } + private static TextSpan GetActiveSpan(ForEachVariableStatementSyntax node, ForEachPart part) + { + switch (part) + { + case ForEachPart.ForEach: + return node.ForEachKeyword.Span; + + case ForEachPart.VariableDeclaration: + return TextSpan.FromBounds(node.Variable.SpanStart, node.Variable.Span.End); + + case ForEachPart.In: + return node.InKeyword.Span; + + case ForEachPart.Expression: + return node.Expression.Span; + + default: + throw ExceptionUtilities.UnexpectedValue(part); + } + } + protected override bool AreEquivalent(SyntaxNode left, SyntaxNode right) { return SyntaxFactory.AreEquivalent(left, right); @@ -601,6 +622,10 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out span = GetActiveSpan((ForEachStatementSyntax)node, (ForEachPart)statementPart); return true; + case SyntaxKind.ForEachVariableStatement: + span = GetActiveSpan((ForEachVariableStatementSyntax)node, (ForEachPart)statementPart); + return true; + case SyntaxKind.DoStatement: // The active statement of DoStatement node is the while condition, // which is lexically not the closest breakpoint span (the body is). @@ -732,10 +757,11 @@ protected override bool AreEquivalentActiveStatements(SyntaxNode oldStatement, S return true; case SyntaxKind.ForEachStatement: + case SyntaxKind.ForEachVariableStatement: Debug.Assert(statementPart != 0); // only check the expression, edits in the body and the variable declaration are allowed: - return AreEquivalentActiveStatements((ForEachStatementSyntax)oldStatement, (ForEachStatementSyntax)newStatement); + return AreEquivalentActiveStatements((CommonForEachStatementSyntax)oldStatement, (CommonForEachStatementSyntax)newStatement); case SyntaxKind.IfStatement: // only check the condition, edits in the body are allowed: @@ -807,11 +833,30 @@ private static bool AreEquivalentActiveStatements(UsingStatementSyntax oldNode, (SyntaxNode)newNode.Declaration ?? newNode.Expression); } - private static bool AreEquivalentActiveStatements(ForEachStatementSyntax oldNode, ForEachStatementSyntax newNode) + private static bool AreEquivalentActiveStatements(CommonForEachStatementSyntax oldNode, CommonForEachStatementSyntax newNode) { - // This is conservative, we might be able to allow changing the type. - return AreEquivalentIgnoringLambdaBodies(oldNode.Type, newNode.Type) - && AreEquivalentIgnoringLambdaBodies(oldNode.Expression, newNode.Expression); + if (oldNode.Kind() != newNode.Kind() || !AreEquivalentIgnoringLambdaBodies(oldNode.Expression, newNode.Expression)) + { + return false; + } + + switch (oldNode.Kind()) + { + case SyntaxKind.ForEachStatement: return AreEquivalentIgnoringLambdaBodies(((ForEachStatementSyntax)oldNode).Type, ((ForEachStatementSyntax)newNode).Type); + case SyntaxKind.ForEachVariableStatement: return AreEquivalentIgnoringLambdaBodies(((ForEachVariableStatementSyntax)oldNode).Variable, ((ForEachVariableStatementSyntax)newNode).Variable); + default: throw ExceptionUtilities.UnexpectedValue(oldNode.Kind()); + } + } + + private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldNode, CommonForEachStatementSyntax newNode) + { + List oldTokens = null; + List newTokens = null; + + StatementSyntaxComparer.GetLocalNames(oldNode, ref oldTokens); + StatementSyntaxComparer.GetLocalNames(newNode, ref newTokens); + + return DeclareSameIdentifiers(oldTokens.ToArray(), newTokens.ToArray()); } internal override bool IsMethod(SyntaxNode declaration) @@ -1259,8 +1304,9 @@ internal static TextSpan GetDiagnosticSpanImpl(SyntaxKind kind, SyntaxNode node, return TextSpan.FromBounds(forStatement.ForKeyword.SpanStart, forStatement.CloseParenToken.Span.End); case SyntaxKind.ForEachStatement: - var forEachStatement = (ForEachStatementSyntax)node; - return TextSpan.FromBounds(forEachStatement.ForEachKeyword.SpanStart, forEachStatement.CloseParenToken.Span.End); + case SyntaxKind.ForEachVariableStatement: + var commonForEachStatement = (CommonForEachStatementSyntax)node; + return TextSpan.FromBounds(commonForEachStatement.ForEachKeyword.SpanStart, commonForEachStatement.CloseParenToken.Span.End); case SyntaxKind.LabeledStatement: return ((LabeledStatementSyntax)node).Identifier.Span; @@ -1342,9 +1388,6 @@ internal static TextSpan GetDiagnosticSpanImpl(SyntaxKind kind, SyntaxNode node, case SyntaxKind.GroupClause: return ((GroupClauseSyntax)node).GroupKeyword.Span; - case SyntaxKind.ForEachVariableStatement: - return ((ForEachVariableStatementSyntax)node).Variable.Span; - case SyntaxKind.IsPatternExpression: case SyntaxKind.TupleType: case SyntaxKind.TupleExpression: @@ -1549,6 +1592,7 @@ internal static string GetStatementDisplayNameImpl(SyntaxNode node) return CSharpFeaturesResources.lock_statement; case SyntaxKind.ForEachStatement: + case SyntaxKind.ForEachVariableStatement: return CSharpFeaturesResources.foreach_statement; case SyntaxKind.CheckedStatement: @@ -1604,9 +1648,6 @@ internal static string GetStatementDisplayNameImpl(SyntaxNode node) case SyntaxKind.IsPatternExpression: return CSharpFeaturesResources.is_pattern; - case SyntaxKind.ForEachVariableStatement: - return CSharpFeaturesResources.deconstruction; - case SyntaxKind.SimpleAssignmentExpression: if (((AssignmentExpressionSyntax)node).IsDeconstruction()) { @@ -3108,7 +3149,6 @@ private static bool IsUnsupportedCSharp7EnCNode(SyntaxNode n) { switch (n.Kind()) { - case SyntaxKind.ForEachVariableStatement: case SyntaxKind.LocalFunctionStatement: return true; default: @@ -3187,15 +3227,15 @@ private void ReportRudeEditsForAncestorsDeclaringInterStatementTemps( // // Unlike exception regions matching where we use LCS, we allow reordering of the statements. - ReportUnmatchedStatements(diagnostics, match, (int)SyntaxKind.LockStatement, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.LockStatement }, oldActiveStatement, newActiveStatement, areEquivalent: AreEquivalentActiveStatements, areSimilar: null); - ReportUnmatchedStatements(diagnostics, match, (int)SyntaxKind.FixedStatement, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.FixedStatement }, oldActiveStatement, newActiveStatement, areEquivalent: AreEquivalentActiveStatements, areSimilar: (n1, n2) => DeclareSameIdentifiers(n1.Declaration.Variables, n2.Declaration.Variables)); - ReportUnmatchedStatements(diagnostics, match, (int)SyntaxKind.UsingStatement, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.UsingStatement }, oldActiveStatement, newActiveStatement, areEquivalent: AreEquivalentActiveStatements, areSimilar: (using1, using2) => { @@ -3203,21 +3243,26 @@ private void ReportRudeEditsForAncestorsDeclaringInterStatementTemps( DeclareSameIdentifiers(using1.Declaration.Variables, using2.Declaration.Variables); }); - ReportUnmatchedStatements(diagnostics, match, (int)SyntaxKind.ForEachStatement, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.ForEachStatement, (int)SyntaxKind.ForEachVariableStatement }, oldActiveStatement, newActiveStatement, areEquivalent: AreEquivalentActiveStatements, - areSimilar: (n1, n2) => SyntaxFactory.AreEquivalent(n1.Identifier, n2.Identifier)); + areSimilar: AreSimilarActiveStatements); } private static bool DeclareSameIdentifiers(SeparatedSyntaxList oldVariables, SeparatedSyntaxList newVariables) { - if (oldVariables.Count != newVariables.Count) + return DeclareSameIdentifiers(oldVariables.Select(v => v.Identifier).ToArray(), newVariables.Select(v => v.Identifier).ToArray()); + } + + private static bool DeclareSameIdentifiers(SyntaxToken[] oldVariables, SyntaxToken[] newVariables) + { + if (oldVariables.Length != newVariables.Length) { return false; } - for (int i = 0; i < oldVariables.Count; i++) + for (int i = 0; i < oldVariables.Length; i++) { - if (!SyntaxFactory.AreEquivalent(oldVariables[i].Identifier, newVariables[i].Identifier)) + if (!SyntaxFactory.AreEquivalent(oldVariables[i], newVariables[i])) { return false; } diff --git a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs index f4700d61ebfd5..cb58a82f1cf29 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs @@ -1006,7 +1006,7 @@ private static void GetLocalNames(VariableDeclarationSyntax localDeclaration, re } } - private static void GetLocalNames(CommonForEachStatementSyntax commonForEach, ref List result) + internal static void GetLocalNames(CommonForEachStatementSyntax commonForEach, ref List result) { switch (commonForEach.Kind()) { diff --git a/src/Features/CSharp/Portable/GenerateConstructor/CSharpGenerateConstructorService.cs b/src/Features/CSharp/Portable/GenerateConstructor/CSharpGenerateConstructorService.cs index 547d78a7a7b9c..93679a43412f1 100644 --- a/src/Features/CSharp/Portable/GenerateConstructor/CSharpGenerateConstructorService.cs +++ b/src/Features/CSharp/Portable/GenerateConstructor/CSharpGenerateConstructorService.cs @@ -166,24 +166,18 @@ protected override bool TryInitializeSimpleAttributeNameGenerationState( } protected override ImmutableArray GenerateParameterNames( - SemanticModel semanticModel, IEnumerable arguments, IList reservedNames) - => semanticModel.GenerateParameterNames(arguments, reservedNames); + SemanticModel semanticModel, IEnumerable arguments, IList reservedNames, CancellationToken cancellationToken) + => semanticModel.GenerateParameterNames(arguments, reservedNames, cancellationToken); protected override ImmutableArray GenerateParameterNames( - SemanticModel semanticModel, IEnumerable arguments, IList reservedNames) - => semanticModel.GenerateParameterNames(arguments, reservedNames).ToImmutableArray(); + SemanticModel semanticModel, IEnumerable arguments, IList reservedNames, CancellationToken cancellationToken) + => semanticModel.GenerateParameterNames(arguments, reservedNames, cancellationToken).ToImmutableArray(); - protected override string GenerateNameForArgument( - SemanticModel semanticModel, ArgumentSyntax argument) - { - return semanticModel.GenerateNameForArgument(argument); - } + protected override string GenerateNameForArgument(SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken) + => semanticModel.GenerateNameForArgument(argument, cancellationToken); - protected override string GenerateNameForArgument( - SemanticModel semanticModel, AttributeArgumentSyntax argument) - { - return semanticModel.GenerateNameForArgument(argument); - } + protected override string GenerateNameForArgument(SemanticModel semanticModel, AttributeArgumentSyntax argument, CancellationToken cancellationToken) + => semanticModel.GenerateNameForArgument(argument, cancellationToken); protected override RefKind GetRefKind(ArgumentSyntax argument) { diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index 3d7d1a4c984f0..16fb81c27c9ac 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -34,7 +34,7 @@ public InvocationExpressionInfo(SemanticDocument document, State state) protected override ImmutableArray DetermineParameterNames(CancellationToken cancellationToken) { return this.Document.SemanticModel.GenerateParameterNames( - _invocationExpression.ArgumentList); + _invocationExpression.ArgumentList, cancellationToken); } protected override bool DetermineReturnsByRef(CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs index 8038b7882b1f9..76869a833a8f7 100644 --- a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs +++ b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs @@ -526,9 +526,9 @@ protected override bool TryGetArgumentList(ObjectCreationExpressionSyntax object } protected override IList GenerateParameterNames( - SemanticModel semanticModel, IList arguments) + SemanticModel semanticModel, IList arguments, CancellationToken cancellationToken) { - return semanticModel.GenerateParameterNames(arguments); + return semanticModel.GenerateParameterNames(arguments, reservedNames: null, cancellationToken: cancellationToken); } public override string GetRootNamespace(CompilationOptions options) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs index 55e0d1e65dac5..b495f04a52263 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs @@ -1,12 +1,10 @@ // 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; using System.Composition; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.InitializeParameter; -using Microsoft.CodeAnalysis.Semantics; namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter { diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 7676ce0266c8f..a8c795ba0dc4e 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -33,10 +33,8 @@ protected override string GetMakeAsyncVoidFunctionResource() return CSharpFeaturesResources.Make_method_async_remain_void; } - protected override bool IsMethodOrAnonymousFunction(SyntaxNode node) - { - return node.IsKind(SyntaxKind.MethodDeclaration) || node.IsAnyLambdaOrAnonymousMethod(); - } + protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) + => node.IsAsyncSupportingFunctionSyntax(); protected override SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, @@ -45,6 +43,7 @@ protected override SyntaxNode AddAsyncTokenAndFixReturnType( switch (node) { case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, taskType, taskOfTType, valueTaskOfTType); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, taskType, taskOfTType, valueTaskOfTType); case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); @@ -56,7 +55,25 @@ private SyntaxNode FixMethod( bool keepVoid, IMethodSymbol methodSymbol, MethodDeclarationSyntax method, INamedTypeSymbol taskType, INamedTypeSymbol taskOfTType, INamedTypeSymbol valueTaskOfTType) { - var newReturnType = method.ReturnType; + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, taskType, taskOfTType, valueTaskOfTType); + var newModifiers = AddAsyncModifierWithCorrectedTrivia(method.Modifiers, ref newReturnType); + return method.WithReturnType(newReturnType).WithModifiers(newModifiers); + } + + private SyntaxNode FixLocalFunction( + bool keepVoid, IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, + INamedTypeSymbol taskType, INamedTypeSymbol taskOfTType, INamedTypeSymbol valueTaskOfTType) + { + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, taskType, taskOfTType, valueTaskOfTType); + var newModifiers = AddAsyncModifierWithCorrectedTrivia(localFunction.Modifiers, ref newReturnType); + return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); + } + + private static TypeSyntax FixMethodReturnType( + bool keepVoid, IMethodSymbol methodSymbol, TypeSyntax returnType, + INamedTypeSymbol taskType, INamedTypeSymbol taskOfTType, INamedTypeSymbol valueTaskOfTType) + { + var newReturnType = returnType; if (methodSymbol.ReturnsVoid) { @@ -75,8 +92,18 @@ private SyntaxNode FixMethod( } } - var newModifiers = method.Modifiers.Add(s_asyncToken); - return method.WithReturnType(newReturnType).WithModifiers(newModifiers); + return newReturnType.WithTriviaFrom(returnType); + } + + private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) + { + if (modifiers.Any()) + return modifiers.Add(s_asyncToken); + + // Move the leading trivia from the return type to the new modifiers list. + SyntaxTokenList result = SyntaxFactory.TokenList(s_asyncToken.WithLeadingTrivia(newReturnType.GetLeadingTrivia())); + newReturnType = newReturnType.WithoutLeadingTrivia(); + return result; } private SyntaxNode FixParenthesizedLambda(ParenthesizedLambdaExpressionSyntax lambda) diff --git a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs index 96d8e9a609312..afed98fce2574 100644 --- a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs @@ -17,16 +17,15 @@ internal class CSharpMakeMethodSynchronousCodeFixProvider : AbstractMakeMethodSy public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(CS1998); - protected override bool IsMethodOrAnonymousFunction(SyntaxNode node) - { - return node.IsKind(SyntaxKind.MethodDeclaration) || node.IsAnyLambdaOrAnonymousMethod(); - } + protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) + => node.IsAsyncSupportingFunctionSyntax(); protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, ITypeSymbol taskType, ITypeSymbol taskOfTType) { switch (node) { case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, taskType, taskOfTType); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, taskType, taskOfTType); case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); @@ -36,47 +35,66 @@ protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol met private SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, ITypeSymbol taskType, ITypeSymbol taskOfTType) { - var newReturnType = method.ReturnType; + var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, taskType, taskOfTType); + var newModifiers = FixMethodModifiers(method.Modifiers, ref newReturnType); + return method.WithReturnType(newReturnType).WithModifiers(newModifiers); + } + + private SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, ITypeSymbol taskType, ITypeSymbol taskOfTType) + { + var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, taskType, taskOfTType); + var newModifiers = FixMethodModifiers(localFunction.Modifiers, ref newReturnType); + return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); + } + + private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnType, ITypeSymbol taskType, ITypeSymbol taskOfTType) + { + var newReturnType = returnType; // If the return type is Task, then make the new return type "T". // If it is Task, then make the new return type "void". if (methodSymbol.ReturnType.OriginalDefinition.Equals(taskType)) { - newReturnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)).WithTriviaFrom(method.ReturnType); + newReturnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)).WithTriviaFrom(returnType); } else if (methodSymbol.ReturnType.OriginalDefinition.Equals(taskOfTType)) { - newReturnType = methodSymbol.ReturnType.GetTypeArguments()[0].GenerateTypeSyntax().WithTriviaFrom(method.ReturnType); + newReturnType = methodSymbol.ReturnType.GetTypeArguments()[0].GenerateTypeSyntax().WithTriviaFrom(returnType); } - var asyncTokenIndex = method.Modifiers.IndexOf(SyntaxKind.AsyncKeyword); + return newReturnType; + } + + private static SyntaxTokenList FixMethodModifiers(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) + { + var asyncTokenIndex = modifiers.IndexOf(SyntaxKind.AsyncKeyword); SyntaxTokenList newModifiers; if (asyncTokenIndex == 0) { - // Have to move the trivia on teh async token appropriately. - var asyncLeadingTrivia = method.Modifiers[0].LeadingTrivia; + // Have to move the trivia on the async token appropriately. + var asyncLeadingTrivia = modifiers[0].LeadingTrivia; - if (method.Modifiers.Count > 1) + if (modifiers.Count > 1) { // Move the trivia to the next modifier; - newModifiers = method.Modifiers.Replace( - method.Modifiers[1], - method.Modifiers[1].WithPrependedLeadingTrivia(asyncLeadingTrivia)); + newModifiers = modifiers.Replace( + modifiers[1], + modifiers[1].WithPrependedLeadingTrivia(asyncLeadingTrivia)); newModifiers = newModifiers.RemoveAt(0); } else { // move it to the return type. - newModifiers = method.Modifiers.RemoveAt(0); + newModifiers = modifiers.RemoveAt(0); newReturnType = newReturnType.WithPrependedLeadingTrivia(asyncLeadingTrivia); } } else { - newModifiers = method.Modifiers.RemoveAt(asyncTokenIndex); + newModifiers = modifiers.RemoveAt(asyncTokenIndex); } - return method.WithReturnType(newReturnType).WithModifiers(newModifiers); + return newModifiers; } private SyntaxNode FixParenthesizedLambda(ParenthesizedLambdaExpressionSyntax lambda) diff --git a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs index 627811613da25..bed9a14231d66 100644 --- a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs @@ -25,9 +25,10 @@ internal class CSharpUpgradeProjectCodeFixProvider : AbstractUpgradeProjectCodeF private const string CS8107 = nameof(CS8107); // error CS8059: Feature is not available in C# 7.0. Please use language version X or greater. private const string CS8302 = nameof(CS8302); // error CS8302: Feature is not available in C# 7.1. Please use language version X or greater. private const string CS8306 = nameof(CS8306); // error CS8306: ... Please use language version 7.1 or greater to access a un-named element by its inferred name. + private const string CS9003 = nameof(CS9003); // error CS9003: An expression of type '{0}' cannot be handled by a pattern of type '{1}' in C# {2}. Please use language version {3} or greater. public override ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059, CS8107, CS8302, CS8306); + ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059, CS8107, CS8302, CS8306, CS9003); public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0; public override string UpgradeAllProjectsResource => CSharpFeaturesResources.Upgrade_all_csharp_projects_to_language_version_0; diff --git a/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs index 87560df22c47d..f7265a2c46e4b 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs @@ -107,7 +107,8 @@ private static async Task GetInstallDataAsync( CancellationToken cancellationToken) { var oldDocument = document; - reference.ReplaceNameNode(ref node, ref document, cancellationToken); + (node, document) = await reference.ReplaceNameNodeAsync( + node, document, cancellationToken).ConfigureAwait(false); var newDocument = await reference.provider.AddImportAsync( node, reference.SearchResult.NameParts, document, placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs b/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs index 07f8df2926288..ba59aac0fadb1 100644 --- a/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs +++ b/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs @@ -105,9 +105,8 @@ protected override async Task> ComputeOperation var reference = service.GetReference(resolvedPath, MetadataReferenceProperties.Assembly); // First add the "using/import" directive in the code. - var node = _node; - var document = _document; - _reference.ReplaceNameNode(ref node, ref document, cancellationToken); + (SyntaxNode node, Document document) = await _reference.ReplaceNameNodeAsync( + _node, _document, cancellationToken).ConfigureAwait(false); var newDocument = await _reference.provider.AddImportAsync( node, _reference.SearchResult.NameParts, document, _placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/AddImport/References/Reference.cs b/src/Features/Core/Portable/AddImport/References/Reference.cs index a36eb4646d36c..51f51030b7c03 100644 --- a/src/Features/Core/Portable/AddImport/References/Reference.cs +++ b/src/Features/Core/Portable/AddImport/References/Reference.cs @@ -92,12 +92,12 @@ public override int GetHashCode() return Hash.CombineValues(this.SearchResult.NameParts); } - protected void ReplaceNameNode( - ref SyntaxNode contextNode, ref Document document, CancellationToken cancellationToken) + protected async Task<(SyntaxNode, Document)> ReplaceNameNodeAsync( + SyntaxNode contextNode, Document document, CancellationToken cancellationToken) { if (!this.SearchResult.DesiredNameDiffersFromSourceName()) { - return; + return (contextNode, document); } var identifier = SearchResult.NameNode.GetFirstToken(); @@ -107,11 +107,15 @@ protected void ReplaceNameNode( var root = contextNode.SyntaxTree.GetRoot(cancellationToken); root = root.ReplaceToken(identifier, newIdentifier.WithAdditionalAnnotations(annotation)); - document = document.WithSyntaxRoot(root); - contextNode = root.GetAnnotatedTokens(annotation).First().Parent; + + var newDocument = document.WithSyntaxRoot(root); + var newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newContextNode = newRoot.GetAnnotatedTokens(annotation).First().Parent; + + return (newContextNode, newDocument); } public abstract Task CreateCodeActionAsync(Document document, SyntaxNode node, bool placeSystemNamespaceFirst, CancellationToken cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/AddImport/References/SymbolReference.cs b/src/Features/Core/Portable/AddImport/References/SymbolReference.cs index 4ad54cbb54f05..9b27495bbc966 100644 --- a/src/Features/Core/Portable/AddImport/References/SymbolReference.cs +++ b/src/Features/Core/Portable/AddImport/References/SymbolReference.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.FindSymbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes.AddImport @@ -61,22 +63,23 @@ private async Task GetOperationAsync( protected virtual Solution GetUpdatedSolution(Document newDocument) => newDocument.Project.Solution; - private Task UpdateDocumentAsync( + private async Task UpdateDocumentAsync( Document document, SyntaxNode contextNode, bool placeSystemNamespaceFirst, bool hasExistingImport, CancellationToken cancellationToken) { - ReplaceNameNode(ref contextNode, ref document, cancellationToken); - // Defer to the language to add the actual import/using. if (hasExistingImport) { - return Task.FromResult(document); + return document; } - return provider.AddImportAsync(contextNode, - this.SymbolResult.Symbol, document, - placeSystemNamespaceFirst, cancellationToken); + (var newContextNode, var newDocument) = await ReplaceNameNodeAsync( + contextNode, document, cancellationToken).ConfigureAwait(false); + + return await provider.AddImportAsync( + newContextNode, this.SymbolResult.Symbol, newDocument, + placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); } public override async Task CreateCodeActionAsync( diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs index 9945ac8a2dfdc..c3bce9a2bfa1d 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs @@ -84,7 +84,7 @@ await FindReferenceAssemblyTypeReferencesAsync( if (symbolSearchService != null && installerService != null && searchNugetPackages && - installerService.IsEnabled) + installerService.IsEnabled(_document.Project.Id)) { foreach (var packageSource in installerService.PackageSources) { diff --git a/src/Features/Core/Portable/AddPackage/AbstractAddPackageCodeFixProvider.cs b/src/Features/Core/Portable/AddPackage/AbstractAddPackageCodeFixProvider.cs index 26178cc3e638a..7d5f063e534cd 100644 --- a/src/Features/Core/Portable/AddPackage/AbstractAddPackageCodeFixProvider.cs +++ b/src/Features/Core/Portable/AddPackage/AbstractAddPackageCodeFixProvider.cs @@ -51,7 +51,7 @@ protected async Task> GetAddPackagesCodeActionsAsync( if (symbolSearchService != null && installerService != null && searchNugetPackages && - installerService.IsEnabled) + installerService.IsEnabled(document.Project.Id)) { foreach (var packageSource in installerService.PackageSources) { diff --git a/src/Features/Core/Portable/AddParameter/AbstractAddParameterCodeFixProvider.cs b/src/Features/Core/Portable/AddParameter/AbstractAddParameterCodeFixProvider.cs index 181e77085f8f8..cec5a750f6aed 100644 --- a/src/Features/Core/Portable/AddParameter/AbstractAddParameterCodeFixProvider.cs +++ b/src/Features/Core/Portable/AddParameter/AbstractAddParameterCodeFixProvider.cs @@ -185,7 +185,8 @@ private async Task FixAsync( var newMethodDeclaration = GetNewMethodDeclaration( method, argument, argumentList, generator, methodDeclaration, - semanticFacts, argumentName, expression, semanticModel, parameterType); + semanticFacts, argumentName, expression, semanticModel, + parameterType, cancellationToken); var root = methodDeclaration.SyntaxTree.GetRoot(cancellationToken); var newRoot = root.ReplaceNode(methodDeclaration, newMethodDeclaration); @@ -206,7 +207,8 @@ private static SyntaxNode GetNewMethodDeclaration( string argumentName, SyntaxNode expression, SemanticModel semanticModel, - ITypeSymbol parameterType) + ITypeSymbol parameterType, + CancellationToken cancellationToken) { if (!string.IsNullOrWhiteSpace(argumentName)) { @@ -222,7 +224,8 @@ private static SyntaxNode GetNewMethodDeclaration( } else { - var name = semanticFacts.GenerateNameForExpression(semanticModel, expression); + var name = semanticFacts.GenerateNameForExpression( + semanticModel, expression, capitalize: false, cancellationToken: cancellationToken); var uniqueName = NameGenerator.EnsureUniqueness(name, method.Parameters.Select(p => p.Name)); var newParameterSymbol = CodeGenerationSymbolFactory.CreateParameterSymbol( diff --git a/src/Features/Core/Portable/CodeFixes/NamingStyle/AbstractNamingStyleCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/NamingStyle/AbstractNamingStyleCodeFixProvider.cs index b5b902eeb1e5e..e0212f23dc80b 100644 --- a/src/Features/Core/Portable/CodeFixes/NamingStyle/AbstractNamingStyleCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/NamingStyle/AbstractNamingStyleCodeFixProvider.cs @@ -1,6 +1,7 @@ // 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; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -8,6 +9,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.NamingStyles; using Microsoft.CodeAnalysis.Rename; @@ -52,9 +54,12 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var solution = context.Document.Project.Solution; context.RegisterCodeFix( new FixNameCodeAction( + solution, + symbol, + fixedName, string.Format(FeaturesResources.Fix_Name_Violation_colon_0, fixedName), c => FixAsync(document, symbol, fixedName, c), - nameof(NamingStyleCodeFixProvider)), + equivalenceKey: nameof(NamingStyleCodeFixProvider)), diagnostic); } } @@ -68,12 +73,51 @@ await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } - private class FixNameCodeAction : CodeAction.SolutionChangeAction + private class FixNameCodeAction : CodeAction { - public FixNameCodeAction(string title, Func> createChangedSolution, string equivalenceKey) - : base(title, createChangedSolution, equivalenceKey) + private readonly Solution _startingSolution; + private readonly ISymbol _symbol; + private readonly string _newName; + private readonly string _title; + private readonly Func> _createChangedSolutionAsync; + private readonly string _equivalenceKey; + + public FixNameCodeAction( + Solution startingSolution, + ISymbol symbol, + string newName, + string title, + Func> createChangedSolutionAsync, + string equivalenceKey) { + _startingSolution = startingSolution; + _symbol = symbol; + _newName = newName; + _title = title; + _createChangedSolutionAsync = createChangedSolutionAsync; + _equivalenceKey = equivalenceKey; } + + protected override async Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) + { + return SpecializedCollections.SingletonEnumerable( + new ApplyChangesOperation(await _createChangedSolutionAsync(cancellationToken).ConfigureAwait(false))); + } + + protected override async Task> ComputeOperationsAsync(CancellationToken cancellationToken) + { + var factory =_startingSolution.Workspace.Services.GetService(); + var newSolution = await _createChangedSolutionAsync(cancellationToken).ConfigureAwait(false); + return new CodeActionOperation[] + { + new ApplyChangesOperation(newSolution), + factory.CreateSymbolRenamedOperation(_symbol, _newName, _startingSolution, newSolution) + }.AsEnumerable(); + } + + public override string Title => _title; + + public override string EquivalenceKey => _equivalenceKey; } } } \ No newline at end of file diff --git a/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs new file mode 100644 index 0000000000000..67e52d65bb1d8 --- /dev/null +++ b/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs @@ -0,0 +1,11 @@ +// 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 Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.CodeActions.WorkspaceServices +{ + internal interface ISymbolRenamedCodeActionOperationFactoryWorkspaceService : IWorkspaceService + { + CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution); + } +} diff --git a/src/Features/Core/Portable/Completion/FileSystemCompletionHelper.cs b/src/Features/Core/Portable/Completion/FileSystemCompletionHelper.cs new file mode 100644 index 0000000000000..d432201d14c20 --- /dev/null +++ b/src/Features/Core/Portable/Completion/FileSystemCompletionHelper.cs @@ -0,0 +1,252 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Completion +{ + internal class FileSystemCompletionHelper + { + private static readonly char[] s_windowsDirectorySeparator = { '\\' }; + + private readonly Glyph _folderGlyph; + private readonly Glyph _fileGlyph; + + // absolute paths + private readonly ImmutableArray _searchPaths; + private readonly string _baseDirectoryOpt; + + private readonly ImmutableArray _allowableExtensions; + private readonly CompletionItemRules _itemRules; + + public FileSystemCompletionHelper( + Glyph folderGlyph, + Glyph fileGlyph, + ImmutableArray searchPaths, + string baseDirectoryOpt, + ImmutableArray allowableExtensions, + CompletionItemRules itemRules) + { + Debug.Assert(searchPaths.All(path => PathUtilities.IsAbsolute(path))); + Debug.Assert(baseDirectoryOpt == null || PathUtilities.IsAbsolute(baseDirectoryOpt)); + + _searchPaths = searchPaths; + _baseDirectoryOpt = baseDirectoryOpt; + _allowableExtensions = allowableExtensions; + _folderGlyph = folderGlyph; + _fileGlyph = fileGlyph; + _itemRules = itemRules; + } + + // virtual for testing + protected virtual string[] GetLogicalDrives() + => IOUtilities.PerformIO(CorLightup.Desktop.GetLogicalDrives, Array.Empty()); + + // virtual for testing + protected virtual bool DirectoryExists(string fullPath) + { + Debug.Assert(PathUtilities.IsAbsolute(fullPath)); + return Directory.Exists(fullPath); + } + + // virtual for testing + protected virtual IEnumerable EnumerateDirectories(string fullDirectoryPath) + { + Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); + return IOUtilities.PerformIO(() => Directory.EnumerateDirectories(fullDirectoryPath), Array.Empty()); + } + + // virtual for testing + protected virtual IEnumerable EnumerateFiles(string fullDirectoryPath) + { + Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); + return IOUtilities.PerformIO(() => Directory.EnumerateFiles(fullDirectoryPath), Array.Empty()); + } + + // virtual for testing + protected virtual bool IsVisibleFileSystemEntry(string fullPath) + { + Debug.Assert(PathUtilities.IsAbsolute(fullPath)); + return IOUtilities.PerformIO(() => (File.GetAttributes(fullPath) & (FileAttributes.Hidden | FileAttributes.System)) == 0, false); + } + + private CompletionItem CreateNetworkRoot() + => CommonCompletionItem.Create( + "\\\\", + glyph: null, + description: "\\\\".ToSymbolDisplayParts(), + rules: _itemRules); + + private CompletionItem CreateUnixRoot() + => CommonCompletionItem.Create( + "/", + glyph: _folderGlyph, + description: "/".ToSymbolDisplayParts(), + rules: _itemRules); + + private CompletionItem CreateFileSystemEntryItem(string fullPath, bool isDirectory) + => CommonCompletionItem.Create( + PathUtilities.GetFileName(fullPath), + glyph: isDirectory ? _folderGlyph : _fileGlyph, + description: fullPath.ToSymbolDisplayParts(), + rules: _itemRules); + + private CompletionItem CreateLogicalDriveItem(string drive) + => CommonCompletionItem.Create( + drive, + glyph: _folderGlyph, + description: drive.ToSymbolDisplayParts(), + rules: _itemRules); + + public Task> GetItemsAsync(string directoryPath, CancellationToken cancellationToken) + { + return Task.Run(() => GetItems(directoryPath, cancellationToken), cancellationToken); + } + + // internal for testing + internal ImmutableArray GetItems(string directoryPath, CancellationToken cancellationToken) + { + if (!PathUtilities.IsUnixLikePlatform && directoryPath.Length == 1 && directoryPath[0] == '\\') + { + // The user has typed only "\". In this case, we want to add "\\" to the list. + return ImmutableArray.Create(CreateNetworkRoot()); + } + + var result = ArrayBuilder.GetInstance(); + + var pathKind = PathUtilities.GetPathKind(directoryPath); + switch (pathKind) + { + case PathKind.Empty: + // base directory + if (_baseDirectoryOpt != null) + { + result.AddRange(GetItemsInDirectory(_baseDirectoryOpt, cancellationToken)); + } + + // roots + if (PathUtilities.IsUnixLikePlatform) + { + result.AddRange(CreateUnixRoot()); + } + else + { + foreach (var drive in GetLogicalDrives()) + { + result.Add(CreateLogicalDriveItem(drive.TrimEnd(s_windowsDirectorySeparator))); + } + + result.Add(CreateNetworkRoot()); + } + + // entries on search paths + foreach (var searchPath in _searchPaths) + { + result.AddRange(GetItemsInDirectory(searchPath, cancellationToken)); + } + + break; + + case PathKind.Absolute: + case PathKind.RelativeToCurrentDirectory: + case PathKind.RelativeToCurrentParent: + case PathKind.RelativeToCurrentRoot: + var fullDirectoryPath = FileUtilities.ResolveRelativePath(directoryPath, basePath: null, baseDirectory: _baseDirectoryOpt); + if (fullDirectoryPath != null) + { + result.AddRange(GetItemsInDirectory(fullDirectoryPath, cancellationToken)); + } + else + { + // invalid path + result.Clear(); + } + + break; + + case PathKind.Relative: + + // base directory: + if (_baseDirectoryOpt != null) + { + result.AddRange(GetItemsInDirectory(PathUtilities.CombineAbsoluteAndRelativePaths(_baseDirectoryOpt, directoryPath), cancellationToken)); + } + + // search paths: + foreach (var searchPath in _searchPaths) + { + result.AddRange(GetItemsInDirectory(PathUtilities.CombineAbsoluteAndRelativePaths(searchPath, directoryPath), cancellationToken)); + } + + break; + + case PathKind.RelativeToDriveDirectory: + // Paths "C:dir" are not supported, but when the path doesn't include any directory, i.e. "C:", + // we return the drive itself. + if (directoryPath.Length == 2) + { + result.Add(CreateLogicalDriveItem(directoryPath)); + } + + break; + + default: + throw ExceptionUtilities.UnexpectedValue(pathKind); + } + + return result.ToImmutableAndFree(); + } + + private IEnumerable GetItemsInDirectory(string fullDirectoryPath, CancellationToken cancellationToken) + { + Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); + + cancellationToken.ThrowIfCancellationRequested(); + + if (!DirectoryExists(fullDirectoryPath)) + { + yield break; + } + + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var directory in EnumerateDirectories(fullDirectoryPath)) + { + if (IsVisibleFileSystemEntry(directory)) + { + yield return CreateFileSystemEntryItem(directory, isDirectory: true); + } + } + + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var file in EnumerateFiles(fullDirectoryPath)) + { + if (_allowableExtensions.Length != 0 && + !_allowableExtensions.Contains( + PathUtilities.GetExtension(file), + PathUtilities.IsUnixLikePlatform ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (IsVisibleFileSystemEntry(file)) + { + yield return CreateFileSystemEntryItem(file, isDirectory: false); + } + } + } + } +} diff --git a/src/Features/Core/Portable/DesignerAttributes/AbstractDesignerAttributeService.cs b/src/Features/Core/Portable/DesignerAttributes/AbstractDesignerAttributeService.cs index a2672a62b64ee..7259b2d7c1635 100644 --- a/src/Features/Core/Portable/DesignerAttributes/AbstractDesignerAttributeService.cs +++ b/src/Features/Core/Portable/DesignerAttributes/AbstractDesignerAttributeService.cs @@ -1,61 +1,22 @@ // 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.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Versions; namespace Microsoft.CodeAnalysis.DesignerAttributes { - internal struct DesignerAttributeResult - { - public string DesignerAttributeArgument; - public bool ContainsErrors; - public bool NotApplicable; - - public DesignerAttributeResult(string designerAttributeArgument, bool containsErrors, bool notApplicable) - { - DesignerAttributeArgument = designerAttributeArgument; - ContainsErrors = containsErrors; - NotApplicable = notApplicable; - } - } - internal abstract class AbstractDesignerAttributeService : IDesignerAttributeService { protected abstract bool ProcessOnlyFirstTypeDefined(); protected abstract IEnumerable GetAllTopLevelTypeDefined(SyntaxNode root); protected abstract bool HasAttributesOrBaseTypeOrIsPartial(SyntaxNode typeNode); - public async Task ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken) - { - var workspace = document.Project.Solution.Workspace; - - // same service run in both inproc and remote host, but remote host will not have RemoteHostClient service, - // so inproc one will always run - var client = await workspace.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false); - if (client != null && !document.IsOpen()) - { - // run designer attributes scanner on remote host - // we only run closed files to make open document to have better responsiveness. - // also we cache everything related to open files anyway, no saving by running - // them in remote host - return await ScanDesignerAttributesInRemoteHostAsync(client, document, cancellationToken).ConfigureAwait(false); - } - - return await ScanDesignerAttributesInCurrentProcessAsync(document, cancellationToken).ConfigureAwait(false); - } - - private async Task ScanDesignerAttributesInRemoteHostAsync(RemoteHostClient client, Document document, CancellationToken cancellationToken) - { - return await client.RunCodeAnalysisServiceOnRemoteHostAsync( - document.Project.Solution, nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync), - document.Id, cancellationToken).ConfigureAwait(false); - } - - private async Task ScanDesignerAttributesInCurrentProcessAsync(Document document, CancellationToken cancellationToken) + public async Task ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); @@ -83,7 +44,7 @@ private async Task ScanDesignerAttributesInCurrentProce { // The DesignerCategoryAttribute doesn't exist. either not applicable or // no idea on design attribute status, just leave things as it is. - return new DesignerAttributeResult(designerAttributeArgument, documentHasError, notApplicable: true); + return new DesignerAttributeDocumentData(document.FilePath, designerAttributeArgument, documentHasError, notApplicable: true); } } @@ -114,7 +75,7 @@ private async Task ScanDesignerAttributesInCurrentProce if (attribute != null && attribute.ConstructorArguments.Length == 1) { designerAttributeArgument = GetArgumentString(attribute.ConstructorArguments[0]); - return new DesignerAttributeResult(designerAttributeArgument, documentHasError, notApplicable: false); + return new DesignerAttributeDocumentData(document.FilePath, designerAttributeArgument, documentHasError, notApplicable: false); } } } @@ -126,7 +87,7 @@ private async Task ScanDesignerAttributesInCurrentProce } } - return new DesignerAttributeResult(designerAttributeArgument, documentHasError, notApplicable: false); + return new DesignerAttributeDocumentData(document.FilePath, designerAttributeArgument, documentHasError, notApplicable: false); } private static string GetArgumentString(TypedConstant argument) @@ -140,5 +101,49 @@ private static string GetArgumentString(TypedConstant argument) return ((string)argument.Value).Trim(); } + + internal static async Task> TryAnalyzeProjectInCurrentProcessAsync( + Project project, CancellationToken cancellationToken) + { + var projectVersion = await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false); + var semanticVersion = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + + // Get whatever data we've current persisted. + var designerAttributeData = await DesignerAttributeProjectData.ReadAsync( + project, cancellationToken).ConfigureAwait(false); + + // If we have no persisted data, or the persisted data is for a previous version of + // the project, then compute the results for the current project snapshot. + if (designerAttributeData == null || + !VersionStamp.CanReusePersistedVersion(semanticVersion, designerAttributeData.SemanticVersion)) + { + designerAttributeData = await ComputeAndPersistDesignerAttributeProjectDataAsync( + project, semanticVersion, cancellationToken).ConfigureAwait(false); + } + + return designerAttributeData.PathToDocumentData; + } + + private static async Task ComputeAndPersistDesignerAttributeProjectDataAsync( + Project project, VersionStamp semanticVersion, CancellationToken cancellationToken) + { + var service = project.LanguageServices.GetService(); + + var tasks = project.Documents.Select( + d => service.ScanDesignerAttributesAsync(d, cancellationToken)).ToArray(); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var task in tasks) + { + var result = await task.ConfigureAwait(false); + builder[result.FilePath] = result; + } + + var data = new DesignerAttributeProjectData(semanticVersion, builder.ToImmutable()); + await data.PersistAsync(project, cancellationToken).ConfigureAwait(false); + return data; + } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeDocumentData.cs b/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeDocumentData.cs new file mode 100644 index 0000000000000..7ab94603a35be --- /dev/null +++ b/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeDocumentData.cs @@ -0,0 +1,41 @@ +// 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; + +namespace Microsoft.CodeAnalysis.DesignerAttributes +{ + /// + /// Marshalling type to pass designer attribute data to/from the OOP process. + /// + internal struct DesignerAttributeDocumentData : IEquatable + { + public string FilePath; + public string DesignerAttributeArgument; + public bool ContainsErrors; + public bool NotApplicable; + + public DesignerAttributeDocumentData(string filePath, string designerAttributeArgument, bool containsErrors, bool notApplicable) + { + FilePath = filePath; + DesignerAttributeArgument = designerAttributeArgument; + ContainsErrors = containsErrors; + NotApplicable = notApplicable; + } + + public override bool Equals(object obj) + => Equals((DesignerAttributeDocumentData)obj); + + public bool Equals(DesignerAttributeDocumentData other) + { + return FilePath == other.FilePath && + DesignerAttributeArgument == other.DesignerAttributeArgument && + ContainsErrors == other.ContainsErrors && + NotApplicable == other.NotApplicable; + } + + // Currently no need for GetHashCode. If we end up using this as a key in a dictionary, + // feel free to add. + public override int GetHashCode() + => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeProjectData.cs b/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeProjectData.cs new file mode 100644 index 0000000000000..d82db11bd93c6 --- /dev/null +++ b/src/Features/Core/Portable/DesignerAttributes/DesignerAttributeProjectData.cs @@ -0,0 +1,108 @@ +// 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; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DesignerAttributes +{ + internal class DesignerAttributeProjectData + { + private const string StreamName = ""; + private const string FormatVersion = "3"; + + public readonly VersionStamp SemanticVersion; + public readonly ImmutableDictionary PathToDocumentData; + + public DesignerAttributeProjectData( + VersionStamp semanticVersion, ImmutableDictionary pathToDocumentData) + { + SemanticVersion = semanticVersion; + PathToDocumentData = pathToDocumentData; + } + + public static async Task ReadAsync( + Project project, CancellationToken cancellationToken) + { + try + { + var solution = project.Solution; + var storageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); + + using (var persistenceService = storageService.GetStorage(solution, checkBranchId: false)) + using (var stream = await persistenceService.ReadStreamAsync(project, StreamName, cancellationToken).ConfigureAwait(false)) + using (var reader = ObjectReader.TryGetReader(stream, cancellationToken)) + { + if (reader != null) + { + var version = reader.ReadString(); + if (version == FormatVersion) + { + var semanticVersion = VersionStamp.ReadFrom(reader); + + var resultCount = reader.ReadInt32(); + var builder = ImmutableDictionary.CreateBuilder(); + + for (var i = 0; i < resultCount; i++) + { + var filePath = reader.ReadString(); + var attribute = reader.ReadString(); + var containsErrors = reader.ReadBoolean(); + var notApplicable = reader.ReadBoolean(); + + builder[filePath] = new DesignerAttributeDocumentData(filePath, attribute, containsErrors, notApplicable); + } + + return new DesignerAttributeProjectData(semanticVersion, builder.ToImmutable()); + } + } + } + } + catch (Exception e) when (IOUtilities.IsNormalIOException(e)) + { + // Storage APIs can throw arbitrary exceptions. + } + + return null; + } + + public async Task PersistAsync(Project project, CancellationToken cancellationToken) + { + try + { + var solution = project.Solution; + var storageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); + + using (var storage = storageService.GetStorage(solution, checkBranchId: false)) + using (var stream = SerializableBytes.CreateWritableStream()) + using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) + { + writer.WriteString(FormatVersion); + this.SemanticVersion.WriteTo(writer); + + writer.WriteInt32(this.PathToDocumentData.Count); + + foreach (var kvp in this.PathToDocumentData) + { + var result = kvp.Value; + writer.WriteString(result.FilePath); + writer.WriteString(result.DesignerAttributeArgument); + writer.WriteBoolean(result.ContainsErrors); + writer.WriteBoolean(result.NotApplicable); + } + + stream.Position = 0; + await storage.WriteStreamAsync(project, StreamName, stream, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception e) when (IOUtilities.IsNormalIOException(e)) + { + // Storage APIs can throw arbitrary exceptions. + } + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/DesignerAttributes/IDesignerAttributeService.cs b/src/Features/Core/Portable/DesignerAttributes/IDesignerAttributeService.cs index aa76cc91a4316..4a9b923387293 100644 --- a/src/Features/Core/Portable/DesignerAttributes/IDesignerAttributeService.cs +++ b/src/Features/Core/Portable/DesignerAttributes/IDesignerAttributeService.cs @@ -8,6 +8,6 @@ namespace Microsoft.CodeAnalysis.DesignerAttributes { internal interface IDesignerAttributeService : ILanguageService { - Task ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken); + Task ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/DesignerAttributes/IRemoteDesignerAttributeService.cs b/src/Features/Core/Portable/DesignerAttributes/IRemoteDesignerAttributeService.cs index c202d46d8ab44..e17055ec67a63 100644 --- a/src/Features/Core/Portable/DesignerAttributes/IRemoteDesignerAttributeService.cs +++ b/src/Features/Core/Portable/DesignerAttributes/IRemoteDesignerAttributeService.cs @@ -6,6 +6,6 @@ namespace Microsoft.CodeAnalysis.DesignerAttributes { internal interface IRemoteDesignerAttributeService { - Task ScanDesignerAttributesAsync(DocumentId documentId); + Task ScanDesignerAttributesAsync(ProjectId projectId); } } diff --git a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index ec520c252c0fa..b1e97f4c5628c 100644 --- a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -101,7 +101,7 @@ public void ClearAnalyzerDiagnostics(ImmutableArray analyzer public void ClearAnalyzerDiagnostics(ProjectId projectId) { - foreach (var analyzer in _analyzerHostDiagnosticsMap.Keys) + foreach (var (analyzer, _) in _analyzerHostDiagnosticsMap) { ClearAnalyzerDiagnostics(analyzer, projectId); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs index fa0ccc66fcbc6..936df0f01ac57 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { @@ -95,7 +96,7 @@ public IEnumerable GetAnalyzers() yield return _compilerAnalyzer; } - foreach (var analyzer in _map.Keys) + foreach (var (analyzer, _) in _map) { yield return analyzer; } @@ -111,7 +112,7 @@ public IEnumerable GetStateSets() } // TODO: for now, this is static, but in future, we might consider making this a dynamic so that we process cheaper analyzer first. - foreach (var set in _map.Values) + foreach (var (_, set) in _map) { yield return set; } diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 2126145774738..d8f76241727ea 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -1696,7 +1696,7 @@ protected void AddRudeDeleteAroundActiveStatement(List diagn protected void ReportUnmatchedStatements( List diagnostics, Match match, - int syntaxKind, + int[] syntaxKinds, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement, Func areEquivalent, @@ -1704,8 +1704,8 @@ protected void ReportUnmatchedStatements( where TSyntaxNode : SyntaxNode { List oldNodes = null, newNodes = null; - GetAncestors(GetEncompassingAncestor(match.OldRoot), oldActiveStatement, syntaxKind, ref oldNodes); - GetAncestors(GetEncompassingAncestor(match.NewRoot), newActiveStatement, syntaxKind, ref newNodes); + GetAncestors(GetEncompassingAncestor(match.OldRoot), oldActiveStatement, syntaxKinds, ref oldNodes); + GetAncestors(GetEncompassingAncestor(match.NewRoot), newActiveStatement, syntaxKinds, ref newNodes); if (newNodes != null) { @@ -1836,11 +1836,11 @@ private static int IndexOfEquivalent(SyntaxNode newNode, List list) + private static void GetAncestors(SyntaxNode root, SyntaxNode node, int[] syntaxKinds, ref List list) { while (node != root) { - if (node.RawKind == syntaxKind) + if (syntaxKinds.Contains(node.RawKind)) { if (list == null) { diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 92bc7f231b944..cff5327a8c52d 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -112,6 +112,8 @@ + + @@ -122,6 +124,8 @@ + + diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.Editor.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.Editor.cs index d89174f79e706..243db44431116 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.Editor.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.Editor.cs @@ -188,7 +188,8 @@ private async Task GenerateDelegatingConstructorAsync( // delegating. var remainingParameterNames = _service.GenerateParameterNames( _document.SemanticModel, remainingArguments, - delegatedConstructor.Parameters.Select(p => p.Name).ToList()); + delegatedConstructor.Parameters.Select(p => p.Name).ToList(), + _cancellationToken); // Can't generate the constructor if the parameter names we're copying over forcibly // conflict with any names we generated. @@ -275,8 +276,8 @@ private ImmutableArray GetParameterNames( ImmutableArray arguments, ImmutableArray typeParametersNames) { return _state.AttributeArguments != null - ? _service.GenerateParameterNames(_document.SemanticModel, _state.AttributeArguments, typeParametersNames) - : _service.GenerateParameterNames(_document.SemanticModel, arguments, typeParametersNames); + ? _service.GenerateParameterNames(_document.SemanticModel, _state.AttributeArguments, typeParametersNames, _cancellationToken) + : _service.GenerateParameterNames(_document.SemanticModel, arguments, typeParametersNames, _cancellationToken); } private void GetParameters( @@ -364,8 +365,8 @@ private bool TryFindMatchingField( // use so we can assign to that. var newFieldName = NameGenerator.EnsureUniqueness( attributeArguments != null - ? _service.GenerateNameForArgument(_document.SemanticModel, attributeArguments.Value[index]) - : _service.GenerateNameForArgument(_document.SemanticModel, arguments[index]), + ? _service.GenerateNameForArgument(_document.SemanticModel, attributeArguments.Value[index], _cancellationToken) + : _service.GenerateNameForArgument(_document.SemanticModel, arguments[index], _cancellationToken), GetUnavailableMemberNames().Concat(parameterToNewFieldMap.Values)); if (isFixed) diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs index b25c3d152fbd9..7fc0642d8f9e1 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -30,12 +30,12 @@ protected AbstractGenerateConstructorService() protected abstract bool TryInitializeClassDeclarationGenerationState(SemanticDocument document, SyntaxNode classDeclaration, CancellationToken cancellationToken, out SyntaxToken token, out IMethodSymbol constructor, out INamedTypeSymbol typeToGenerateIn); protected abstract bool TryInitializeConstructorInitializerGeneration(SemanticDocument document, SyntaxNode constructorInitializer, CancellationToken cancellationToken, out SyntaxToken token, out ImmutableArray arguments, out INamedTypeSymbol typeToGenerateIn); protected abstract bool TryInitializeSimpleAttributeNameGenerationState(SemanticDocument document, SyntaxNode simpleName, CancellationToken cancellationToken, out SyntaxToken token, out ImmutableArray arguments, out ImmutableArray attributeArguments, out INamedTypeSymbol typeToGenerateIn); - protected abstract ImmutableArray GenerateParameterNames(SemanticModel semanticModel, IEnumerable arguments, IList reservedNames = null); - protected virtual ImmutableArray GenerateParameterNames(SemanticModel semanticModel, IEnumerable arguments, IList reservedNames = null) + protected abstract ImmutableArray GenerateParameterNames(SemanticModel semanticModel, IEnumerable arguments, IList reservedNames, CancellationToken cancellationToken); + protected virtual ImmutableArray GenerateParameterNames(SemanticModel semanticModel, IEnumerable arguments, IList reservedNames, CancellationToken cancellationToken) => default(ImmutableArray); - protected abstract string GenerateNameForArgument(SemanticModel semanticModel, TArgumentSyntax argument); - protected virtual string GenerateNameForArgument(SemanticModel semanticModel, TAttributeArgumentSyntax argument) { return null; } + protected abstract string GenerateNameForArgument(SemanticModel semanticModel, TArgumentSyntax argument, CancellationToken cancellationToken); + protected virtual string GenerateNameForArgument(SemanticModel semanticModel, TAttributeArgumentSyntax argument, CancellationToken cancellationToken) { return null; } protected abstract RefKind GetRefKind(TArgumentSyntax argument); protected abstract bool IsNamedArgument(TArgumentSyntax argument); protected abstract ITypeSymbol GetArgumentType(SemanticModel semanticModel, TArgumentSyntax argument, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs index 6f2e0020f7b6e..e480c4bcbca15 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs @@ -206,7 +206,7 @@ private void AddFieldDelegatingConstructor( var availableTypeParameters = _service.GetAvailableTypeParameters(_state, _document.SemanticModel, _intoNamespace, _cancellationToken); var parameterTypes = GetArgumentTypes(argumentList); - var parameterNames = _service.GenerateParameterNames(_document.SemanticModel, argumentList); + var parameterNames = _service.GenerateParameterNames(_document.SemanticModel, argumentList, _cancellationToken); var parameters = ArrayBuilder.GetInstance(); var parameterToExistingFieldMap = new Dictionary(); diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs index e279b2e7015db..9f6f7d978f983 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs @@ -39,7 +39,7 @@ protected AbstractGenerateTypeService() protected abstract string DefaultFileExtension { get; } protected abstract ImmutableArray GetTypeParameters(State state, SemanticModel semanticModel, CancellationToken cancellationToken); protected abstract Accessibility GetAccessibility(State state, SemanticModel semanticModel, bool intoNamespace, CancellationToken cancellationToken); - protected abstract IList GenerateParameterNames(SemanticModel semanticModel, IList arguments); + protected abstract IList GenerateParameterNames(SemanticModel semanticModel, IList arguments, CancellationToken cancellationToken); protected abstract INamedTypeSymbol DetermineTypeToGenerateIn(SemanticModel semanticModel, TSimpleNameSyntax simpleName, CancellationToken cancellationToken); protected abstract ITypeSymbol DetermineArgumentType(SemanticModel semanticModel, TArgumentSyntax argument, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs index 4071f72f96809..9e5b472aa6ef8 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -159,8 +159,8 @@ protected override Task GetChangedDocumentAsync(CancellationToken canc public Task GetUpdatedDocumentAsync(CancellationToken cancellationToken) { - var unimplementedMembers = Explicitly - ? State.UnimplementedExplicitMembers + var unimplementedMembers = Explicitly + ? State.UnimplementedExplicitMembers : State.UnimplementedMembers; return GetUpdatedDocumentAsync(Document, unimplementedMembers, State.ClassOrStructType, State.ClassOrStructDecl, cancellationToken); } @@ -441,14 +441,14 @@ private IMethodSymbol GetAddOrRemoveMethod(bool generateInvisibly, return generateInvisibly ? accessor : null; } - private SyntaxNode CreateThroughExpression(SyntaxGenerator factory) + private SyntaxNode CreateThroughExpression(SyntaxGenerator generator) { var through = ThroughMember.IsStatic - ? GenerateName(factory, State.ClassOrStructType.IsGenericType) - : factory.ThisExpression(); + ? GenerateName(generator, State.ClassOrStructType.IsGenericType) + : generator.ThisExpression(); - through = factory.MemberAccessExpression( - through, factory.IdentifierName(ThroughMember.Name)); + through = generator.MemberAccessExpression( + through, generator.IdentifierName(ThroughMember.Name)); var throughMemberType = ThroughMember.GetMemberType(); if ((State.InterfaceTypes != null) && (throughMemberType != null)) @@ -476,13 +476,32 @@ private SyntaxNode CreateThroughExpression(SyntaxGenerator factory) // uncommon case and optimize for the common one - in other words, we only apply the cast // in cases where we can unambiguously figure out which interface we are trying to implement. var interfaceBeingImplemented = State.InterfaceTypes.SingleOrDefault(); - if ((interfaceBeingImplemented != null) && (!throughMemberType.Equals(interfaceBeingImplemented))) + if (interfaceBeingImplemented != null) { - through = factory.CastExpression(interfaceBeingImplemented, - through.WithAdditionalAnnotations(Simplifier.Annotation)); - - var facts = this.Document.GetLanguageService(); - through = facts.Parenthesize(through); + if (!throughMemberType.Equals(interfaceBeingImplemented)) + { + through = generator.CastExpression(interfaceBeingImplemented, + through.WithAdditionalAnnotations(Simplifier.Annotation)); + } + else if (!ThroughMember.IsStatic && + ThroughMember is IPropertySymbol throughMemberProperty && + throughMemberProperty.ExplicitInterfaceImplementations.Any()) + { + // If we are implementing through an explicitly implemented property, we need to cast 'this' to + // the explicitly implemented interface type before calling the member, as in: + // ((IA)this).Prop.Member(); + // + var explicitlyImplementedProperty = throughMemberProperty.ExplicitInterfaceImplementations[0]; + + var explicitImplementationCast = generator.CastExpression( + explicitlyImplementedProperty.ContainingType, + generator.ThisExpression()); + + through = generator.MemberAccessExpression(explicitImplementationCast, + generator.IdentifierName(explicitlyImplementedProperty.Name)); + + through = through.WithAdditionalAnnotations(Simplifier.Annotation); + } } } diff --git a/src/Features/Core/Portable/IncrementalCaches/SymbolTreeInfoIncrementalAnalyzerProvider.cs b/src/Features/Core/Portable/IncrementalCaches/SymbolTreeInfoIncrementalAnalyzerProvider.cs index 673afccdea765..9f730f3180c94 100644 --- a/src/Features/Core/Portable/IncrementalCaches/SymbolTreeInfoIncrementalAnalyzerProvider.cs +++ b/src/Features/Core/Portable/IncrementalCaches/SymbolTreeInfoIncrementalAnalyzerProvider.cs @@ -38,22 +38,8 @@ namespace Microsoft.CodeAnalysis.IncrementalCaches [ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService))] internal class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider, IWorkspaceServiceFactory { - private struct ProjectInfo - { - public readonly VersionStamp VersionStamp; - public readonly SymbolTreeInfo SymbolTreeInfo; - - public ProjectInfo(VersionStamp versionStamp, SymbolTreeInfo info) - { - VersionStamp = versionStamp; - SymbolTreeInfo = info; - } - } - private struct MetadataInfo { - public readonly DateTime TimeStamp; - /// /// Note: can be null if were unable to create a SymbolTreeInfo /// (for example, if the metadata was bogus and we couldn't read it in). @@ -67,9 +53,8 @@ private struct MetadataInfo /// public readonly HashSet ReferencingProjects; - public MetadataInfo(DateTime timeStamp, SymbolTreeInfo info, HashSet referencingProjects) + public MetadataInfo(SymbolTreeInfo info, HashSet referencingProjects) { - TimeStamp = timeStamp; SymbolTreeInfo = info; ReferencingProjects = referencingProjects; } @@ -77,7 +62,7 @@ public MetadataInfo(DateTime timeStamp, SymbolTreeInfo info, HashSet // Concurrent dictionaries so they can be read from the SymbolTreeInfoCacheService while // they are being populated/updated by the IncrementalAnalyzer. - private readonly ConcurrentDictionary _projectToInfo = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _projectToInfo = new ConcurrentDictionary(); private readonly ConcurrentDictionary _metadataPathToInfo = new ConcurrentDictionary(); public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) @@ -102,37 +87,18 @@ private void OnCacheFlushRequested(object sender, EventArgs e) } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - return new SymbolTreeInfoCacheService(_projectToInfo, _metadataPathToInfo); - } + => new SymbolTreeInfoCacheService(_projectToInfo, _metadataPathToInfo); private static string GetReferenceKey(PortableExecutableReference reference) - { - return reference.FilePath ?? reference.Display; - } - - private static bool TryGetLastWriteTime(string path, out DateTime time) - { - var succeeded = false; - time = IOUtilities.PerformIO( - () => - { - var result = File.GetLastWriteTimeUtc(path); - succeeded = true; - return result; - }, - default(DateTime)); - - return succeeded; - } + => reference.FilePath ?? reference.Display; private class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService { - private readonly ConcurrentDictionary _projectToInfo; + private readonly ConcurrentDictionary _projectToInfo; private readonly ConcurrentDictionary _metadataPathToInfo; public SymbolTreeInfoCacheService( - ConcurrentDictionary projectToInfo, + ConcurrentDictionary projectToInfo, ConcurrentDictionary metadataPathToInfo) { _projectToInfo = projectToInfo; @@ -144,15 +110,15 @@ public async Task TryGetMetadataSymbolTreeInfoAsync( PortableExecutableReference reference, CancellationToken cancellationToken) { + var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, reference, cancellationToken); + var key = GetReferenceKey(reference); if (key != null) { - if (_metadataPathToInfo.TryGetValue(key, out var metadataInfo)) + if (_metadataPathToInfo.TryGetValue(key, out var metadataInfo) && + metadataInfo.SymbolTreeInfo.Checksum == checksum) { - if (TryGetLastWriteTime(key, out var writeTime) && writeTime == metadataInfo.TimeStamp) - { - return metadataInfo.SymbolTreeInfo; - } + return metadataInfo.SymbolTreeInfo; } } @@ -160,20 +126,17 @@ public async Task TryGetMetadataSymbolTreeInfoAsync( // Note: pass 'loadOnly' so we only attempt to load from disk, not to actually // try to create the metadata. var info = await SymbolTreeInfo.TryGetInfoForMetadataReferenceAsync( - solution, reference, loadOnly: true, cancellationToken: cancellationToken).ConfigureAwait(false); + solution, reference, checksum, loadOnly: true, cancellationToken: cancellationToken).ConfigureAwait(false); return info; } public async Task TryGetSourceSymbolTreeInfoAsync( Project project, CancellationToken cancellationToken) { - if (_projectToInfo.TryGetValue(project.Id, out var projectInfo)) + if (_projectToInfo.TryGetValue(project.Id, out var projectInfo) && + projectInfo.Checksum == await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false)) { - var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - if (version == projectInfo.VersionStamp) - { - return projectInfo.SymbolTreeInfo; - } + return projectInfo; } return null; @@ -182,11 +145,11 @@ public async Task TryGetSourceSymbolTreeInfoAsync( private class IncrementalAnalyzer : IncrementalAnalyzerBase { - private readonly ConcurrentDictionary _projectToInfo; + private readonly ConcurrentDictionary _projectToInfo; private readonly ConcurrentDictionary _metadataPathToInfo; public IncrementalAnalyzer( - ConcurrentDictionary projectToInfo, + ConcurrentDictionary projectToInfo, ConcurrentDictionary metadataPathToInfo) { _projectToInfo = projectToInfo; @@ -232,40 +195,40 @@ private async Task UpdateSymbolTreeInfoAsync(Project project, CancellationToken return; } - // Check the semantic version of this project. The semantic version will change - // if any of the source files changed, or if the project version itself changed. - // (The latter happens when something happens to the project like metadata - // changing on disk). - var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - if (!_projectToInfo.TryGetValue(project.Id, out var projectInfo) || projectInfo.VersionStamp != version) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + // Produce the indices for the source and metadata symbols in parallel. + var projectTask = UpdateSourceSymbolTreeInfoAsync(project, cancellationToken); + var referencesTask = UpdateReferencesAync(project, cancellationToken); - // Update the symbol tree infos for metadata and source in parallel. - var referencesTask = UpdateReferencesAync(project, compilation, cancellationToken); - var projectTask = SymbolTreeInfo.GetInfoForSourceAssemblyAsync(project, cancellationToken); + await Task.WhenAll(projectTask, referencesTask).ConfigureAwait(false); + } - await Task.WhenAll(referencesTask, projectTask).ConfigureAwait(false); + private async Task UpdateSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken) + { + var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false); + if (!_projectToInfo.TryGetValue(project.Id, out var projectInfo) || + projectInfo.Checksum != checksum) + { + projectInfo = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync( + project, checksum, cancellationToken).ConfigureAwait(false); // Mark that we're up to date with this project. Future calls with the same // semantic version can bail out immediately. - projectInfo = new ProjectInfo(version, await projectTask.ConfigureAwait(false)); _projectToInfo.AddOrUpdate(project.Id, projectInfo, (_1, _2) => projectInfo); } } - private Task UpdateReferencesAync(Project project, Compilation compilation, CancellationToken cancellationToken) + private Task UpdateReferencesAync(Project project, CancellationToken cancellationToken) { // Process all metadata references in parallel. var tasks = project.MetadataReferences.OfType() - .Select(r => UpdateReferenceAsync(project, compilation, r, cancellationToken)) + .Select(r => UpdateReferenceAsync(project, r, cancellationToken)) .ToArray(); return Task.WhenAll(tasks); } private async Task UpdateReferenceAsync( - Project project, Compilation compilation, PortableExecutableReference reference, CancellationToken cancellationToken) + Project project, PortableExecutableReference reference, CancellationToken cancellationToken) { var key = GetReferenceKey(reference); if (key == null) @@ -273,21 +236,17 @@ private async Task UpdateReferenceAsync( return; } - if (!TryGetLastWriteTime(key, out var lastWriteTime)) - { - // Couldn't get the write time. Just ignore this reference. - return; - } - - if (!_metadataPathToInfo.TryGetValue(key, out var metadataInfo) || metadataInfo.TimeStamp == lastWriteTime) + var checksum = SymbolTreeInfo.GetMetadataChecksum(project.Solution, reference, cancellationToken); + if (!_metadataPathToInfo.TryGetValue(key, out var metadataInfo) || + metadataInfo.SymbolTreeInfo.Checksum != checksum) { var info = await SymbolTreeInfo.TryGetInfoForMetadataReferenceAsync( - project.Solution, reference, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false); + project.Solution, reference, checksum, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false); // Note, getting the info may fail (for example, bogus metadata). That's ok. // We still want to cache that result so that don't try to continuously produce // this info over and over again. - metadataInfo = new MetadataInfo(lastWriteTime, info, metadataInfo.ReferencingProjects ?? new HashSet()); + metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); _metadataPathToInfo.AddOrUpdate(key, metadataInfo, (_1, _2) => metadataInfo); } @@ -318,4 +277,4 @@ private void RemoveMetadataReferences(ProjectId projectId) } } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index 1d5571df4144a..8f55faec5f59e 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -117,7 +117,10 @@ private bool IsIfNullCheck(IOperation statement, IParameterSymbol parameter) { if (statement is IIfStatement ifStatement) { - if (ifStatement.Condition is IBinaryOperatorExpression binaryOperator) + var condition = ifStatement.Condition; + condition = UnwrapImplicitConversion(condition); + + if (condition is IBinaryOperatorExpression binaryOperator) { // Look for code of the form "if (p == null)" or "if (null == p)" if (IsNullCheck(binaryOperator.LeftOperand, binaryOperator.RightOperand, parameter) || @@ -127,7 +130,7 @@ private bool IsIfNullCheck(IOperation statement, IParameterSymbol parameter) } } else if (parameter.Type.SpecialType == SpecialType.System_String && - IsStringCheck(ifStatement.Condition, parameter)) + IsStringCheck(condition, parameter)) { return true; } @@ -238,11 +241,13 @@ private static TStatementSyntax CreateStringCheckStatement( Compilation compilation, SyntaxGenerator generator, IParameterSymbol parameter, string methodName) { + var stringType = compilation.GetSpecialType(SpecialType.System_String); + // generates: if (string.IsXXX(s)) throw new ArgumentException("message", nameof(s)) return (TStatementSyntax)generator.IfStatement( generator.InvocationExpression( generator.MemberAccessExpression( - generator.TypeExpression(SpecialType.System_String), + generator.TypeExpression(stringType), generator.IdentifierName(methodName)), generator.Argument(generator.IdentifierName(parameter.Name))), SpecializedCollections.SingletonEnumerable( @@ -369,11 +374,25 @@ private async Task TryAddNullCheckToAssignmentAsync( return null; } + private static SyntaxNode GetTypeNode( + Compilation compilation, SyntaxGenerator generator, Type type) + { + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + if (typeSymbol == null) + { + return generator.QualifiedName( + generator.IdentifierName(nameof(System)), + generator.IdentifierName(type.Name)); + } + + return generator.TypeExpression(typeSymbol); + } + private static SyntaxNode CreateArgumentNullException( Compilation compilation, SyntaxGenerator generator, IParameterSymbol parameter) { return generator.ObjectCreationExpression( - compilation.GetTypeByMetadataName("System.ArgumentNullException"), + GetTypeNode(compilation, generator, typeof(ArgumentNullException)), generator.NameOfExpression(generator.IdentifierName(parameter.Name))); } @@ -383,7 +402,7 @@ private static SyntaxNode CreateArgumentException( // Note "message" is not localized. It is the name of the first parameter of // "ArgumentException" return generator.ObjectCreationExpression( - compilation.GetTypeByMetadataName("System.ArgumentException"), + GetTypeNode(compilation, generator, typeof(ArgumentException)), generator.LiteralExpression("message"), generator.NameOfExpression(generator.IdentifierName(parameter.Name))); } diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs index dddb1285028f8..0930cbf71d797 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs @@ -199,11 +199,12 @@ protected static SyntaxToken GenerateUniqueFieldName( bool isConstant, CancellationToken cancellationToken) { - var syntaxFacts = document.Project.LanguageServices.GetService(); - var semanticFacts = document.Project.LanguageServices.GetService(); + var syntaxFacts = document.Document.GetLanguageService(); + var semanticFacts = document.Document.GetLanguageService(); var semanticModel = document.SemanticModel; - var baseName = semanticFacts.GenerateNameForExpression(semanticModel, expression, isConstant); + var baseName = semanticFacts.GenerateNameForExpression( + semanticModel, expression, isConstant, cancellationToken); // A field can't conflict with any existing member names. var declaringType = semanticModel.GetEnclosingNamedType(expression.SpanStart, cancellationToken); @@ -226,7 +227,8 @@ protected static SyntaxToken GenerateUniqueLocalName( var semanticModel = document.SemanticModel; var existingSymbols = GetExistingSymbols(semanticModel, container, cancellationToken); - var baseName = semanticFacts.GenerateNameForExpression(semanticModel, expression, capitalize: isConstant); + var baseName = semanticFacts.GenerateNameForExpression( + semanticModel, expression, capitalize: isConstant, cancellationToken: cancellationToken); var reservedNames = semanticModel.LookupSymbols(expression.SpanStart) .Select(s => s.Name) .Concat(existingSymbols.Select(s => s.Name)); diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index fbe6a70c245fa..c4103f2154d66 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -15,7 +14,7 @@ namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous { internal abstract class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixProvider { - protected abstract bool IsMethodOrAnonymousFunction(SyntaxNode node); + protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); protected abstract SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, INamedTypeSymbol taskType, INamedTypeSymbol taskOfTType, INamedTypeSymbol valueTaskOfTType); @@ -51,8 +50,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // If it's a void returning method, offer to keep the void return type, or convert to // a Task return type. - if (symbol?.MethodKind == MethodKind.Ordinary && - symbol.ReturnsVoid) + bool isOrdinaryOrLocalFunction = symbol.IsOrdinaryMethodOrLocalFunction(); + if (isOrdinaryOrLocalFunction && symbol.ReturnsVoid) { context.RegisterCodeFix( new MyCodeAction(GetMakeAsyncTaskFunctionResource(), c => FixNodeAsync( @@ -100,8 +99,8 @@ private async Task FixNodeAsync( var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbolOpt = semanticModel.GetDeclaredSymbol(node) as IMethodSymbol; - if (methodSymbolOpt?.MethodKind == MethodKind.Ordinary && - !methodSymbolOpt.Name.EndsWith(AsyncSuffix)) + bool isOrdinaryOrLocalFunction = methodSymbolOpt.IsOrdinaryMethodOrLocalFunction(); + if (isOrdinaryOrLocalFunction && !methodSymbolOpt.Name.EndsWith(AsyncSuffix)) { return await RenameThenAddAsyncTokenAsync( keepVoid, document, node, methodSymbolOpt, cancellationToken).ConfigureAwait(false); @@ -116,7 +115,7 @@ private async Task FixNodeAsync( private SyntaxNode GetContainingFunction(Diagnostic diagnostic, CancellationToken cancellationToken) { var token = diagnostic.Location.FindToken(cancellationToken); - var node = token.GetAncestor(IsMethodOrAnonymousFunction); + var node = token.GetAncestor(IsAsyncSupportingFunctionSyntax); return node; } @@ -152,8 +151,7 @@ private async Task AddAsyncTokenAsync( var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var (taskType, taskOfTType, valueTaskOfTType) = GetTaskTypes(compilation); - var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, taskType, taskOfTType, valueTaskOfTType) - .WithAdditionalAnnotations(Formatter.Annotation); + var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, taskType, taskOfTType, valueTaskOfTType); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(node, newNode); diff --git a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs index 838dd461c1cc8..864434ac384af 100644 --- a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs @@ -22,7 +22,7 @@ internal abstract class AbstractMakeMethodSynchronousCodeFixProvider : CodeFixPr { public static readonly string EquivalenceKey = FeaturesResources.Make_method_synchronous; - protected abstract bool IsMethodOrAnonymousFunction(SyntaxNode node); + protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); protected abstract SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, ITypeSymbol taskType, ITypeSymbol taskOfTType); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -41,7 +41,7 @@ private async Task FixNodeAsync( Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var token = diagnostic.Location.FindToken(cancellationToken); - var node = token.GetAncestor(IsMethodOrAnonymousFunction); + var node = token.GetAncestor(IsAsyncSupportingFunctionSyntax); // See if we're on an actual method declaration (otherwise we're on a lambda declaration). // If we're on a method declaration, we'll get an IMethodSymbol back. In that case, check @@ -49,7 +49,8 @@ private async Task FixNodeAsync( var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbolOpt = semanticModel.GetDeclaredSymbol(node) as IMethodSymbol; - if (methodSymbolOpt?.MethodKind == MethodKind.Ordinary && + bool isOrdinaryOrLocalFunction = methodSymbolOpt.IsOrdinaryMethodOrLocalFunction(); + if (isOrdinaryOrLocalFunction && methodSymbolOpt.Name.Length > AsyncSuffix.Length && methodSymbolOpt.Name.EndsWith(AsyncSuffix)) { diff --git a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs index a73d7d438f8ac..56969eea12210 100644 --- a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SolutionCrawler { @@ -21,7 +22,7 @@ public AggregateIncrementalAnalyzer(Workspace workspace, IncrementalAnalyzerProv public async Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) { - foreach (var analyzer in this.Analyzers.Values) + foreach (var (_, analyzer) in this.Analyzers) { if (analyzer.IsValueCreated) { @@ -98,7 +99,7 @@ private bool TryGetAnalyzer(Project project, out IIncrementalAnalyzer analyzer) public void RemoveDocument(DocumentId documentId) { - foreach (var analyzer in this.Analyzers.Values) + foreach (var (_, analyzer) in this.Analyzers) { if (analyzer.IsValueCreated) { @@ -109,7 +110,7 @@ public void RemoveDocument(DocumentId documentId) public void RemoveProject(ProjectId projectId) { - foreach (var analyzer in this.Analyzers.Values) + foreach (var (_, analyzer) in this.Analyzers) { if (analyzer.IsValueCreated) { diff --git a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs index a1a7b0f1763d6..a1b35e4d19ac4 100644 --- a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs @@ -102,7 +102,7 @@ protected override async Task FixAllAsync( subEditor.ReplaceNode(statement, newStatement); foreach (var match in matches) { - subEditor.RemoveNode(match); + subEditor.RemoveNode(match, SyntaxRemoveOptions.KeepUnbalancedDirectives); } document = document.WithSyntaxRoot(subEditor.GetChangedRoot()); diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs index 9f6283f6cb76a..6054312f5878d 100644 --- a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs @@ -101,7 +101,7 @@ protected override async Task FixAllAsync( subEditor.ReplaceNode(statement, newStatement); foreach (var match in matches) { - subEditor.RemoveNode(match.Statement); + subEditor.RemoveNode(match.Statement, SyntaxRemoveOptions.KeepUnbalancedDirectives); } document = document.WithSyntaxRoot(subEditor.GetChangedRoot()); diff --git a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj index 71f6b35a7f052..352ccc1c9746a 100644 --- a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj +++ b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj @@ -10,6 +10,7 @@ Microsoft.CodeAnalysis.VisualBasic.Features netstandard1.3 portable-net45+win8;dotnet + $(NoWarn);40057 @@ -90,7 +91,7 @@ - + diff --git a/src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/VisualBasicRemoveUnusedVariableCodeFixProvider.vb similarity index 82% rename from src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.vb rename to src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/VisualBasicRemoveUnusedVariableCodeFixProvider.vb index 88222e5e9e66a..41103342ca8fe 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/RemoveUnusedVariableCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/RemoveUnusedVariable/VisualBasicRemoveUnusedVariableCodeFixProvider.vb @@ -8,8 +8,9 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.RemoveUnusedVariable - - Friend Class RemoveUnusedVariableCodeFixProvider + + + Friend Class VisualBasicRemoveUnusedVariableCodeFixProvider Inherits AbstractRemoveUnusedVariableCodeFixProvider(Of LocalDeclarationStatementSyntax, ModifiedIdentifierSyntax, VariableDeclaratorSyntax) @@ -22,4 +23,4 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.RemoveUnusedVariable End Get End Property End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 4b2b269fd7316..9e2c5e681ddf0 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -3155,19 +3155,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ' ' Unlike exception regions matching where we use LCS, we allow reordering of the statements. - ReportUnmatchedStatements(Of SyncLockBlockSyntax)(diagnostics, match, SyntaxKind.SyncLockBlock, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(Of SyncLockBlockSyntax)(diagnostics, match, New Integer() {SyntaxKind.SyncLockBlock}, oldActiveStatement, newActiveStatement, areEquivalent:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(n1.SyncLockStatement.Expression, n2.SyncLockStatement.Expression), areSimilar:=Nothing) - ReportUnmatchedStatements(Of WithBlockSyntax)(diagnostics, match, SyntaxKind.WithBlock, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(Of WithBlockSyntax)(diagnostics, match, New Integer() {SyntaxKind.WithBlock}, oldActiveStatement, newActiveStatement, areEquivalent:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(n1.WithStatement.Expression, n2.WithStatement.Expression), areSimilar:=Nothing) - ReportUnmatchedStatements(Of UsingBlockSyntax)(diagnostics, match, SyntaxKind.UsingBlock, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(Of UsingBlockSyntax)(diagnostics, match, New Integer() {SyntaxKind.UsingBlock}, oldActiveStatement, newActiveStatement, areEquivalent:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(n1.UsingStatement.Expression, n2.UsingStatement.Expression), areSimilar:=Nothing) - ReportUnmatchedStatements(Of ForOrForEachBlockSyntax)(diagnostics, match, SyntaxKind.ForEachBlock, oldActiveStatement, newActiveStatement, + ReportUnmatchedStatements(Of ForOrForEachBlockSyntax)(diagnostics, match, New Integer() {SyntaxKind.ForEachBlock}, oldActiveStatement, newActiveStatement, areEquivalent:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(n1.ForOrForEachStatement, n2.ForOrForEachStatement), areSimilar:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(DirectCast(n1.ForOrForEachStatement, ForEachStatementSyntax).ControlVariable, DirectCast(n2.ForOrForEachStatement, ForEachStatementSyntax).ControlVariable)) diff --git a/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb b/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb index 1edfbe56575c2..d38bade0703ae 100644 --- a/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb +++ b/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb @@ -14,15 +14,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor Partial Friend Class VisualBasicGenerateConstructorService Inherits AbstractGenerateConstructorService(Of VisualBasicGenerateConstructorService, ArgumentSyntax, AttributeSyntax) - Protected Overrides Function GenerateNameForArgument(semanticModel As SemanticModel, argument As ArgumentSyntax) As String - Return semanticModel.GenerateNameForArgument(argument) + Protected Overrides Function GenerateNameForArgument(semanticModel As SemanticModel, argument As ArgumentSyntax, cancellationToken As CancellationToken) As String + Return semanticModel.GenerateNameForArgument(argument, cancellationToken) End Function Protected Overrides Function GenerateParameterNames( semanticModel As SemanticModel, arguments As IEnumerable(Of ArgumentSyntax), - Optional reservedNames As IList(Of String) = Nothing) As ImmutableArray(Of ParameterName) - Return semanticModel.GenerateParameterNames(arguments?.ToList(), reservedNames) + reservedNames As IList(Of String), + cancellationToken As CancellationToken) As ImmutableArray(Of ParameterName) + Return semanticModel.GenerateParameterNames(arguments?.ToList(), reservedNames, cancellationToken) End Function Protected Overrides Function GetArgumentType( diff --git a/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateParameterizedMemberService.vb b/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateParameterizedMemberService.vb index f23061159b515..68bb9b3441e88 100644 --- a/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateParameterizedMemberService.vb +++ b/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateParameterizedMemberService.vb @@ -28,7 +28,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateMember.GenerateMethod Protected Overrides Function DetermineParameterNames(cancellationToken As CancellationToken) As ImmutableArray(Of ParameterName) Dim typeParametersNames = Me.DetermineTypeParameters(cancellationToken).SelectAsArray(Function(t) t.Name) Return Me.Document.SemanticModel.GenerateParameterNames( - Me.InvocationExpression.ArgumentList, reservedNames:=typeParametersNames) + Me.InvocationExpression.ArgumentList, reservedNames:=typeParametersNames, cancellationToken:=cancellationToken) End Function Protected Overrides Function DetermineReturnsByRef(cancellationToken As CancellationToken) As Boolean diff --git a/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb b/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb index cd539d54b5b88..efc2d344a51c9 100644 --- a/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb +++ b/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb @@ -31,8 +31,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateType End Get End Property - Protected Overrides Function GenerateParameterNames(semanticModel As SemanticModel, arguments As IList(Of ArgumentSyntax)) As IList(Of ParameterName) - Return semanticModel.GenerateParameterNames(arguments) + Protected Overrides Function GenerateParameterNames(semanticModel As SemanticModel, arguments As IList(Of ArgumentSyntax), cancellationToken As CancellationToken) As IList(Of ParameterName) + Return semanticModel.GenerateParameterNames(arguments, reservedNames:=Nothing, cancellationToken:=cancellationToken) End Function Protected Overrides Function GetLeftSideOfDot(simpleName As SimpleNameSyntax) As ExpressionSyntax diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index cdb6ebfa661c4..09a30912a7f1b 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -35,13 +35,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return VBFeaturesResources.Make_Async_Sub End Function - Protected Overrides Function IsMethodOrAnonymousFunction(node As SyntaxNode) As Boolean - Return node.IsKind(SyntaxKind.FunctionBlock) OrElse - node.IsKind(SyntaxKind.SubBlock) OrElse - node.IsKind(SyntaxKind.MultiLineFunctionLambdaExpression) OrElse - node.IsKind(SyntaxKind.MultiLineSubLambdaExpression) OrElse - node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) OrElse - node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) + Protected Overrides Function IsAsyncSupportingFunctionSyntax(node As SyntaxNode) As Boolean + Return node.IsAsyncSupportedFunctionSyntax() End Function Protected Overrides Function AddAsyncTokenAndFixReturnType( diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb index 3a0e037f50700..e3ad9a10b79a3 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb @@ -21,13 +21,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous End Get End Property - Protected Overrides Function IsMethodOrAnonymousFunction(node As SyntaxNode) As Boolean - Return node.IsKind(SyntaxKind.FunctionBlock) OrElse - node.IsKind(SyntaxKind.SubBlock) OrElse - node.IsKind(SyntaxKind.MultiLineFunctionLambdaExpression) OrElse - node.IsKind(SyntaxKind.MultiLineSubLambdaExpression) OrElse - node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) OrElse - node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) + Protected Overrides Function IsAsyncSupportingFunctionSyntax(node As SyntaxNode) As Boolean + Return node.IsAsyncSupportedFunctionSyntax() End Function Protected Overrides Function RemoveAsyncTokenAndFixReturnType(methodSymbolOpt As IMethodSymbol, node As SyntaxNode, taskType As ITypeSymbol, taskOfTType As ITypeSymbol) As SyntaxNode diff --git a/src/Interactive/CsiCore/CsiCore.csproj b/src/Interactive/CsiCore/CsiCore.csproj index 404bc41443a34..f5ee930ed0275 100644 --- a/src/Interactive/CsiCore/CsiCore.csproj +++ b/src/Interactive/CsiCore/CsiCore.csproj @@ -5,6 +5,7 @@ x64 x64 + x64 {D1B051A4-F2A1-4E97-9747-C41D13E475FD} Exe CSharpInteractive diff --git a/src/Interactive/EditorFeatures/CSharp/CSharpInteractiveEditorFeatures.csproj b/src/Interactive/EditorFeatures/CSharp/CSharpInteractiveEditorFeatures.csproj index 4ff1e84c567f4..18f19259486ec 100644 --- a/src/Interactive/EditorFeatures/CSharp/CSharpInteractiveEditorFeatures.csproj +++ b/src/Interactive/EditorFeatures/CSharp/CSharpInteractiveEditorFeatures.csproj @@ -82,6 +82,7 @@ + diff --git a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/DirectiveCompletionProviderUtilities.cs b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/DirectiveCompletionProviderUtilities.cs new file mode 100644 index 0000000000000..d4d1e71874a98 --- /dev/null +++ b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/DirectiveCompletionProviderUtilities.cs @@ -0,0 +1,32 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem +{ + internal static class DirectiveCompletionProviderUtilities + { + internal static bool TryGetStringLiteralToken(SyntaxTree tree, int position, SyntaxKind directiveKind, out SyntaxToken stringLiteral, CancellationToken cancellationToken) + { + if (tree.IsEntirelyWithinStringLiteral(position, cancellationToken)) + { + var token = tree.GetRoot(cancellationToken).FindToken(position, findInsideTrivia: true); + if (token.Kind() == SyntaxKind.EndOfDirectiveToken || token.Kind() == SyntaxKind.EndOfFileToken) + { + token = token.GetPreviousToken(includeSkipped: true, includeDirectives: true); + } + + if (token.Kind() == SyntaxKind.StringLiteralToken && token.Parent.Kind() == directiveKind) + { + stringLiteral = token; + return true; + } + } + + stringLiteral = default(SyntaxToken); + return false; + } + } +} diff --git a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs index 8c3c2e7c59a0a..fc97127aee53a 100644 --- a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs +++ b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs @@ -1,13 +1,8 @@ // 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.Collections.Immutable; -using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editor.Completion.FileSystem; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text.Editor; @@ -18,110 +13,9 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem // regular C# contexts. We will need to remove this and implement a new "CSharp Script" Content type // in order to fix #load completion in .csx files (https://github.com/dotnet/roslyn/issues/5325). [TextViewRole(PredefinedInteractiveTextViewRoles.InteractiveTextViewRole)] - internal partial class LoadDirectiveCompletionProvider : CommonCompletionProvider + internal sealed class LoadDirectiveCompletionProvider : AbstractLoadDirectiveCompletionProvider { - private const string NetworkPath = "\\\\"; - private static readonly Regex s_directiveRegex = new Regex(@"#load\s+(""[^""]*""?)", RegexOptions.Compiled); - - private static readonly ImmutableArray s_commitRules = - ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, '"', '\\', ',')); - - private static readonly CompletionItemRules s_rules = CompletionItemRules.Create(commitCharacterRules: s_commitRules); - - public override async Task ProvideCompletionsAsync(CompletionContext context) - { - var text = await context.Document.GetTextAsync(context.CancellationToken).ConfigureAwait(false); - var items = GetItems(text, context.Document, context.Position, context.Trigger, context.CancellationToken); - context.AddItems(items); - } - - internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options) - { - return PathCompletionUtilities.IsTriggerCharacter(text, characterPosition); - } - - private string GetPathThroughLastSlash(SourceText text, int position, Group quotedPathGroup) - { - return PathCompletionUtilities.GetPathThroughLastSlash( - quotedPath: quotedPathGroup.Value, - quotedPathStart: GetQuotedPathStart(text, position, quotedPathGroup), - position: position); - } - - private TextSpan GetTextChangeSpan(SourceText text, int position, Group quotedPathGroup) - { - return PathCompletionUtilities.GetTextChangeSpan( - quotedPath: quotedPathGroup.Value, - quotedPathStart: GetQuotedPathStart(text, position, quotedPathGroup), - position: position); - } - - private static int GetQuotedPathStart(SourceText text, int position, Group quotedPathGroup) - { - return text.Lines.GetLineFromPosition(position).Start + quotedPathGroup.Index; - } - - private ImmutableArray GetItems(SourceText text, Document document, int position, CompletionTrigger triggerInfo, CancellationToken cancellationToken) - { - var line = text.Lines.GetLineFromPosition(position); - var lineText = text.ToString(TextSpan.FromBounds(line.Start, position)); - var match = s_directiveRegex.Match(lineText); - if (!match.Success) - { - return ImmutableArray.Empty; - } - - var quotedPathGroup = match.Groups[1]; - var quotedPath = quotedPathGroup.Value; - var endsWithQuote = PathCompletionUtilities.EndsWithQuote(quotedPath); - if (endsWithQuote && (position >= line.Start + match.Length)) - { - return ImmutableArray.Empty; - } - - var buffer = text.Container.GetTextBuffer(); - var snapshot = text.FindCorrespondingEditorTextSnapshot(); - if (snapshot == null) - { - return ImmutableArray.Empty; - } - - var fileSystem = CurrentWorkingDirectoryDiscoveryService.GetService(snapshot); - - // TODO: https://github.com/dotnet/roslyn/issues/5263 - // Avoid dependency on a specific resolver. - // The search paths should be provided by specialized workspaces: - // - InteractiveWorkspace for interactive window - // - ScriptWorkspace for loose .csx files (we don't have such workspace today) - var searchPaths = (document.Project.CompilationOptions.SourceReferenceResolver as SourceFileResolver)?.SearchPaths ?? ImmutableArray.Empty; - - var helper = new FileSystemCompletionHelper( - this, - GetTextChangeSpan(text, position, quotedPathGroup), - fileSystem, - Glyph.OpenFolder, - Glyph.CSharpFile, - searchPaths: searchPaths, - allowableExtensions: new[] { ".csx" }, - itemRules: s_rules); - - var pathThroughLastSlash = this.GetPathThroughLastSlash(text, position, quotedPathGroup); - - return helper.GetItems(pathThroughLastSlash, documentPath: null); - } - - protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) - { - // When we commit "\\" when the user types \ we have to adjust for the fact that the - // controller will automatically append \ after we commit. Because of that, we don't - // want to actually commit "\\" as we'll end up with "\\\". So instead we just commit - // "\" and know that controller will append "\" and give us "\\". - if (selectedItem.DisplayText == NetworkPath && ch == '\\') - { - return Task.FromResult(new TextChange(selectedItem.Span, "\\")); - } - - return base.GetTextChangeAsync(selectedItem, ch, cancellationToken); - } + protected override bool TryGetStringLiteralToken(SyntaxTree tree, int position, out SyntaxToken stringLiteral, CancellationToken cancellationToken) + => DirectiveCompletionProviderUtilities.TryGetStringLiteralToken(tree, position, SyntaxKind.LoadDirectiveTrivia, out stringLiteral, cancellationToken); } } \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/ReferenceDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/ReferenceDirectiveCompletionProvider.cs index eabb0e5b7aadf..0404cf37b04c9 100644 --- a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/ReferenceDirectiveCompletionProvider.cs +++ b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/ReferenceDirectiveCompletionProvider.cs @@ -2,7 +2,6 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.Editor.Completion.FileSystem; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text.Editor; @@ -15,28 +14,9 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.Completion.FileSystem // regular C# contexts. We will need to remove this and implement a new "CSharp Script" Content type // in order to fix #r completion in .csx files (https://github.com/dotnet/roslyn/issues/5325). [TextViewRole(PredefinedInteractiveTextViewRoles.InteractiveTextViewRole)] - internal partial class ReferenceDirectiveCompletionProvider : AbstractReferenceDirectiveCompletionProvider + internal sealed class ReferenceDirectiveCompletionProvider : AbstractReferenceDirectiveCompletionProvider { - protected override bool TryGetStringLiteralToken(SyntaxTree tree, int position, out SyntaxToken stringLiteral, CancellationToken cancellationToken) - { - if (tree.IsEntirelyWithinStringLiteral(position, cancellationToken)) - { - var token = tree.GetRoot(cancellationToken).FindToken(position, findInsideTrivia: true); - if (token.Kind() == SyntaxKind.EndOfDirectiveToken || token.Kind() == SyntaxKind.EndOfFileToken) - { - token = token.GetPreviousToken(includeSkipped: true, includeDirectives: true); - } - - if (token.Kind() == SyntaxKind.StringLiteralToken && - token.Parent.Kind() == SyntaxKind.ReferenceDirectiveTrivia) - { - stringLiteral = token; - return true; - } - } - - stringLiteral = default(SyntaxToken); - return false; - } + protected override bool TryGetStringLiteralToken(SyntaxTree tree, int position, out SyntaxToken stringLiteral, CancellationToken cancellationToken) => + DirectiveCompletionProviderUtilities.TryGetStringLiteralToken(tree, position, SyntaxKind.ReferenceDirectiveTrivia, out stringLiteral, cancellationToken); } } diff --git a/src/Interactive/EditorFeatures/Core/Completion/AbstractDirectivePathCompletionProvider.cs b/src/Interactive/EditorFeatures/Core/Completion/AbstractDirectivePathCompletionProvider.cs new file mode 100644 index 0000000000000..486597b9b39c9 --- /dev/null +++ b/src/Interactive/EditorFeatures/Core/Completion/AbstractDirectivePathCompletionProvider.cs @@ -0,0 +1,143 @@ +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Completion.FileSystem +{ + internal abstract class AbstractDirectivePathCompletionProvider : CompletionProvider + { + protected static bool IsDirectorySeparator(char ch) => + ch == '/' || (ch == '\\' && !PathUtilities.IsUnixLikePlatform); + + protected abstract bool TryGetStringLiteralToken(SyntaxTree tree, int position, out SyntaxToken stringLiteral, CancellationToken cancellationToken); + + public sealed override async Task ProvideCompletionsAsync(CompletionContext context) + { + var document = context.Document; + var position = context.Position; + var cancellationToken = context.CancellationToken; + + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + if (!TryGetStringLiteralToken(tree, position, out var stringLiteral, cancellationToken)) + { + return; + } + + var literalValue = stringLiteral.ToString(); + + context.CompletionListSpan = GetTextChangeSpan( + quotedPath: literalValue, + quotedPathStart: stringLiteral.SpanStart, + position: position); + + var pathThroughLastSlash = GetPathThroughLastSlash( + quotedPath: literalValue, + quotedPathStart: stringLiteral.SpanStart, + position: position); + + await ProvideCompletionsAsync(context, pathThroughLastSlash).ConfigureAwait(false); + } + + public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options) + { + return true; + } + + private static string GetPathThroughLastSlash(string quotedPath, int quotedPathStart, int position) + { + Contract.ThrowIfTrue(quotedPath[0] != '"'); + + const int QuoteLength = 1; + + var positionInQuotedPath = position - quotedPathStart; + var path = quotedPath.Substring(QuoteLength, positionInQuotedPath - QuoteLength).Trim(); + var afterLastSlashIndex = AfterLastSlashIndex(path, path.Length); + + // We want the portion up to, and including the last slash if there is one. That way if + // the user pops up completion in the middle of a path (i.e. "C:\Win") then we'll + // consider the path to be "C:\" and we will show appropriate completions. + return afterLastSlashIndex >= 0 ? path.Substring(0, afterLastSlashIndex) : path; + } + + private static TextSpan GetTextChangeSpan(string quotedPath, int quotedPathStart, int position) + { + // We want the text change to be from after the last slash to the end of the quoted + // path. If there is no last slash, then we want it from right after the start quote + // character. + var positionInQuotedPath = position - quotedPathStart; + + // Where we want to start tracking is right after the slash (if we have one), or else + // right after the string starts. + var afterLastSlashIndex = AfterLastSlashIndex(quotedPath, positionInQuotedPath); + var afterFirstQuote = 1; + + var startIndex = Math.Max(afterLastSlashIndex, afterFirstQuote); + var endIndex = quotedPath.Length; + + // If the string ends with a quote, the we do not want to consume that. + if (EndsWithQuote(quotedPath)) + { + endIndex--; + } + + return TextSpan.FromBounds(startIndex + quotedPathStart, endIndex + quotedPathStart); + } + + private static bool EndsWithQuote(string quotedPath) + { + return quotedPath.Length >= 2 && quotedPath[quotedPath.Length - 1] == '"'; + } + + /// + /// Returns the index right after the last slash that precedes 'position'. If there is no + /// slash in the string, -1 is returned. + /// + private static int AfterLastSlashIndex(string text, int position) + { + // Position might be out of bounds of the string (if the string is unterminated. Make + // sure it's within bounds. + position = Math.Min(position, text.Length - 1); + + int index; + if ((index = text.LastIndexOf('/', position)) >= 0 || + !PathUtilities.IsUnixLikePlatform && (index = text.LastIndexOf('\\', position)) >= 0) + { + return index + 1; + } + + return -1; + } + + protected abstract Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash); + + internal static string GetBaseDirectory(SourceText text, Document document) + { + string result; + if (document.Project.IsSubmission) + { + var buffer = text.Container.GetTextBuffer(); + var snapshot = text.FindCorrespondingEditorTextSnapshot(); + if (snapshot == null) + { + return null; + } + + result = CurrentWorkingDirectoryDiscoveryService.GetService(snapshot).WorkingDirectory; + } + else + { + result = PathUtilities.GetDirectoryName(document.FilePath); + } + + return PathUtilities.IsAbsolute(result) ? result : null; + } + } +} diff --git a/src/Interactive/EditorFeatures/Core/Completion/AbstractLoadDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/Core/Completion/AbstractLoadDirectiveCompletionProvider.cs new file mode 100644 index 0000000000000..8119c107c6dd0 --- /dev/null +++ b/src/Interactive/EditorFeatures/Core/Completion/AbstractLoadDirectiveCompletionProvider.cs @@ -0,0 +1,61 @@ +// 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.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Completion.FileSystem +{ + internal abstract class AbstractLoadDirectiveCompletionProvider : AbstractDirectivePathCompletionProvider + { + private static readonly CompletionItemRules s_rules = CompletionItemRules.Create( + filterCharacterRules: ImmutableArray.Empty, + commitCharacterRules: ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, GetCommitCharacters())), + enterKeyRule: EnterKeyRule.Never, + selectionBehavior: CompletionItemSelectionBehavior.HardSelection); + + private static ImmutableArray GetCommitCharacters() + { + var builder = ArrayBuilder.GetInstance(); + builder.Add('"'); + if (PathUtilities.IsUnixLikePlatform) + { + builder.Add('/'); + } + else + { + builder.Add('/'); + builder.Add('\\'); + } + + return builder.ToImmutableAndFree(); + } + + private FileSystemCompletionHelper GetFileSystemCompletionHelper(SourceText text, Document document) + { + // TODO: https://github.com/dotnet/roslyn/issues/5263 + // Avoid dependency on a specific resolver. + // The search paths should be provided by specialized workspaces: + // - InteractiveWorkspace for interactive window + // - MiscFilesWorkspace for loose .csx files + var searchPaths = (document.Project.CompilationOptions.SourceReferenceResolver as SourceFileResolver)?.SearchPaths ?? ImmutableArray.Empty; + + return new FileSystemCompletionHelper( + Glyph.OpenFolder, + Glyph.CSharpFile, + searchPaths: searchPaths, + baseDirectoryOpt: GetBaseDirectory(text, document), + allowableExtensions: ImmutableArray.Create(".csx"), + itemRules: s_rules); + } + + protected override async Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash) + { + var text = await context.Document.GetTextAsync(context.CancellationToken).ConfigureAwait(false); + var helper = GetFileSystemCompletionHelper(text, context.Document); + context.AddItems(await helper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); + } + } +} \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs index d8962aea2ec56..68b05b05d7bf0 100644 --- a/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs +++ b/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs @@ -1,124 +1,97 @@ // 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; using System.Collections.Immutable; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Completion.FileSystem { - internal abstract partial class AbstractReferenceDirectiveCompletionProvider : CommonCompletionProvider + internal abstract class AbstractReferenceDirectiveCompletionProvider : AbstractDirectivePathCompletionProvider { - protected abstract bool TryGetStringLiteralToken(SyntaxTree tree, int position, out SyntaxToken stringLiteral, CancellationToken cancellationToken); - - internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options) - { - return PathCompletionUtilities.IsTriggerCharacter(text, characterPosition); - } - - private TextSpan GetTextChangeSpan(SyntaxToken stringLiteral, int position) - { - return PathCompletionUtilities.GetTextChangeSpan( - quotedPath: stringLiteral.ToString(), - quotedPathStart: stringLiteral.SpanStart, - position: position); - } - - private static ICurrentWorkingDirectoryDiscoveryService GetFileSystemDiscoveryService(ITextSnapshot textSnapshot) - { - return CurrentWorkingDirectoryDiscoveryService.GetService(textSnapshot); - } - - private static readonly ImmutableArray s_commitRules = - ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, '"', '\\', ',')); - - private static readonly ImmutableArray s_filterRules = ImmutableArray.Empty; - private static readonly CompletionItemRules s_rules = CompletionItemRules.Create( - filterCharacterRules: s_filterRules, commitCharacterRules: s_commitRules, enterKeyRule: EnterKeyRule.Never); + filterCharacterRules: ImmutableArray.Empty, + commitCharacterRules: ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, GetCommitCharacters())), + enterKeyRule: EnterKeyRule.Never, + selectionBehavior: CompletionItemSelectionBehavior.HardSelection); - public override async Task ProvideCompletionsAsync(CompletionContext context) + private static readonly char[] s_pathIndicators = new char[] { '/', '\\', ':' }; + + private static ImmutableArray GetCommitCharacters() { - var document = context.Document; - var position = context.Position; - var cancellationToken = context.CancellationToken; + var builder = ArrayBuilder.GetInstance(); - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + builder.Add('"'); - // first try to get the #r string literal token. If we couldn't, then we're not in a #r - // reference directive and we immediately bail. - SyntaxToken stringLiteral; - if (!TryGetStringLiteralToken(tree, position, out stringLiteral, cancellationToken)) + if (PathUtilities.IsUnixLikePlatform) { - return; + builder.Add('/'); + } + else + { + builder.Add('/'); + builder.Add('\\'); } - var textChangeSpan = this.GetTextChangeSpan(stringLiteral, position); - - var gacHelper = new GlobalAssemblyCacheCompletionHelper(this, textChangeSpan, itemRules: s_rules); - var text = await document.GetTextAsync(context.CancellationToken).ConfigureAwait(false); - var snapshot = text.FindCorrespondingEditorTextSnapshot(); - if (snapshot == null) + if (GacFileResolver.IsAvailable) { - // Passing null to GetFileSystemDiscoveryService raises an exception. - // Instead, return here since there is no longer snapshot for this document. - return; + builder.Add(','); } - var referenceResolver = document.Project.CompilationOptions.MetadataReferenceResolver; + return builder.ToImmutableAndFree(); + } + private FileSystemCompletionHelper GetFileSystemCompletionHelper(SourceText text, Document document) + { + var referenceResolver = document.Project.CompilationOptions.MetadataReferenceResolver; + // TODO: https://github.com/dotnet/roslyn/issues/5263 // Avoid dependency on a specific resolvers. // The search paths should be provided by specialized workspaces: // - InteractiveWorkspace for interactive window - // - ScriptWorkspace for loose .csx files (we don't have such workspace today) + // - MiscFilesWorkspace for loose .csx files ImmutableArray searchPaths; - RuntimeMetadataReferenceResolver rtResolver; - WorkspaceMetadataFileReferenceResolver workspaceResolver; - - if ((rtResolver = referenceResolver as RuntimeMetadataReferenceResolver) != null) + if (referenceResolver is RuntimeMetadataReferenceResolver rtResolver) { searchPaths = rtResolver.PathResolver.SearchPaths; } - else if ((workspaceResolver = referenceResolver as WorkspaceMetadataFileReferenceResolver) != null) + else if (referenceResolver is WorkspaceMetadataFileReferenceResolver workspaceResolver) { searchPaths = workspaceResolver.PathResolver.SearchPaths; } else { - return; + searchPaths = ImmutableArray.Empty; } - var fileSystemHelper = new FileSystemCompletionHelper( - this, textChangeSpan, - GetFileSystemDiscoveryService(snapshot), + return new FileSystemCompletionHelper( Glyph.OpenFolder, Glyph.Assembly, searchPaths: searchPaths, - allowableExtensions: new[] { ".dll", ".exe" }, - exclude: path => path.Contains(","), + baseDirectoryOpt: GetBaseDirectory(text, document), + allowableExtensions: ImmutableArray.Create(".dll", ".exe"), itemRules: s_rules); - - var pathThroughLastSlash = GetPathThroughLastSlash(stringLiteral, position); - - var documentPath = document.Project.IsSubmission ? null : document.FilePath; - context.AddItems(gacHelper.GetItems(pathThroughLastSlash, documentPath)); - context.AddItems(fileSystemHelper.GetItems(pathThroughLastSlash, documentPath)); } - private static string GetPathThroughLastSlash(SyntaxToken stringLiteral, int position) + protected override async Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash) { - return PathCompletionUtilities.GetPathThroughLastSlash( - quotedPath: stringLiteral.ToString(), - quotedPathStart: stringLiteral.SpanStart, - position: position); + if (GacFileResolver.IsAvailable && pathThroughLastSlash.IndexOfAny(s_pathIndicators) < 0) + { + var gacHelper = new GlobalAssemblyCacheCompletionHelper(s_rules); + context.AddItems(await gacHelper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); + } + + if (pathThroughLastSlash.IndexOf(',') < 0) + { + var text = await context.Document.GetTextAsync(context.CancellationToken).ConfigureAwait(false); + var fileSystemHelper = GetFileSystemCompletionHelper(text, context.Document); + context.AddItems(await fileSystemHelper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); + } } } } diff --git a/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs b/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs index df640acd029e8..e96469b0a4e55 100644 --- a/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs +++ b/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs @@ -2,15 +2,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.Editor.Completion.FileSystem { @@ -18,55 +18,49 @@ internal sealed class GlobalAssemblyCacheCompletionHelper { private static readonly Lazy> s_lazyAssemblySimpleNames = new Lazy>(() => GlobalAssemblyCache.Instance.GetAssemblySimpleNames().ToList()); - private readonly CompletionProvider _completionProvider; - private readonly TextSpan _textChangeSpan; + private readonly CompletionItemRules _itemRules; - public GlobalAssemblyCacheCompletionHelper( - CompletionProvider completionProvider, - TextSpan textChangeSpan, - CompletionItemRules itemRules = null) + public GlobalAssemblyCacheCompletionHelper(CompletionItemRules itemRules) { - _completionProvider = completionProvider; - _textChangeSpan = textChangeSpan; + Debug.Assert(itemRules != null); _itemRules = itemRules; } - public IEnumerable GetItems(string pathSoFar, string documentPath) + public Task> GetItemsAsync(string directoryPath, CancellationToken cancellationToken) { - var containsSlash = pathSoFar.Contains(@"/") || pathSoFar.Contains(@"\"); - if (containsSlash) - { - return SpecializedCollections.EmptyEnumerable(); - } - - return GetCompletionsWorker(pathSoFar).ToList(); + return Task.Run(() => GetItems(directoryPath, cancellationToken)); } - private IEnumerable GetCompletionsWorker(string pathSoFar) + // internal for testing + internal ImmutableArray GetItems(string directoryPath, CancellationToken cancellationToken) { - var comma = pathSoFar.IndexOf(','); + var result = ArrayBuilder.GetInstance(); + + var comma = directoryPath.IndexOf(','); if (comma >= 0) { - var path = pathSoFar.Substring(0, comma); - return from identity in GetAssemblyIdentities(path) - let text = identity.GetDisplayName() - select CommonCompletionItem.Create(text, glyph: Glyph.Assembly, rules: _itemRules); + var partialName = directoryPath.Substring(0, comma); + foreach (var identity in GetAssemblyIdentities(partialName)) + { + result.Add(CommonCompletionItem.Create(identity.GetDisplayName(), glyph: Glyph.Assembly, rules: _itemRules)); + } } else { - return from displayName in s_lazyAssemblySimpleNames.Value - select CommonCompletionItem.Create( - displayName, - description: GlobalAssemblyCache.Instance.ResolvePartialName(displayName).GetDisplayName().ToSymbolDisplayParts(), - glyph: Glyph.Assembly, - rules: _itemRules); + foreach (var displayName in s_lazyAssemblySimpleNames.Value) + { + cancellationToken.ThrowIfCancellationRequested(); + result.Add(CommonCompletionItem.Create(displayName, glyph: Glyph.Assembly, rules: _itemRules)); + } } + + return result.ToImmutableAndFree(); } - private IEnumerable GetAssemblyIdentities(string pathSoFar) + private IEnumerable GetAssemblyIdentities(string partialName) { - return IOUtilities.PerformIO(() => GlobalAssemblyCache.Instance.GetAssemblyIdentities(pathSoFar), + return IOUtilities.PerformIO(() => GlobalAssemblyCache.Instance.GetAssemblyIdentities(partialName), SpecializedCollections.EmptyEnumerable()); } } diff --git a/src/Interactive/EditorFeatures/Core/InteractiveEditorFeatures.csproj b/src/Interactive/EditorFeatures/Core/InteractiveEditorFeatures.csproj index 943854a9aabc3..6c17643941924 100644 --- a/src/Interactive/EditorFeatures/Core/InteractiveEditorFeatures.csproj +++ b/src/Interactive/EditorFeatures/Core/InteractiveEditorFeatures.csproj @@ -105,9 +105,11 @@ + + diff --git a/src/Interactive/EditorFeatures/VisualBasic/BasicInteractiveEditorFeatures.vbproj b/src/Interactive/EditorFeatures/VisualBasic/BasicInteractiveEditorFeatures.vbproj index d5df24ac7354a..1b687dd026684 100644 --- a/src/Interactive/EditorFeatures/VisualBasic/BasicInteractiveEditorFeatures.vbproj +++ b/src/Interactive/EditorFeatures/VisualBasic/BasicInteractiveEditorFeatures.vbproj @@ -67,6 +67,8 @@ + + diff --git a/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/DirectiveCompletionProviderUtilities.vb b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/DirectiveCompletionProviderUtilities.vb new file mode 100644 index 0000000000000..95a4f395edd53 --- /dev/null +++ b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/DirectiveCompletionProviderUtilities.vb @@ -0,0 +1,23 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Threading +Imports Microsoft.CodeAnalysis + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion.CompletionProviders + Friend Module DirectiveCompletionProviderUtilities + Friend Function TryGetStringLiteralToken(tree As SyntaxTree, position As Integer, directiveKind As SyntaxKind, ByRef stringLiteral As SyntaxToken, cancellationToken As CancellationToken) As Boolean + If tree.IsEntirelyWithinStringLiteral(position, cancellationToken) Then + Dim token = tree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives:=True, includeDocumentationComments:=True) + + ' Verifies that the string literal under caret is the path token. + If token.IsKind(SyntaxKind.StringLiteralToken) AndAlso token.Parent.IsKind(directiveKind) Then + stringLiteral = token + Return True + End If + End If + + stringLiteral = Nothing + Return False + End Function + End Module +End Namespace \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/LoadDirectiveCompletionProvider.vb b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/LoadDirectiveCompletionProvider.vb new file mode 100644 index 0000000000000..1f62f3c6fd525 --- /dev/null +++ b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/LoadDirectiveCompletionProvider.vb @@ -0,0 +1,19 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editor.Completion.FileSystem +Imports Microsoft.VisualStudio.InteractiveWindow +Imports Microsoft.VisualStudio.Text.Editor + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion.CompletionProviders +#If TODO Then ' VB doesn't have LoadDirectiveTrivia defined yet + + + Friend NotInheritable Class LoadDirectiveCompletionProvider : Inherits AbstractReferenceDirectiveCompletionProvider + Protected Overrides Function TryGetStringLiteralToken(tree As SyntaxTree, position As Integer, ByRef stringLiteral As SyntaxToken, cancellationToken As CancellationToken) As Boolean + Return DirectiveCompletionProviderUtilities.TryGetStringLiteralToken(tree, position, SyntaxKind.LoadDirectiveTrivia, stringLiteral, cancellationToken) + End Function + End Class +#End If +End Namespace \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/ReferenceDirectiveCompletionProvider.vb b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/ReferenceDirectiveCompletionProvider.vb index 0c9abadae68bb..a6f82b2d32165 100644 --- a/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/ReferenceDirectiveCompletionProvider.vb +++ b/src/Interactive/EditorFeatures/VisualBasic/Interactive/FileSystem/ReferenceDirectiveCompletionProvider.vb @@ -12,18 +12,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion.CompletionProvide Friend Class ReferenceDirectiveCompletionProvider : Inherits AbstractReferenceDirectiveCompletionProvider Protected Overrides Function TryGetStringLiteralToken(tree As SyntaxTree, position As Integer, ByRef stringLiteral As SyntaxToken, cancellationToken As CancellationToken) As Boolean - If tree.IsEntirelyWithinStringLiteral(position, cancellationToken) Then - Dim token = tree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives:=True, includeDocumentationComments:=True) - - ' Verifies that the string literal under caret is the path token. - If token.IsKind(SyntaxKind.StringLiteralToken) AndAlso token.Parent.IsKind(SyntaxKind.ReferenceDirectiveTrivia) Then - stringLiteral = token - Return True - End If - End If - - stringLiteral = Nothing - Return False + Return DirectiveCompletionProviderUtilities.TryGetStringLiteralToken(tree, position, SyntaxKind.ReferenceDirectiveTrivia, stringLiteral, cancellationToken) End Function End Class diff --git a/src/Interactive/VbiCore/VbiCore.vbproj b/src/Interactive/VbiCore/VbiCore.vbproj index 6bfe4b00dbc83..24da7ac09ae0b 100644 --- a/src/Interactive/VbiCore/VbiCore.vbproj +++ b/src/Interactive/VbiCore/VbiCore.vbproj @@ -5,6 +5,7 @@ x64 x64 + x64 {1EEFB4B6-A6CC-4869-AF05-A43C8B82A8FD} Exe Sub Main @@ -15,6 +16,7 @@ win7-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;osx.10.12-x64 portable-net452 true + $(NoWarn);40057 diff --git a/src/Samples/VisualBasic/Analyzers/BasicAnalyzers/BasicAnalyzers/BasicAnalyzers.vbproj b/src/Samples/VisualBasic/Analyzers/BasicAnalyzers/BasicAnalyzers/BasicAnalyzers.vbproj index 333f9e4b9d5a7..a55ea7f1a4f32 100644 --- a/src/Samples/VisualBasic/Analyzers/BasicAnalyzers/BasicAnalyzers/BasicAnalyzers.vbproj +++ b/src/Samples/VisualBasic/Analyzers/BasicAnalyzers/BasicAnalyzers/BasicAnalyzers.vbproj @@ -14,7 +14,7 @@ portable-net45+win8;dotnet false Off - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 + $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036;40057 diff --git a/src/Scripting/VisualBasic/BasicScripting.vbproj b/src/Scripting/VisualBasic/BasicScripting.vbproj index 2cf39f64ce46d..51cda3aa86ee9 100644 --- a/src/Scripting/VisualBasic/BasicScripting.vbproj +++ b/src/Scripting/VisualBasic/BasicScripting.vbproj @@ -10,6 +10,7 @@ Microsoft.CodeAnalysis.VisualBasic.Scripting netstandard1.3 portable-net452 + $(NoWarn);40057 diff --git a/src/Scripting/VisualBasicTest/BasicScriptingTest.vbproj b/src/Scripting/VisualBasicTest/BasicScriptingTest.vbproj index 18f8eefa40256..de98e9101948a 100644 --- a/src/Scripting/VisualBasicTest/BasicScriptingTest.vbproj +++ b/src/Scripting/VisualBasicTest/BasicScriptingTest.vbproj @@ -12,6 +12,7 @@ netstandard1.3 portable-net452 UnitTestDesktop + $(NoWarn);40057 diff --git a/src/Scripting/VisualBasicTest/InteractiveSessionTests.vb b/src/Scripting/VisualBasicTest/InteractiveSessionTests.vb index 67e2bb07062b2..ffafd0c97a97d 100644 --- a/src/Scripting/VisualBasicTest/InteractiveSessionTests.vb +++ b/src/Scripting/VisualBasicTest/InteractiveSessionTests.vb @@ -19,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.UnitTests ContinueWith("Dim y As Integer = 2"). ContinueWith("?x + y") - Assert.Equal(3, s.ReturnValue) + Assert.Equal(3, CType(s.ReturnValue, Integer)) End Function @@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.UnitTests Dim source = " ?1 _ " - Assert.Equal(1, VisualBasicScript.EvaluateAsync(source).Result) + Assert.Equal(1, CType(VisualBasicScript.EvaluateAsync(source).Result, Integer)) End Sub @@ -35,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.UnitTests Dim source = " ?1 " - Assert.Equal(1, VisualBasicScript.EvaluateAsync(source).Result) + Assert.Equal(1, CType(VisualBasicScript.EvaluateAsync(source).Result, Integer)) End Sub @@ -61,7 +61,7 @@ End If ?x + 1 " - Assert.Equal(6, VisualBasicScript.EvaluateAsync(source).Result) + Assert.Equal(6, CType(VisualBasicScript.EvaluateAsync(source).Result, Integer)) End Sub @@ -81,7 +81,7 @@ Dim d = New With { Key .F = 777 } & "" "" & (a.GetType() Is b.GetType()).ToString() _ & "" "" & (b.GetType() is d.GetType()).ToString() ") - Assert.Equal("True False True", script.EvaluateAsync().Result) + Assert.Equal("True False True", CType(script.EvaluateAsync().Result, String)) End Sub @@ -107,7 +107,7 @@ Dim d = Function () As Integer & "" "" & (b.GetType() is d.GetType()).ToString() ") - Assert.Equal("True False True", script.EvaluateAsync().Result) + Assert.Equal("True False True", CType(script.EvaluateAsync().Result, String)) End Sub End Class diff --git a/src/Scripting/VisualBasicTest/ScriptTests.vb b/src/Scripting/VisualBasicTest/ScriptTests.vb index e64264a006757..135694269fbaf 100644 --- a/src/Scripting/VisualBasicTest/ScriptTests.vb +++ b/src/Scripting/VisualBasicTest/ScriptTests.vb @@ -22,14 +22,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.UnitTests Public Sub TestEvalScript() - Dim value = VisualBasicScript.EvaluateAsync("? 1 + 2", s_defaultOptions) - Assert.Equal(3, value.Result) + Dim value = CType(VisualBasicScript.EvaluateAsync("? 1 + 2", s_defaultOptions).Result, Integer) + Assert.Equal(3, value) End Sub Public Async Function TestRunScript() As Task Dim state = Await VisualBasicScript.RunAsync("? 1 + 2", s_defaultOptions) - Assert.Equal(3, state.ReturnValue) + Assert.Equal(3, CType(state.ReturnValue, Integer)) End Function @@ -37,13 +37,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.UnitTests Dim script = VisualBasicScript.Create("? 1 + 2", s_defaultOptions) Dim state = Await script.RunAsync() Assert.Same(script, state.Script) - Assert.Equal(3, state.ReturnValue) + Assert.Equal(3, CType(state.ReturnValue, Integer)) End Function Public Async Function TestRunScriptWithSpecifiedReturnType() As Task Dim state = Await VisualBasicScript.RunAsync("? 1 + 2", s_defaultOptions) - Assert.Equal(3, state.ReturnValue) + Assert.Equal(3, CType(state.ReturnValue, Integer)) End Function diff --git a/src/Setup/DevDivInsertionFiles/BuildDevDivInsertionFiles.vb b/src/Setup/DevDivInsertionFiles/BuildDevDivInsertionFiles.vb index c15b2fa407b69..92489632208fb 100644 --- a/src/Setup/DevDivInsertionFiles/BuildDevDivInsertionFiles.vb +++ b/src/Setup/DevDivInsertionFiles/BuildDevDivInsertionFiles.vb @@ -406,7 +406,6 @@ Public Class BuildDevDivInsertionFiles ' And now copy over all our core compiler binaries and related files ' Build tools setup authoring depends on these files being inserted. For Each fileName In CompilerFiles - Dim dependency As DependencyInfo = Nothing If Not dependencies.TryGetValue(fileName, dependency) Then AddXmlDocumentationFile(filesToInsert, fileName) @@ -414,12 +413,14 @@ Public Class BuildDevDivInsertionFiles End If Next - ' Add just the compiler files to a separate compiler nuspec - ' (with the Immutable collections and System.Reflection.Metadata, which - ' are normally inserted separately) - Dim allCompilerFiles = CompilerFiles.Concat({ - "System.Collections.Immutable.dll", "System.Reflection.Metadata.dll"}) - GenerateRoslynCompilerNuSpec(allCompilerFiles) + ' VS.Tools.Roslyn CoreXT package needs to contain all dependencies. + Dim vsToolsetFiles = CompilerFiles.Concat({ + "System.Collections.Immutable.dll", + "System.Reflection.Metadata.dll", + "Microsoft.DiaSymReader.Native.amd64.dll", + "Microsoft.DiaSymReader.Native.x86.dll"}) + + GenerateVSToolsRoslynCoreXTNuspec(vsToolsetFiles) ' Copy over the files in the NetFX20 subdirectory (identical, except for references and Authenticode signing). ' These are for msvsmon, whose setup authoring is done by the debugger. @@ -558,7 +559,7 @@ Public Class BuildDevDivInsertionFiles Dim targetObj = DirectCast(DirectCast(DirectCast(items, JObject).Property("targets")?.Value, JObject).Property(targetFx)?.Value, JObject) If targetObj Is Nothing Then - Throw New InvalidDataException($"Expected platform not found in '{projectLockJson}': '{targetFx}'") + Throw New InvalidDataException($"Expected platform Not found in '{projectLockJson}': '{targetFx}'") End If For Each targetProperty In targetObj.Properties @@ -724,7 +725,7 @@ Public Class BuildDevDivInsertionFiles Private Sub ParseSwrFile(path As String, ByRef version As Version, ByRef files As IEnumerable(Of String)) Dim lines = File.ReadAllLines(path) - version = version.Parse(lines.Single(Function(line) line.TrimStart().StartsWith("version=")).Split("="c)(1)) + version = Version.Parse(lines.Single(Function(line) line.TrimStart().StartsWith("version=")).Split("="c)(1)) files = (From line In lines Where line.TrimStart().StartsWith("file")).ToArray() End Sub @@ -1039,7 +1040,7 @@ Public Class BuildDevDivInsertionFiles End Sub - Private Sub GenerateRoslynCompilerNuSpec(filesToInsert As IEnumerable(Of String)) + Private Sub GenerateVSToolsRoslynCoreXTNuspec(filesToInsert As IEnumerable(Of String)) Const PackageName As String = "VS.Tools.Roslyn" ' No duplicates are allowed diff --git a/src/Test/DeployCoreClrTestRuntime/DeployCoreClrTestRuntime.csproj b/src/Test/DeployCoreClrTestRuntime/DeployCoreClrTestRuntime.csproj index 0f4381cf8a54a..4fa1f2cccaab2 100644 --- a/src/Test/DeployCoreClrTestRuntime/DeployCoreClrTestRuntime.csproj +++ b/src/Test/DeployCoreClrTestRuntime/DeployCoreClrTestRuntime.csproj @@ -5,6 +5,7 @@ x64 x64 + x64 Exe DeployCoreClrTestRuntime_DoNotUse false diff --git a/src/Test/Utilities/Desktop/AppDomainUtils.cs b/src/Test/Utilities/Desktop/AppDomainUtils.cs index fca76da648b55..141d5997cb3c2 100644 --- a/src/Test/Utilities/Desktop/AppDomainUtils.cs +++ b/src/Test/Utilities/Desktop/AppDomainUtils.cs @@ -13,7 +13,7 @@ public static class AppDomainUtils public static AppDomain Create(string name = null, string basePath = null) { - name = name ?? "Custtom AppDomain"; + name = name ?? "Custom AppDomain"; basePath = basePath ?? Path.GetDirectoryName(typeof(AppDomainUtils).Assembly.Location); lock (s_lock) diff --git a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs index 304adec7cc5a8..95209b461d02a 100644 --- a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs +++ b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Test.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Test.Utilities @@ -255,6 +256,15 @@ internal override void VisitNoneOperation(IOperation operation) { LogString("IOperation: "); LogCommonPropertiesAndNewLine(operation); + + if (operation is IOperationWithChildren operationWithChildren) + { + var children = operationWithChildren.Children.WhereNotNull().ToImmutableArray(); + if (children.Length > 0) + { + VisitArray(children, "Children", logElementCount: true); + } + } } public override void VisitBlockStatement(IBlockStatement operation) diff --git a/src/Tools/MicroBuild/Build.proj b/src/Tools/MicroBuild/Build.proj index cd75fcdba0d3e..15a63f55bf426 100644 --- a/src/Tools/MicroBuild/Build.proj +++ b/src/Tools/MicroBuild/Build.proj @@ -38,7 +38,7 @@ - + diff --git a/src/Tools/Source/CompilerGeneratorTools/DeployCompilerGeneratorToolsRuntime/DeployCompilerGeneratorToolsRuntime.csproj b/src/Tools/Source/CompilerGeneratorTools/DeployCompilerGeneratorToolsRuntime/DeployCompilerGeneratorToolsRuntime.csproj index 6c1d5daffb4dd..a7cfaf5197f2d 100644 --- a/src/Tools/Source/CompilerGeneratorTools/DeployCompilerGeneratorToolsRuntime/DeployCompilerGeneratorToolsRuntime.csproj +++ b/src/Tools/Source/CompilerGeneratorTools/DeployCompilerGeneratorToolsRuntime/DeployCompilerGeneratorToolsRuntime.csproj @@ -6,6 +6,7 @@ 14.0 x64 x64 + x64 {6DA08F12-32F2-4DD9-BBAD-982EB71A2C9B} Exe DeployCompilerGeneratorToolsRuntime diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/CompilersBoundTreeGenerator.csproj b/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/CompilersBoundTreeGenerator.csproj index 105962d92b265..4211db95c80a7 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/CompilersBoundTreeGenerator.csproj +++ b/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/CompilersBoundTreeGenerator.csproj @@ -6,6 +6,7 @@ True x64 x64 + x64 {02459936-CD2C-4F61-B671-5C518F2A3DDC} Exe Roslyn.Compilers.Internal.BoundTreeGenerator diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpErrorFactsGenerator/CSharpErrorFactsGenerator.csproj b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpErrorFactsGenerator/CSharpErrorFactsGenerator.csproj index d588982d9cd69..17fbabdc69905 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpErrorFactsGenerator/CSharpErrorFactsGenerator.csproj +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpErrorFactsGenerator/CSharpErrorFactsGenerator.csproj @@ -5,6 +5,7 @@ x64 x64 + x64 {288089C5-8721-458E-BE3E-78990DAB5E2E} Exe Roslyn.Compilers.CSharp.Internal.CSharpErrorFactsGenerator diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/CSharpSyntaxGenerator.csproj b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/CSharpSyntaxGenerator.csproj index de57ab661a9e1..8f8c7a2918ca2 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/CSharpSyntaxGenerator.csproj +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/CSharpSyntaxGenerator.csproj @@ -5,6 +5,7 @@ x64 x64 + x64 {288089C5-8721-458E-BE3E-78990DAB5E2D} Exe Roslyn.Compilers.CSharp.Internal.CSharpSyntaxGenerator diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicErrorFactsGenerator/VisualBasicErrorFactsGenerator.vbproj b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicErrorFactsGenerator/VisualBasicErrorFactsGenerator.vbproj index 4f2dc9a260d1b..bf950924681ed 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicErrorFactsGenerator/VisualBasicErrorFactsGenerator.vbproj +++ b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicErrorFactsGenerator/VisualBasicErrorFactsGenerator.vbproj @@ -6,6 +6,7 @@ true x64 x64 + x64 {909B656F-6095-4AC2-A5AB-C3F032315C45} Exe @@ -15,6 +16,7 @@ netcoreapp1.1 win7-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;osx.10.12-x64 true + $(NoWarn);40057 diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/VisualBasicSyntaxGenerator.vbproj b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/VisualBasicSyntaxGenerator.vbproj index 289f1a0d23404..9aa9b9d8967e7 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/VisualBasicSyntaxGenerator.vbproj +++ b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/VisualBasicSyntaxGenerator.vbproj @@ -6,6 +6,7 @@ true x64 x64 + x64 {6AA96934-D6B7-4CC8-990D-DB6B9DD56E34} Exe Microsoft.CodeAnalysis.VisualBasic.Internal.VBSyntaxGenerator.Program @@ -16,6 +17,7 @@ netcoreapp1.1 win7-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;osx.10.12-x64 true + $(NoWarn);40057 diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzer.cs index 15f0c7613c35e..43ee00a0a7c89 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzer.cs @@ -1,7 +1,9 @@ // 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; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -14,7 +16,6 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Versions; using Microsoft.VisualStudio.Designer.Interfaces; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; @@ -27,7 +28,6 @@ internal partial class DesignerAttributeIncrementalAnalyzer : ForegroundThreadAf private readonly IForegroundNotificationService _notificationService; private readonly IServiceProvider _serviceProvider; - private readonly DesignerAttributeState _state; private readonly IAsynchronousOperationListener _listener; /// @@ -37,6 +37,13 @@ internal partial class DesignerAttributeIncrementalAnalyzer : ForegroundThreadAf /// private IVSMDDesignerService _dotNotAccessDirectlyDesigner; + /// + /// Keep track of the last results we reported to VS. We can use this to diff future results + /// to report only what actually changed. + /// + private readonly ConcurrentDictionary> _lastReportedProjectData = + new ConcurrentDictionary>(); + public DesignerAttributeIncrementalAnalyzer( IServiceProvider serviceProvider, IForegroundNotificationService notificationService, @@ -48,13 +55,6 @@ public DesignerAttributeIncrementalAnalyzer( _notificationService = notificationService; _listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DesignerAttribute); - _state = new DesignerAttributeState(); - } - - public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) - { - _state.Remove(document.Id); - return _state.PersistAsync(document, new Data(VersionStamp.Default, VersionStamp.Default, designerAttributeArgument: null), cancellationToken); } public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) @@ -62,133 +62,173 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs return false; } - public async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) + public async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(document.IsFromPrimaryBranch()); - + Contract.ThrowIfFalse(project.IsFromPrimaryBranch()); cancellationToken.ThrowIfCancellationRequested(); - if (!document.Project.Solution.Workspace.Options.GetOption(InternalFeatureOnOffOptions.DesignerAttributes)) + var vsWorkspace = project.Solution.Workspace as VisualStudioWorkspaceImpl; + if (vsWorkspace == null) { return; } - // use tree version so that things like compiler option changes are considered - var textVersion = await document.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); - var projectVersion = await document.Project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false); - var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - - var existingData = await _state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); - if (existingData != null) + if (!vsWorkspace.Options.GetOption(InternalFeatureOnOffOptions.DesignerAttributes)) { - // check whether we can use the data as it is (can happen when re-using persisted data from previous VS session) - if (CheckVersions(document, textVersion, projectVersion, semanticVersion, existingData)) - { - RegisterDesignerAttribute(document, existingData.DesignerAttributeArgument); - return; - } + return; } - var result = await ScanDesignerAttributesOnRemoteHostIfPossibleAsync(document, cancellationToken).ConfigureAwait(false); - if (result.NotApplicable) + // CPS projects do not support designer attributes. So we just skip these projects entirely. + var isCPSProject = await Task.Factory.StartNew( + () => vsWorkspace.IsCPSProject(project), + cancellationToken, + TaskCreationOptions.None, + this.ForegroundTaskScheduler).ConfigureAwait(false); + + if (isCPSProject) { - _state.Remove(document.Id); return; } - // we checked all types in the document, but couldn't find designer attribute, but we can't say this document doesn't have designer attribute - // if the document also contains some errors. - var designerAttributeArgumentOpt = result.ContainsErrors ? new Optional() : new Optional(result.DesignerAttributeArgument); - await RegisterDesignerAttributeAndSaveStateAsync(document, textVersion, semanticVersion, designerAttributeArgumentOpt, cancellationToken).ConfigureAwait(false); - } - - private async Task ScanDesignerAttributesOnRemoteHostIfPossibleAsync(Document document, CancellationToken cancellationToken) - { - var service = document.GetLanguageService(); - if (service == null) + // Try to compute this data in the remote process. If that fails, then compute + // the results in the local process. + var pathToResult = await TryAnalyzeProjectInRemoteProcessAsync(project, cancellationToken).ConfigureAwait(false); + if (pathToResult == null) { - return new DesignerAttributeResult(designerAttributeArgument: null, containsErrors: true, notApplicable: true); + pathToResult = await AbstractDesignerAttributeService.TryAnalyzeProjectInCurrentProcessAsync( + project, cancellationToken).ConfigureAwait(false); } - return await service.ScanDesignerAttributesAsync(document, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + // Once we get the current data, diff it and report the results to VS. + RegisterDesignerAttributes(project, pathToResult); } - private bool CheckVersions( - Document document, VersionStamp textVersion, VersionStamp dependentProjectVersion, VersionStamp dependentSemanticVersion, Data existingData) + private async Task> TryAnalyzeProjectInRemoteProcessAsync(Project project, CancellationToken cancellationToken) { - // first check full version to see whether we can reuse data in same session, if we can't, check timestamp only version to see whether - // we can use it cross-session. - return document.CanReusePersistedTextVersion(textVersion, existingData.TextVersion) && - document.Project.CanReusePersistedDependentSemanticVersion(dependentProjectVersion, dependentSemanticVersion, existingData.SemanticVersion); + using (var session = await TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false)) + { + if (session == null) + { + return null; + } + + var serializedResults = await session.InvokeAsync( + nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync), project.Id).ConfigureAwait(false); + + var data = serializedResults.ToImmutableDictionary(kvp => kvp.FilePath); + return data; + } } - private async Task RegisterDesignerAttributeAndSaveStateAsync( - Document document, VersionStamp textVersion, VersionStamp semanticVersion, Optional designerAttributeArgumentOpt, CancellationToken cancellationToken) + private static async Task TryGetRemoteSessionAsync( + Solution solution, CancellationToken cancellationToken) { - if (!designerAttributeArgumentOpt.HasValue) + var client = await solution.Workspace.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false); + if (client == null) { - // no value means it couldn't determine whether this document has designer attribute or not. - // one of such case is when base type is error type. - return; + return null; } - var data = new Data(textVersion, semanticVersion, designerAttributeArgumentOpt.Value); - await _state.PersistAsync(document, data, cancellationToken).ConfigureAwait(false); - - RegisterDesignerAttribute(document, designerAttributeArgumentOpt.Value); + return await client.TryCreateCodeAnalysisServiceSessionAsync( + solution, cancellationToken).ConfigureAwait(false); } - private void RegisterDesignerAttribute(Document document, string designerAttributeArgument) + private void RegisterDesignerAttributes( + Project project, ImmutableDictionary pathToResult) { - var workspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - if (workspace == null) + // Diff this result against the last result we reported for this project. + // If there are any changes report them all at once to VS. + var lastPathToResult = _lastReportedProjectData.GetOrAdd( + project.Id, ImmutableDictionary.Empty); + + _lastReportedProjectData[project.Id] = pathToResult; + + var difference = GetDifference(lastPathToResult, pathToResult); + if (difference.Count == 0) { return; } - var documentId = document.Id; _notificationService.RegisterNotification(() => { - var vsDocument = workspace.GetHostDocument(documentId); - if (vsDocument == null) + foreach (var document in project.Documents) { - return; + if (difference.TryGetValue(document.FilePath, out var result)) + { + RegisterDesignerAttribute(document, result.DesignerAttributeArgument); + } } + }, _listener.BeginAsyncOperation("RegisterDesignerAttribute")); + } - uint itemId = vsDocument.GetItemId(); - if (itemId == (uint)VSConstants.VSITEMID.Nil) - { - // it is no longer part of the solution - return; - } + private ImmutableDictionary GetDifference( + ImmutableDictionary oldFileToResult, + ImmutableDictionary newFileToResult) + { + var difference = ImmutableDictionary.CreateBuilder(); - if (ErrorHandler.Succeeded(vsDocument.Project.Hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue))) + foreach (var newKvp in newFileToResult) + { + // 1) If this result is for a new document. We always need to report it + // 2) If both the old and new data have this result, then report it if it is different. + var filePath = newKvp.Key; + var newResult = newKvp.Value; + + if (!oldFileToResult.TryGetValue(filePath, out var oldResult) || + !newResult.Equals(oldResult)) { - var currentStringValue = string.IsNullOrEmpty(currentValue as string) ? null : (string)currentValue; - if (string.Equals(currentStringValue, designerAttributeArgument, StringComparison.OrdinalIgnoreCase)) - { - // PERF: Avoid sending the message if the project system already has the current value. - return; - } + difference.Add(filePath, newResult); } + } + + return difference.ToImmutable(); + } + + private void RegisterDesignerAttribute(Document document, string designerAttributeArgument) + { + var workspace = (VisualStudioWorkspaceImpl)document.Project.Solution.Workspace; + + var vsDocument = workspace.GetHostDocument(document.Id); + if (vsDocument == null) + { + return; + } + + uint itemId = vsDocument.GetItemId(); + if (itemId == (uint)VSConstants.VSITEMID.Nil) + { + // it is no longer part of the solution + return; + } - try + if (ErrorHandler.Succeeded(vsDocument.Project.Hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue))) + { + var currentStringValue = string.IsNullOrEmpty(currentValue as string) ? null : (string)currentValue; + if (string.Equals(currentStringValue, designerAttributeArgument, StringComparison.OrdinalIgnoreCase)) { - var designer = GetDesignerFromForegroundThread(); - if (designer != null) - { - designer.RegisterDesignViewAttribute(vsDocument.Project.Hierarchy, (int)itemId, dwClass: 0, pwszAttributeValue: designerAttributeArgument); - } + // PERF: Avoid sending the message if the project system already has the current value. + return; } - catch + } + + try + { + var designer = GetDesignerFromForegroundThread(); + if (designer != null) { - // DevDiv # 933717 - // turns out RegisterDesignViewAttribute can throw in certain cases such as a file failed to be checked out by source control - // or IVSHierarchy failed to set a property for this project - // - // just swallow it. don't crash VS. + designer.RegisterDesignViewAttribute(vsDocument.Project.Hierarchy, (int)itemId, dwClass: 0, pwszAttributeValue: designerAttributeArgument); } - }, _listener.BeginAsyncOperation("RegisterDesignerAttribute")); + } + catch + { + // DevDiv # 933717 + // turns out RegisterDesignViewAttribute can throw in certain cases such as a file failed to be checked out by source control + // or IVSHierarchy failed to set a property for this project + // + // just swallow it. don't crash VS. + } } private IVSMDDesignerService GetDesignerFromForegroundThread() @@ -204,54 +244,34 @@ private IVSMDDesignerService GetDesignerFromForegroundThread() return _dotNotAccessDirectlyDesigner; } - public void RemoveDocument(DocumentId documentId) - { - _state.Remove(documentId); - } +#region unused - private class Data - { - public readonly VersionStamp TextVersion; - public readonly VersionStamp SemanticVersion; - public readonly string DesignerAttributeArgument; - - public Data(VersionStamp textVersion, VersionStamp semanticVersion, string designerAttributeArgument) - { - this.TextVersion = textVersion; - this.SemanticVersion = semanticVersion; - this.DesignerAttributeArgument = designerAttributeArgument; - } - } - - #region unused public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + => SpecializedTasks.EmptyTask; + + public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) + => SpecializedTasks.EmptyTask; public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + => SpecializedTasks.EmptyTask; public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + => SpecializedTasks.EmptyTask; + + public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) + => SpecializedTasks.EmptyTask; public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + => SpecializedTasks.EmptyTask; - public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken) + public void RemoveDocument(DocumentId documentId) { - return SpecializedTasks.EmptyTask; } public void RemoveProject(ProjectId projectId) { } - #endregion + +#endregion } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzerProvider.cs index 6386228a19eea..68e459f7602c6 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeIncrementalAnalyzerProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribu [ExportIncrementalAnalyzerProvider(Name, new[] { WorkspaceKind.Host }), Shared] internal class DesignerAttributeIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider { - public const string Name = "DesignerAttributeIncrementalAnalyzerProvider"; + public const string Name = nameof(DesignerAttributeIncrementalAnalyzerProvider); private readonly IServiceProvider _serviceProvider; private readonly IForegroundNotificationService _notificationService; @@ -36,4 +36,4 @@ public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) return new DesignerAttributeIncrementalAnalyzer(_serviceProvider, _notificationService, _asyncListeners); } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeState.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeState.cs deleted file mode 100644 index e5d769f295c2e..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/DesignerAttributeState.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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; -using System.IO; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.SolutionCrawler.State; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute -{ - internal partial class DesignerAttributeIncrementalAnalyzer : IIncrementalAnalyzer - { - private class DesignerAttributeState : AbstractDocumentAnalyzerState - { - private const string FormatVersion = "1"; - - protected override string StateName - { - get - { - return ""; - } - } - - protected override int GetCount(Data data) - { - return 1; - } - - protected override Data TryGetExistingData(Stream stream, Document value, CancellationToken cancellationToken) - { - using (var reader = ObjectReader.TryGetReader(stream)) - { - if (reader != null) - { - var format = reader.ReadString(); - if (string.Equals(format, FormatVersion, StringComparison.InvariantCulture)) - { - var textVersion = VersionStamp.ReadFrom(reader); - var dataVersion = VersionStamp.ReadFrom(reader); - var designerAttributeArgument = reader.ReadString(); - - return new Data(textVersion, dataVersion, designerAttributeArgument); - } - } - } - - return null; - } - - protected override void WriteTo(Stream stream, Data data, CancellationToken cancellationToken) - { - using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) - { - writer.WriteString(FormatVersion); - data.TextVersion.WriteTo(writer); - data.SemanticVersion.WriteTo(writer); - writer.WriteString(data.DesignerAttributeArgument); - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs index 6cd13bf588cfa..71a8ab80602d2 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs @@ -40,6 +40,13 @@ internal sealed partial class VisualStudioProjectTracker : ForegroundThreadAffin private readonly HostWorkspaceServices _workspaceServices; + /// + /// Set to true while we're batching project loads. That is, between + /// and + /// . + /// + private bool _batchingProjectLoads = false; + /// /// The list of projects loaded in this batch between and /// . @@ -282,7 +289,7 @@ internal void AddProject(AbstractProject project) { StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(project)); } - else + else if (_batchingProjectLoads) { _projectsLoadedThisBatch.Add(project); } @@ -916,6 +923,7 @@ internal void OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) { AssertIsForeground(); + _batchingProjectLoads = true; _projectsLoadedThisBatch.Clear(); } @@ -930,6 +938,7 @@ internal void OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) StartPushingToWorkspaceAndNotifyOfOpenDocuments(_projectsLoadedThisBatch); } + _batchingProjectLoads = false; _projectsLoadedThisBatch.Clear(); } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index 9340d2f5bb24f..d27587ad0e88b 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -187,7 +187,12 @@ internal override bool CanChangeActiveContextDocument } internal override bool CanRenameFilesDuringCodeActions(CodeAnalysis.Project project) + => !IsCPSProject(project); + + internal bool IsCPSProject(CodeAnalysis.Project project) { + _foregroundObject.Value.AssertIsForeground(); + if (this.TryGetHierarchy(project.Id, out var hierarchy)) { // Currently renaming files in CPS projects (i.e. .Net Core) doesn't work proprey. @@ -195,10 +200,10 @@ internal override bool CanRenameFilesDuringCodeActions(CodeAnalysis.Project proj // (despite the DTE interfaces being synchronous). So Roslyn calls the methods // expecting the changes to happen immediately. Because they are deferred in CPS // this causes problems. - return !hierarchy.IsCapabilityMatch("CPS"); + return hierarchy.IsCapabilityMatch("CPS"); } - return true; + return false; } protected override bool CanApplyParseOptionChange(ParseOptions oldOptions, ParseOptions newOptions, CodeAnalysis.Project project) diff --git a/src/VisualStudio/Core/Def/Implementation/Serialization/AssemblySerializationInfoService.cs b/src/VisualStudio/Core/Def/Implementation/Serialization/AssemblySerializationInfoService.cs deleted file mode 100644 index bfcc341c8a38c..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/Serialization/AssemblySerializationInfoService.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Composition; -using System.IO; -using Microsoft.CodeAnalysis.Host.Mef; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Serialization -{ - [ExportWorkspaceService(typeof(IAssemblySerializationInfoService), ServiceLayer.Host)] - [Shared] - internal class AssemblySerializationInfoService : IAssemblySerializationInfoService - { - public bool Serializable(Solution solution, string assemblyFilePath) - { - if (assemblyFilePath == null || !File.Exists(assemblyFilePath)) - { - return false; - } - - // if solution is not from a disk, just create one. - if (solution.FilePath == null || !File.Exists(solution.FilePath)) - { - return false; - } - - return true; - } - - public bool TryGetSerializationPrefixAndVersion(Solution solution, string assemblyFilePath, out string prefix, out VersionStamp version) - { - prefix = PathUtilities.GetRelativePath(solution.FilePath, assemblyFilePath); - version = VersionStamp.Create(File.GetLastWriteTimeUtc(assemblyFilePath)); - - return true; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index e7874fe6e52af..27d826135cee9 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -276,7 +276,7 @@ private bool ApplySuppressionFix(IEnumerable diagnosticsToFix, F // We have different suppression fixers for every language. // So we need to group diagnostics by the containing project language and apply fixes separately. - languages = new HashSet(projectDiagnosticsToFixMap.Keys.Select(p => p.Language).Concat(documentDiagnosticsToFixMap.Select(kvp => kvp.Key.Project.Language))); + languages = new HashSet(projectDiagnosticsToFixMap.Select(p => p.Key.Language).Concat(documentDiagnosticsToFixMap.Select(kvp => kvp.Key.Project.Language))); foreach (var language in languages) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs new file mode 100644 index 0000000000000..264ac8171dc87 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs @@ -0,0 +1,80 @@ +// 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; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation +{ + [ExportWorkspaceService(typeof(ISymbolRenamedCodeActionOperationFactoryWorkspaceService), ServiceLayer.Host), Shared] + internal sealed class VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService : ISymbolRenamedCodeActionOperationFactoryWorkspaceService + { + private readonly IEnumerable _refactorNotifyServices; + + [ImportingConstructor] + public VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService( + [ImportMany] IEnumerable refactorNotifyServices) + { + _refactorNotifyServices = refactorNotifyServices; + } + + public CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) + { + return new RenameSymbolOperation( + _refactorNotifyServices, + symbol ?? throw new ArgumentNullException(nameof(symbol)), + newName ?? throw new ArgumentNullException(nameof(newName)), + startingSolution ?? throw new ArgumentNullException(nameof(startingSolution)), + updatedSolution ?? throw new ArgumentNullException(nameof(updatedSolution))); + } + + private class RenameSymbolOperation : CodeActionOperation + { + private readonly IEnumerable _refactorNotifyServices; + private readonly ISymbol _symbol; + private readonly string _newName; + private readonly Solution _startingSolution; + private readonly Solution _updatedSolution; + + public RenameSymbolOperation( + IEnumerable refactorNotifyServices, + ISymbol symbol, + string newName, + Solution startingSolution, + Solution updatedSolution) + { + _refactorNotifyServices = refactorNotifyServices; + _symbol = symbol; + _newName = newName; + _startingSolution = startingSolution; + _updatedSolution = updatedSolution; + } + + public override void Apply(Workspace workspace, CancellationToken cancellationToken = default(CancellationToken)) + { + var updatedDocumentIds = _updatedSolution.GetChanges(_startingSolution).GetProjectChanges().SelectMany(p => p.GetChangedDocuments()); + + foreach (var refactorNotifyService in _refactorNotifyServices) + { + // If something goes wrong and some language service rejects the rename, we + // can't really do anything about it because we're potentially in the middle of + // some unknown set of CodeActionOperations. This is a best effort approach. + + if (refactorNotifyService.TryOnBeforeGlobalSymbolRenamed(workspace, updatedDocumentIds, _symbol, _newName, throwOnFailure: false)) + { + refactorNotifyService.TryOnAfterGlobalSymbolRenamed(workspace, updatedDocumentIds, _symbol, _newName, throwOnFailure: false); + } + } + } + + public override string Title => string.Format(EditorFeaturesResources.Rename_0_to_1, _symbol.Name, _newName); + } + } +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerService.ProjectState.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerService.ProjectState.cs new file mode 100644 index 0000000000000..e4a1f6003d77f --- /dev/null +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerService.ProjectState.cs @@ -0,0 +1,21 @@ +// 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.Collections.Generic; + +namespace Microsoft.VisualStudio.LanguageServices.Packaging +{ + internal partial class PackageInstallerService + { + private struct ProjectState + { + public readonly bool IsEnabled; + public readonly Dictionary InstalledPackageToVersion; + + public ProjectState(bool isEnabled, Dictionary installedPackageToVersion) + { + IsEnabled = isEnabled; + InstalledPackageToVersion = installedPackageToVersion; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 51264ba221f47..2731b73f54f7d 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -56,8 +56,8 @@ internal partial class PackageInstallerService : AbstractDelayStartedService, IP private bool _solutionChanged; private HashSet _changedProjects = new HashSet(); - private readonly ConcurrentDictionary> _projectToInstalledPackageAndVersion = - new ConcurrentDictionary>(); + private readonly ConcurrentDictionary _projectToInstalledPackageAndVersion = + new ConcurrentDictionary(); [ImportingConstructor] public PackageInstallerService( @@ -77,7 +77,23 @@ public PackageInstallerService( public event EventHandler PackageSourcesChanged; - public bool IsEnabled => _packageServices != null; + private bool IsEnabled => _packageServices != null; + + bool IPackageInstallerService.IsEnabled(ProjectId projectId) + { + if (_packageServices == null) + { + return false; + } + + if (_projectToInstalledPackageAndVersion.TryGetValue(projectId, out var state)) + { + return state.IsEnabled; + } + + // If we haven't scanned the project yet, assume that we're available for it. + return true; + } protected override void EnableService() { @@ -270,10 +286,25 @@ private string GetInstalledVersion(string packageName, EnvDTE.Project dteProject var metadata = installedPackages.FirstOrDefault(m => m.Id == packageName); return metadata?.VersionString; } + catch (ArgumentException e) when (IsKnownNugetIssue(e)) + { + // Nuget may throw an ArgumentException when there is something about the project + // they do not like/support. + } catch (Exception e) when (FatalError.ReportWithoutCrash(e)) { - return null; } + + return null; + } + + private bool IsKnownNugetIssue(ArgumentException exception) + { + // See https://github.com/NuGet/Home/issues/4706 + // Nuget throws on legal projects. We do not want to report this exception + // as it is known (and NFWs are expensive), but we do want to report if we + // run into anything else. + return exception.Message.Contains("is not a valid version string"); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) @@ -388,8 +419,9 @@ private ProjectId DequeueNextProject(Solution solution) private void ProcessProjectChange(Solution solution, ProjectId projectId) { this.AssertIsForeground(); + // Remove anything we have associated with this project. - _projectToInstalledPackageAndVersion.TryRemove(projectId, out var installedPackages); + _projectToInstalledPackageAndVersion.TryRemove(projectId, out var projectState); var project = solution.GetProject(projectId); if (project == null) @@ -416,26 +448,35 @@ private void ProcessProjectChange(Solution solution, ProjectId projectId) return; } - installedPackages = new Dictionary(); + var installedPackages = new Dictionary(); + var isEnabled = false; // Calling into NuGet. Assume they may fail for any reason. try { var installedPackageMetadata = _packageServices.GetInstalledPackages(dteProject); installedPackages.AddRange(installedPackageMetadata.Select(m => new KeyValuePair(m.Id, m.VersionString))); + isEnabled = true; + } + catch (ArgumentException e) when (IsKnownNugetIssue(e)) + { + // Nuget may throw an ArgumentException when there is something about the project + // they do not like/support. } catch (Exception e) when (FatalError.ReportWithoutCrash(e)) { } - _projectToInstalledPackageAndVersion.AddOrUpdate(projectId, installedPackages, (_1, _2) => installedPackages); + var state = new ProjectState(isEnabled, installedPackages); + _projectToInstalledPackageAndVersion.AddOrUpdate( + projectId, state, (_1, _2) => state); } public bool IsInstalled(Workspace workspace, ProjectId projectId, string packageName) { ThisCanBeCalledOnAnyThread(); return _projectToInstalledPackageAndVersion.TryGetValue(projectId, out var installedPackages) && - installedPackages.ContainsKey(packageName); + installedPackages.InstalledPackageToVersion.ContainsKey(packageName); } public ImmutableArray GetInstalledVersions(string packageName) @@ -443,10 +484,10 @@ public ImmutableArray GetInstalledVersions(string packageName) ThisCanBeCalledOnAnyThread(); var installedVersions = new HashSet(); - foreach (var installedPackages in _projectToInstalledPackageAndVersion.Values) + foreach (var state in _projectToInstalledPackageAndVersion.Values) { string version = null; - if (installedPackages?.TryGetValue(packageName, out version) == true && version != null) + if (state.InstalledPackageToVersion.TryGetValue(packageName, out version) && version != null) { installedVersions.Add(version); } @@ -494,16 +535,13 @@ public IEnumerable GetProjectsWithInstalledPackage(Solution solution, s foreach (var kvp in this._projectToInstalledPackageAndVersion) { - var installedPackageAndVersion = kvp.Value; - if (installedPackageAndVersion != null) + var state = kvp.Value; + if (state.InstalledPackageToVersion.TryGetValue(packageName, out var installedVersion) && installedVersion == version) { - if (installedPackageAndVersion.TryGetValue(packageName, out var installedVersion) && installedVersion == version) + var project = solution.GetProject(kvp.Key); + if (project != null) { - var project = solution.GetProject(kvp.Key); - if (project != null) - { - result.Add(project); - } + result.Add(project); } } } @@ -631,4 +669,4 @@ public void UninstallPackage(EnvDTE.Project project, string packageId, bool remo => _packageUninstaller.UninstallPackage(project, packageId, removeDependencies); } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj index 3dced3f823996..66b63471c2003 100644 --- a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj +++ b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj @@ -128,7 +128,6 @@ - @@ -175,6 +174,7 @@ + @@ -494,7 +494,6 @@ - @@ -741,6 +740,7 @@ + diff --git a/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs b/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs index fb1cc5e0dd9b8..46c6e7688b7d1 100644 --- a/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs +++ b/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs @@ -29,10 +29,16 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo return false; } + // A project node is represented in two different hierarchies: the solution's IVsHierarchy (where it is a leaf node) + // and the project's own IVsHierarchy (where it is the root node). The IVsHierarchyItem joins them together for the + // purpose of creating the tree displayed in Solution Explorer. The project's hierarchy is what is passed from the + // project system to the language service, so that's the one the one to query here. To do that we need to get + // the "nested" hierarchy from the IVsHierarchyItem. var nestedHierarchy = hierarchyItem.HierarchyIdentity.NestedHierarchy; var nestedHierarchyId = hierarchyItem.HierarchyIdentity.NestedItemID; - if (!nestedHierarchy.TryGetCanonicalName(nestedHierarchyId, out string nestedCanonicalName)) + if (!nestedHierarchy.TryGetCanonicalName(nestedHierarchyId, out string nestedCanonicalName) + || !nestedHierarchy.TryGetItemName(nestedHierarchyId, out string nestedName)) { projectId = default(ProjectId); return false; @@ -41,8 +47,16 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo var project = _workspace.DeferredState.ProjectTracker.ImmutableProjects .Where(p => { + // Here we try to match the hierarchy from Solution Explorer to a hierarchy from the Roslyn project. + // The canonical name of a hierarchy item must be unique _within_ an hierarchy, but since we're + // examining multiple hierarchies the canonical name could be the same. Indeed this happens when two + // project files are in the same folder--they both use the full path to the _folder_ as the canonical + // name. To distinguish them we also examine the "regular" name, which will necessarily be different + // if the two projects are in the same folder. if (p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName) - && projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase)) + && p.Hierarchy.TryGetItemName((uint)VSConstants.VSITEMID.Root, out string projectName) + && projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase) + && projectName.Equals(nestedName)) { if (targetFrameworkMoniker == null) { diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index a0ac2a9bedcb0..ca558d84ed835 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -92,11 +92,11 @@ class Test { }"; var solution = workspace.CurrentSolution; - var result = await client.RunCodeAnalysisServiceOnRemoteHostAsync( + var result = await client.RunCodeAnalysisServiceOnRemoteHostAsync( solution, nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync), - solution.Projects.First().DocumentIds.First(), CancellationToken.None); + solution.Projects.First().Id, CancellationToken.None); - Assert.Equal(result.DesignerAttributeArgument, "Form"); + Assert.Equal(result[0].DesignerAttributeArgument, "Form"); } } diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb index 1b82262cbde41..f55ff538c3e19 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb @@ -47,6 +47,112 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim End Using End Sub + + + Public Sub SetCompilerOptions_LangVersion14() + Using environment = New TestEnvironment() + Dim project = CreateVisualBasicProject(environment, "Test") + + Dim compilerOptionsHost = DirectCast(project, Implementation.ProjectSystem.Interop.ICompilerOptionsHostObject) + Dim supported As Boolean + compilerOptionsHost.SetCompilerOptions("/langversion:14", supported) + Assert.True(supported) + + Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() + Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + + ' SetCompilerOptions only handles versions 15.3 and up + Assert.Equal(LanguageVersion.VisualBasic15, options.LanguageVersion) + Assert.Equal(LanguageVersion.VisualBasic15, options.SpecifiedLanguageVersion) + + project.Disconnect() + End Using + End Sub + + + + Public Sub SetCompilerOptions_LangVersion15() + Using environment = New TestEnvironment() + Dim project = CreateVisualBasicProject(environment, "Test") + + Dim compilerOptionsHost = DirectCast(project, Implementation.ProjectSystem.Interop.ICompilerOptionsHostObject) + Dim supported As Boolean + compilerOptionsHost.SetCompilerOptions("/langversion:15", supported) + Assert.True(supported) + + Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() + Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + + Assert.Equal(LanguageVersion.VisualBasic15, options.LanguageVersion) + Assert.Equal(LanguageVersion.VisualBasic15, options.SpecifiedLanguageVersion) + + project.Disconnect() + End Using + End Sub + + + + Public Sub SetCompilerOptions_LangVersionDefault() + Using environment = New TestEnvironment() + Dim project = CreateVisualBasicProject(environment, "Test") + + Dim compilerOptionsHost = DirectCast(project, Implementation.ProjectSystem.Interop.ICompilerOptionsHostObject) + Dim supported As Boolean + compilerOptionsHost.SetCompilerOptions("/langversion:Default", supported) + Assert.True(supported) + + Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() + Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + + Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) + Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + + project.Disconnect() + End Using + End Sub + + + + Public Sub SetCompilerOptions_LangVersion15_3() + Using environment = New TestEnvironment() + Dim project = CreateVisualBasicProject(environment, "Test") + + Dim compilerOptionsHost = DirectCast(project, Implementation.ProjectSystem.Interop.ICompilerOptionsHostObject) + Dim supported As Boolean + compilerOptionsHost.SetCompilerOptions("/langversion:15.3", supported) + Assert.True(supported) + + Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() + Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + + Assert.Equal(LanguageVersion.VisualBasic15_3, options.LanguageVersion) + Assert.Equal(LanguageVersion.VisualBasic15_3, options.SpecifiedLanguageVersion) + + project.Disconnect() + End Using + End Sub + + + + Public Sub SetCompilerOptions_LangVersionLatest() + Using environment = New TestEnvironment() + Dim project = CreateVisualBasicProject(environment, "Test") + + Dim compilerOptionsHost = DirectCast(project, Implementation.ProjectSystem.Interop.ICompilerOptionsHostObject) + Dim supported As Boolean + compilerOptionsHost.SetCompilerOptions("/langversion:latest", supported) + Assert.True(supported) + + Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() + Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + + Assert.Equal(LanguageVersion.Latest.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) + Assert.Equal(LanguageVersion.Latest.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + + project.Disconnect() + End Using + End Sub + diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpInteractive.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpInteractive.cs index a7da7a931fe31..e110ea1038d48 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpInteractive.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpInteractive.cs @@ -93,7 +93,7 @@ public async Task WpfInteractionAsync() VisualStudio.InteractiveWindow.SubmitText("b = null; w.Close(); w = null;"); } - [Fact] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19232")] public void TypingHelpDirectiveWorks() { VisualStudio.Workspace.SetUseSuggestionMode(true); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspacesNetCore.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspacesNetCore.cs index 3ac2153f492a0..59efc22b89791 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspacesNetCore.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspacesNetCore.cs @@ -41,7 +41,7 @@ public override void MetadataReference() base.MetadataReference(); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19223"), Trait(Traits.Feature, Traits.Features.Workspace)] public override void ProjectReference() { base.ProjectReference(); diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest index ddc6459c06620..98e129d348282 100644 --- a/src/VisualStudio/Setup/source.extension.vsixmanifest +++ b/src/VisualStudio/Setup/source.extension.vsixmanifest @@ -48,6 +48,5 @@ - diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb index ac6fd0531302c..ac629cfba7be5 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb @@ -392,7 +392,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Protected Overrides Function CreateParseOptions(commandLineArguments As CommandLineArguments) As ParseOptions Dim baseParseOptions = DirectCast(MyBase.CreateParseOptions(commandLineArguments), VisualBasicParseOptions) - Return VisualBasicProjectOptionsHelper.CreateParseOptions(baseParseOptions, _rawOptions) + + Dim resultParseOptions = VisualBasicProjectOptionsHelper.CreateParseOptions(baseParseOptions, _rawOptions) + + Dim commandLineOptions = DirectCast(commandLineArguments.ParseOptions, VisualBasicParseOptions) + If commandLineOptions.LanguageVersion > LanguageVersion.VisualBasic15 Then + ' For language versions after VB 15, we expect the version to be passed from MSBuild to the IDE + ' via command-line arguments (`ICompilerOptionsHostObject.SetCompilerOptions`) + ' instead of using `IVbcHostObject3.SetLanguageVersion` + resultParseOptions = resultParseOptions.WithLanguageVersion(commandLineOptions.LanguageVersion) + End If + + Return resultParseOptions End Function Private Shadows Sub UpdateOptions() diff --git a/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs index a4ef1c95191a2..8dc3fb75aebb8 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs @@ -1,5 +1,6 @@ // 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -170,7 +171,7 @@ private static bool CanBindToken(SyntaxToken token) /// argument. /// public static string GenerateNameForArgument( - this SemanticModel semanticModel, ArgumentSyntax argument) + this SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken) { // If it named argument then we use the name provided. if (argument.NameColon != null) @@ -178,11 +179,12 @@ public static string GenerateNameForArgument( return argument.NameColon.Name.Identifier.ValueText; } - return semanticModel.GenerateNameForExpression(argument.Expression); + return semanticModel.GenerateNameForExpression( + argument.Expression, capitalize: false, cancellationToken: cancellationToken); } public static string GenerateNameForArgument( - this SemanticModel semanticModel, AttributeArgumentSyntax argument) + this SemanticModel semanticModel, AttributeArgumentSyntax argument, CancellationToken cancellationToken) { // If it named argument then we use the name provided. if (argument.NameEquals != null) @@ -190,7 +192,8 @@ public static string GenerateNameForArgument( return argument.NameEquals.Name.Identifier.ValueText; } - return semanticModel.GenerateNameForExpression(argument.Expression); + return semanticModel.GenerateNameForExpression( + argument.Expression, capitalize: false, cancellationToken: cancellationToken); } /// @@ -198,7 +201,8 @@ public static string GenerateNameForArgument( /// that expression. /// public static string GenerateNameForExpression( - this SemanticModel semanticModel, ExpressionSyntax expression, bool capitalize = false) + this SemanticModel semanticModel, ExpressionSyntax expression, + bool capitalize, CancellationToken cancellationToken) { // Try to find a usable name node that we can use to name the // parameter. If we have an expression that has a name as part of it @@ -244,32 +248,78 @@ public static string GenerateNameForExpression( } } + // there was nothing in the expression to signify a name. If we're in an argument + // location, then try to choose a name based on the argument name. + var argumentName = TryGenerateNameForArgumentExpression( + semanticModel, expression, cancellationToken); + if (argumentName != null) + { + return capitalize ? argumentName.ToPascalCase() : argumentName.ToCamelCase(); + } + // Otherwise, figure out the type of the expression and generate a name from that // instead. - var info = semanticModel.GetTypeInfo(expression); + var info = semanticModel.GetTypeInfo(expression, cancellationToken); // If we can't determine the type, then fallback to some placeholders. var type = info.Type; return type.CreateParameterName(capitalize); } + private static string TryGenerateNameForArgumentExpression( + SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) + { + var topExpression = expression.WalkUpParentheses(); + if (topExpression.IsParentKind(SyntaxKind.Argument)) + { + var argument = (ArgumentSyntax)topExpression.Parent; + if (argument.NameColon != null) + { + return argument.NameColon.Name.Identifier.ValueText; + } + + var argumentList = argument.Parent as BaseArgumentListSyntax; + if (argumentList != null) + { + var index = argumentList.Arguments.IndexOf(argument); + var member = semanticModel.GetSymbolInfo(argumentList.Parent, cancellationToken).Symbol as IMethodSymbol; + if (member != null && index < member.Parameters.Length) + { + var parameter = member.Parameters[index]; + if (parameter.Type.OriginalDefinition.TypeKind != TypeKind.TypeParameter) + { + return parameter.Name; + } + } + } + } + + return null; + } + public static ImmutableArray GenerateParameterNames( - this SemanticModel semanticModel, ArgumentListSyntax argumentList) + this SemanticModel semanticModel, + ArgumentListSyntax argumentList, + CancellationToken cancellationToken) { - return semanticModel.GenerateParameterNames(argumentList.Arguments); + return semanticModel.GenerateParameterNames( + argumentList.Arguments, reservedNames: null, cancellationToken: cancellationToken); } public static IList GenerateParameterNames( this SemanticModel semanticModel, - AttributeArgumentListSyntax argumentList) + AttributeArgumentListSyntax argumentList, + CancellationToken cancellationToken) { - return semanticModel.GenerateParameterNames(argumentList.Arguments); + return semanticModel.GenerateParameterNames( + argumentList.Arguments, reservedNames: null, cancellationToken: cancellationToken); } public static ImmutableArray GenerateParameterNames( this SemanticModel semanticModel, IEnumerable arguments, - IList reservedNames = null) + IList reservedNames, + CancellationToken cancellationToken) { reservedNames = reservedNames ?? SpecializedCollections.EmptyList(); @@ -278,7 +328,7 @@ public static ImmutableArray GenerateParameterNames( arguments.Select(a => a.NameColon != null)).ToList(); var parameterNames = reservedNames.Concat( - arguments.Select(semanticModel.GenerateNameForArgument)).ToList(); + arguments.Select(a => semanticModel.GenerateNameForArgument(a, cancellationToken))).ToList(); return GenerateNames(reservedNames, isFixed, parameterNames); } @@ -293,7 +343,8 @@ private static ImmutableArray GenerateNames(IList reserve public static IList GenerateParameterNames( this SemanticModel semanticModel, IEnumerable arguments, - IList reservedNames = null) + IList reservedNames, + CancellationToken cancellationToken) { reservedNames = reservedNames ?? SpecializedCollections.EmptyList(); @@ -302,7 +353,7 @@ public static IList GenerateParameterNames( arguments.Select(a => a.NameEquals != null)).ToList(); var parameterNames = reservedNames.Concat( - arguments.Select(a => semanticModel.GenerateNameForArgument(a))).ToList(); + arguments.Select(a => semanticModel.GenerateNameForArgument(a, cancellationToken))).ToList(); return GenerateNames(reservedNames, isFixed, parameterNames); } diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs index 9702bdc3eed2a..985d5144e2eba 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs @@ -295,6 +295,13 @@ public static TNode ConvertToSingleLine(this TNode node, bool useElasticT return (TNode)rewriter.Visit(node); } + public static bool IsAsyncSupportingFunctionSyntax(this SyntaxNode node) + { + return node.IsKind(SyntaxKind.MethodDeclaration) + || node.IsAnyLambdaOrAnonymousMethod() + || node.IsKind(SyntaxKind.LocalFunctionStatement); + } + public static bool IsAnyArgumentList(this SyntaxNode node) { return node.IsKind(SyntaxKind.ArgumentList) || diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs index 68fae2b079a0e..a1e51b6633007 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs @@ -108,10 +108,8 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, SyntaxNode express return (expression as ExpressionSyntax).CanReplaceWithRValue(semanticModel, cancellationToken); } - public string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize = false) - { - return semanticModel.GenerateNameForExpression((ExpressionSyntax)expression, capitalize); - } + public string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize, CancellationToken cancellationToken) + => semanticModel.GenerateNameForExpression((ExpressionSyntax)expression, capitalize, cancellationToken); public ISymbol GetDeclaredSymbol(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index 04cdd88ad91b3..8a9c40f5c2c61 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -310,6 +310,11 @@ private IEnumerable InferTypeInArgument( return InferTypeInElementAccessExpression(elementAccess, index, argument); } + + if (argument.IsParentKind(SyntaxKind.TupleExpression)) + { + return InferTypeInTupleExpression((TupleExpressionSyntax)argument.Parent, argument); + } } if (argument.Parent.IsParentKind(SyntaxKind.ImplicitElementAccess) && @@ -331,6 +336,18 @@ private IEnumerable InferTypeInArgument( return SpecializedCollections.EmptyEnumerable(); } + private IEnumerable InferTypeInTupleExpression( + TupleExpressionSyntax tupleExpression, ArgumentSyntax argument) + { + var index = tupleExpression.Arguments.IndexOf(argument); + var parentTypes = InferTypes(tupleExpression); + + return parentTypes.Select(typeInfo => typeInfo.InferredType) + .OfType() + .Where(namedType => namedType.IsTupleType && index < namedType.TupleElements.Length) + .Select(tupleType => new TypeInferenceInfo(tupleType.TupleElements[index].Type)); + } + private IEnumerable InferTypeInAttributeArgument(AttributeArgumentSyntax argument, SyntaxToken? previousToken = null, ArgumentSyntax argumentOpt = null) { if (previousToken.HasValue) diff --git a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs index 3acf0e9251774..7ab27cb27c6d7 100644 --- a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs +++ b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs @@ -215,7 +215,8 @@ public override SyntaxToken VisitToken(SyntaxToken token) isRenameLocation || token.ValueText == _replacementText || isOldText || - _possibleNameConflicts.Contains(token.ValueText); + _possibleNameConflicts.Contains(token.ValueText) || + IsPossiblyDestructorConflict(token, _replacementText); if (tokenNeedsConflictCheck) { @@ -230,6 +231,13 @@ public override SyntaxToken VisitToken(SyntaxToken token) return newToken; } + private bool IsPossiblyDestructorConflict(SyntaxToken token, string replacementText) + { + return _replacementText == "Finalize" && + token.IsKind(SyntaxKind.IdentifierToken) && + token.Parent.IsKind(SyntaxKind.DestructorDeclaration); + } + private SyntaxNode Complexify(SyntaxNode originalNode, SyntaxNode newNode) { _isProcessingComplexifiedSpans = true; diff --git a/src/Workspaces/Core/Desktop/Workspace/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs b/src/Workspaces/Core/Desktop/Workspace/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs index b8e597b49880b..417c6b2eb6a1d 100644 --- a/src/Workspaces/Core/Desktop/Workspace/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs +++ b/src/Workspaces/Core/Desktop/Workspace/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs @@ -880,7 +880,7 @@ public bool SetLanguageVersion(string languageVersion) { if (!string.IsNullOrWhiteSpace(languageVersion)) { - _commandLineArgs.Add("/languageversion:" + languageVersion); + _commandLineArgs.Add("/langversion:" + languageVersion); } return true; diff --git a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage.Accessor.cs b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage.Accessor.cs index 5317debdd0f95..7a4d33d740d2d 100644 --- a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage.Accessor.cs +++ b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage.Accessor.cs @@ -30,11 +30,12 @@ private abstract class Accessor new MultiDictionary>(); /// - /// Keep track of how many threads are trying to write out this particular queue. All threads - /// trying to write out the queue will wait until all the writes are done. + /// The task responsible for writing out all the batched actions we have for a particular + /// queue. When new reads come in for that queue they can 'await' this write-task completing + /// so that all reads for the queue observe any previously completed writes. /// - private readonly Dictionary _writeQueueKeyToCountdown = - new Dictionary(); + private readonly Dictionary _writeQueueKeyToWriteTask = + new Dictionary(); public Accessor(SQLitePersistentStorage storage) { @@ -119,7 +120,7 @@ await AddWriteTaskAsync(key, con => private Task FlushPendingWritesAsync(SqlConnection connection, TKey key, CancellationToken cancellationToken) => Storage.FlushSpecificWritesAsync( - connection, _writeQueueKeyToWrites, _writeQueueKeyToCountdown, GetWriteQueueKey(key), cancellationToken); + connection, _writeQueueKeyToWrites, _writeQueueKeyToWriteTask, GetWriteQueueKey(key), cancellationToken); private Task AddWriteTaskAsync(TKey key, Action action, CancellationToken cancellationToken) => Storage.AddWriteTaskAsync(_writeQueueKeyToWrites, GetWriteQueueKey(key), action, cancellationToken); diff --git a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_Helpers.cs b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_Helpers.cs index 86d448098498c..ee63e5fe77c28 100644 --- a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_Helpers.cs +++ b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_Helpers.cs @@ -72,10 +72,10 @@ private static void CopyTo(Stream stream, byte[] bytes, int length) } /// - /// Amount of time to wait between flushing writes to disk. 250ms means we can flush - /// writes to disk four times a second. + /// Amount of time to wait between flushing writes to disk. 500ms means we can flush + /// writes to disk two times a second. /// - private const int FlushAllDelayMS = 250; + private const int FlushAllDelayMS = 500; /// /// We use a pool to cache reads/writes that are less than 4k. Testing with Roslyn, @@ -89,7 +89,7 @@ private static void CopyTo(Stream stream, byte[] bytes, int length) /// /// The max amount of byte[]s we cache. This caps our cache at 4MB while allowing /// us to massively speed up writing (by batching writes). Because we can write to - /// disk 4 times a second. That means a total of 16MB/s that can be written to disk + /// disk two times a second. That means a total of 8MB/s that can be written to disk /// using only our cache. Given that Roslyn itself only writes about 50MB to disk /// after several minutes of analysis, this amount of bandwidth is more than sufficient. /// diff --git a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_WriteBatching.cs b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_WriteBatching.cs index 3ffc4428f5a31..783d1ac68aecb 100644 --- a/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_WriteBatching.cs +++ b/src/Workspaces/Core/Desktop/Workspace/SQLite/SQLitePersistentStorage_WriteBatching.cs @@ -38,7 +38,7 @@ private async Task AddWriteTaskAsync( if (_flushAllTask == null) { var token = _shutdownTokenSource.Token; - var delay = + _flushAllTask = Task.Delay(FlushAllDelayMS, token) .ContinueWith( async _ => await FlushAllPendingWritesAsync(token).ConfigureAwait(false), @@ -52,14 +52,14 @@ private async Task AddWriteTaskAsync( private async Task FlushSpecificWritesAsync( SqlConnection connection, MultiDictionary> keyToWriteActions, - Dictionary keyToCountdown, + Dictionary keyToWriteTask, TKey key, CancellationToken cancellationToken) { var writesToProcess = ArrayBuilder>.GetInstance(); try { await FlushSpecificWritesAsync( - connection, keyToWriteActions, keyToCountdown, key, writesToProcess, cancellationToken).ConfigureAwait(false); + connection, keyToWriteActions, keyToWriteTask, key, writesToProcess, cancellationToken).ConfigureAwait(false); } finally { @@ -69,97 +69,90 @@ await FlushSpecificWritesAsync( private async Task FlushSpecificWritesAsync( SqlConnection connection, MultiDictionary> keyToWriteActions, - Dictionary keyToCountdown, TKey key, + Dictionary keyToWriteTask, TKey key, ArrayBuilder> writesToProcess, CancellationToken cancellationToken) { - // Many threads many be trying to flush a specific queue. If some other thread - // beats us to writing this queue, we want to still wait until it is down. To - // accomplish that, we use a countdown that effectively states how many current - // writers there are, and which only lets us past once all the concurrent writers - // say they are done. - CountdownEvent countdown; - - // Note: by blocking on _writeQueueGate we are guaranteed to see all the writes - // performed by FlushAllPendingWrites. - using (await _writeQueueGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + // Get's the task representing the current writes being performed by another + // thread for this queue+key, and a TaskCompletionSource we can use to let + // other threads know about our own progress writing any new writes in this queue. + var (previousWritesTask, taskCompletionSource) = await GetWriteTaskAsync().ConfigureAwait(false); + try { - // Get the writes we need to process. - writesToProcess.AddRange(keyToWriteActions[key]); - - // and clear them from the queues so we don't process things multiple times. - keyToWriteActions.Remove(key); - - // We may have acquired _writeQueueGate between the time that an existing thread - // completes the "Wait" below and grabs this lock. If that's the case, let go - // of the countdown associated with this key as it is no longer usable. - RemoveCountdownIfComplete(keyToCountdown, key); + // Wait for all previous writes to be flushed. + await previousWritesTask.ConfigureAwait(false); - // See if there's an existing countdown keeping track of the number of writers - // writing this queue. - if (!keyToCountdown.TryGetValue(key, out countdown)) + if (writesToProcess.Count == 0) { - // We're the first writer for this queue. Set the count to one, and keep - // it around so future concurrent writers will see it. - countdown = new CountdownEvent(initialCount: 1); - keyToCountdown.Add(key, countdown); + // No additional writes for us to flush. We can immediately bail out. + Debug.Assert(taskCompletionSource == null); + return; } - else - { - // If there is, increment the count to indicate that we're writing as well. - countdown.AddCount(); - } - - Debug.Assert(countdown.CurrentCount >= 1); - } - // Now actually process any writes we found for this queue. - ProcessWriteQueue(connection, writesToProcess); + // Now, if we have writes of our own, do them on this thread. + // + // Note: this flushing is not cancellable. We've already removed the + // writes from the write queue. If we were not to write them out we + // would be losing data. + Debug.Assert(taskCompletionSource != null); - // Mark that we're done writing out this queue, and wait until all other writers - // for this queue are done. Note: this needs to happen in the lock so that - // changes to the countdown value are observed consistently across all threads. - bool lastSignal; - using (await _writeQueueGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + ProcessWriteQueue(connection, writesToProcess); + } + catch (OperationCanceledException ex) { - lastSignal = countdown.Signal(); + taskCompletionSource?.TrySetCanceled(ex.CancellationToken); } - - // Don't proceed until all concurrent writers of this queue complete. - countdown.Wait(); - - // If we're the thread that finally got the countdown to zero, then dispose of this - // count down and remove it from the dictionary (if it hasn't already been replaced - // by the next request). - if (lastSignal) + catch (Exception ex) + { + taskCompletionSource?.TrySetException(ex); + } + finally { - Debug.Assert(countdown.CurrentCount == 0); + // Mark our TCS as completed. Any other threads waiting on us will now be able + // to proceed. + taskCompletionSource?.TrySetResult(0); + } - // Safe to call outside of lock. Countdown is only given out to a set of threads - // that have incremented it. And we can only get here once all the threads have - // been allowed to get past the 'Wait' point. Only one of those threads will - // have lastSignal set to true, so we'll only dispose this once. - countdown.Dispose(); + return; + // Local functions + async Task<(Task previousTask, TaskCompletionSource taskCompletionSource)> GetWriteTaskAsync() + { + // Have to acquire the semaphore. We're going to mutate the shared 'keyToWriteActions' + // and 'keyToWriteTask' collections. + // + // Note: by blocking on _writeQueueGate we are guaranteed to see all the writes + // performed by FlushAllPendingWritesAsync. using (await _writeQueueGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // Remove the countdown if it's still in the dictionary. It may not be if - // another thread came in after this batch of threads completed, and it - // removed the completed countdown already. - RemoveCountdownIfComplete(keyToCountdown, key); - } - } - } + // Get the writes we need to process. + writesToProcess.AddRange(keyToWriteActions[key]); - private void RemoveCountdownIfComplete( - Dictionary keyToCountdown, TKey key) - { - Debug.Assert(_writeQueueGate.CurrentCount == 0); + // and clear them from the queues so we don't process things multiple times. + keyToWriteActions.Remove(key); - if (keyToCountdown.TryGetValue(key, out var tempCountDown) && - tempCountDown.CurrentCount == 0) - { - keyToCountdown.Remove(key); + // Find the existing task responsible for writing to this queue. + var existingWriteTask = keyToWriteTask.TryGetValue(key, out var task) + ? task + : SpecializedTasks.EmptyTask; + + if (writesToProcess.Count == 0) + { + // We have no writes of our own. But there may be an existing task that + // is writing out this queue. Return this so our caller can wait for + // all existing writes to complete. + return (previousTask: existingWriteTask, taskCompletionSource: null); + } + + // Create a TCS that represents our own work writing out "writesToProcess". + // Store it in keyToWriteTask so that if other threads come along, they'll + // wait for us to complete before doing their own reads/writes on this queue. + var localCompletionSource = new TaskCompletionSource(); + + keyToWriteTask[key] = localCompletionSource.Task; + + return (previousTask: existingWriteTask, taskCompletionSource: localCompletionSource); + } } } diff --git a/src/Workspaces/Core/Desktop/Workspace/Storage/PersistentStorageService.cs b/src/Workspaces/Core/Desktop/Workspace/Storage/PersistentStorageService.cs index c846675c6954b..1819cd9e7ddbd 100644 --- a/src/Workspaces/Core/Desktop/Workspace/Storage/PersistentStorageService.cs +++ b/src/Workspaces/Core/Desktop/Workspace/Storage/PersistentStorageService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Storage /// A service that enables storing and retrieving of information associated with solutions, /// projects or documents across runtime sessions. /// - internal abstract partial class AbstractPersistentStorageService : IPersistentStorageService + internal abstract partial class AbstractPersistentStorageService : IPersistentStorageService2 { protected readonly IOptionService OptionService; private readonly SolutionSizeTracker _solutionSizeTracker; @@ -58,8 +58,11 @@ protected AbstractPersistentStorageService(IOptionService optionService, bool te protected abstract bool ShouldDeleteDatabase(Exception exception); public IPersistentStorage GetStorage(Solution solution) + => GetStorage(solution, checkBranchId: true); + + public IPersistentStorage GetStorage(Solution solution, bool checkBranchId) { - if (!ShouldUseDatabase(solution)) + if (!ShouldUseDatabase(solution, checkBranchId)) { return NoOpPersistentStorage.Instance; } @@ -136,16 +139,21 @@ private IPersistentStorage GetStorage(Solution solution, string workingFolderPat } } - private bool ShouldUseDatabase(Solution solution) + private bool ShouldUseDatabase(Solution solution, bool checkBranchId) { if (_testing) { return true; } - // we only use database for primary solution. (Ex, forked solution will not use database) - if (solution.BranchId != solution.Workspace.PrimaryBranchId || solution.FilePath == null) + if (solution.FilePath == null) + { + return false; + } + + if (checkBranchId && solution.BranchId != solution.Workspace.PrimaryBranchId) { + // we only use database for primary solution. (Ex, forked solution will not use database) return false; } @@ -159,6 +167,14 @@ private bool SolutionSizeAboveThreshold(Solution solution) return true; } + var workspace = solution.Workspace; + if (workspace.Kind == WorkspaceKind.RemoteWorkspace || + workspace.Kind == WorkspaceKind.RemoteTemporaryWorkspace) + { + // Storage is always available in the remote server. + return true; + } + if (_solutionSizeTracker == null) { return false; diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs index bc6f24eb445bc..c1af48119044c 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs @@ -112,7 +112,7 @@ public static void LogDiagnosticsStats(ImmutableDictionary { - m[s_documentsWithDiagnosticsToFix] = documentsAndDiagnosticsToFixMap.Keys.Count(); + m[s_documentsWithDiagnosticsToFix] = documentsAndDiagnosticsToFixMap.Count; m[s_totalDiagnosticsToFix] = documentsAndDiagnosticsToFixMap.Values.Sum(v => v.Length); })); } @@ -121,7 +121,7 @@ public static void LogDiagnosticsStats(ImmutableDictionary { - m[s_projectsWithDiagnosticsToFix] = projectsAndDiagnosticsToFixMap.Keys.Count(); + m[s_projectsWithDiagnosticsToFix] = projectsAndDiagnosticsToFixMap.Count; m[s_totalDiagnosticsToFix] = projectsAndDiagnosticsToFixMap.Values.Sum(v => v.Length); })); } diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index ab5d278cde284..f070ce9035bbe 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -174,7 +174,6 @@ public static ImmutableDictionary(); ImmutableArray diagnostics; - ImmutableDictionary> diagnosticsByAnalyzerMap; foreach (var analyzer in analyzers) { @@ -182,20 +181,18 @@ public static ImmutableDictionary /// To prevent lots of allocations, we concatenate all the names in all our @@ -80,35 +80,35 @@ internal partial class SymbolTreeInfo }; private SymbolTreeInfo( - VersionStamp version, + Checksum checksum, string concatenatedNames, Node[] sortedNodes, Task spellCheckerTask, OrderPreservingMultiDictionary inheritanceMap) - : this(version, concatenatedNames, sortedNodes, spellCheckerTask) + : this(checksum, concatenatedNames, sortedNodes, spellCheckerTask) { var indexBasedInheritanceMap = CreateIndexBasedInheritanceMap(inheritanceMap); _inheritanceMap = indexBasedInheritanceMap; } private SymbolTreeInfo( - VersionStamp version, + Checksum checksum, string concatenatedNames, Node[] sortedNodes, Task spellCheckerTask, OrderPreservingMultiDictionary inheritanceMap) - : this(version, concatenatedNames, sortedNodes, spellCheckerTask) + : this(checksum, concatenatedNames, sortedNodes, spellCheckerTask) { _inheritanceMap = inheritanceMap; } private SymbolTreeInfo( - VersionStamp version, + Checksum checksum, string concatenatedNames, Node[] sortedNodes, Task spellCheckerTask) { - _version = version; + Checksum = checksum; _concatenatedNames = concatenatedNames; _nodes = ImmutableArray.Create(sortedNodes); _spellCheckerTask = spellCheckerTask; @@ -315,15 +315,21 @@ private int BinarySearch(string name) _ => new SemaphoreSlim(1); private static Task GetSpellCheckerTask( - Solution solution, VersionStamp version, string filePath, + Solution solution, Checksum checksum, string filePath, string concatenatedNames, Node[] sortedNodes) { // Create a new task to attempt to load or create the spell checker for this // SymbolTreeInfo. This way the SymbolTreeInfo will be ready immediately // for non-fuzzy searches, and soon afterwards it will be able to perform // fuzzy searches as well. - return Task.Run(() => LoadOrCreateSpellCheckerAsync(solution, filePath, - v => new SpellChecker(v, sortedNodes.Select(n => new StringSlice(concatenatedNames, n.NameSpan))))); + return Task.Run(() => LoadOrCreateSpellCheckerAsync(solution, checksum, filePath, + () => CreateSpellCheckerAsync(checksum, concatenatedNames, sortedNodes))); + } + + private static Task CreateSpellCheckerAsync(Checksum checksum, string concatenatedNames, Node[] sortedNodes) + { + return Task.FromResult(new SpellChecker( + checksum, sortedNodes.Select(n => new StringSlice(concatenatedNames, n.NameSpan)))); } private static void SortNodes( @@ -472,7 +478,7 @@ private string GetName(Node node) internal void AssertEquivalentTo(SymbolTreeInfo other) { - Debug.Assert(_version.Equals(other._version)); + Debug.Assert(Checksum.Equals(other.Checksum)); Debug.Assert(_concatenatedNames == other._concatenatedNames); Debug.Assert(_nodes.Length == other._nodes.Length); @@ -499,16 +505,16 @@ internal void AssertEquivalentTo(SymbolTreeInfo other) } private static SymbolTreeInfo CreateSymbolTreeInfo( - Solution solution, VersionStamp version, + Solution solution, Checksum checksum, string filePath, ImmutableArray unsortedNodes, OrderPreservingMultiDictionary inheritanceMap) { SortNodes(unsortedNodes, out var concatenatedNames, out var sortedNodes); var createSpellCheckerTask = GetSpellCheckerTask( - solution, version, filePath, concatenatedNames, sortedNodes); + solution, checksum, filePath, concatenatedNames, sortedNodes); return new SymbolTreeInfo( - version, concatenatedNames, sortedNodes, createSpellCheckerTask, inheritanceMap); + checksum, concatenatedNames, sortedNodes, createSpellCheckerTask, inheritanceMap); } private OrderPreservingMultiDictionary CreateIndexBasedInheritanceMap( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 3a96404e0f923..680635d5627ea 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; @@ -57,6 +59,16 @@ private static Metadata GetMetadataNoThrow(PortableExecutableReference reference } } + public static Task TryGetInfoForMetadataReferenceAsync( + Solution solution, PortableExecutableReference reference, + bool loadOnly, CancellationToken cancellationToken) + { + var checksum = GetMetadataChecksum(solution, reference, cancellationToken); + return TryGetInfoForMetadataReferenceAsync( + solution, reference, checksum, + loadOnly, cancellationToken); + } + /// /// Produces a for a given . /// Note: can return null if we weren't able to actually load the metadata for some @@ -65,6 +77,7 @@ private static Metadata GetMetadataNoThrow(PortableExecutableReference reference public static Task TryGetInfoForMetadataReferenceAsync( Solution solution, PortableExecutableReference reference, + Checksum checksum, bool loadOnly, CancellationToken cancellationToken) { @@ -90,11 +103,11 @@ public static Task TryGetInfoForMetadataReferenceAsync( } return TryGetInfoForMetadataReferenceSlowAsync( - solution, reference, loadOnly, metadata, cancellationToken); + solution, reference, checksum, loadOnly, metadata, cancellationToken); } private static async Task TryGetInfoForMetadataReferenceSlowAsync( - Solution solution, PortableExecutableReference reference, + Solution solution, PortableExecutableReference reference, Checksum checksum, bool loadOnly, Metadata metadata, CancellationToken cancellationToken) { // Find the lock associated with this piece of metadata. This way only one thread is @@ -109,7 +122,7 @@ private static async Task TryGetInfoForMetadataReferenceSlowAsyn } var info = await LoadOrCreateMetadataSymbolTreeInfoAsync( - solution, reference, loadOnly, cancellationToken: cancellationToken).ConfigureAwait(false); + solution, reference, checksum, loadOnly, cancellationToken).ConfigureAwait(false); if (info == null && loadOnly) { return null; @@ -124,32 +137,44 @@ private static async Task TryGetInfoForMetadataReferenceSlowAsyn } } + public static Checksum GetMetadataChecksum( + Solution solution, PortableExecutableReference reference, CancellationToken cancellationToken) + { + // We can reuse the index for any given reference as long as it hasn't changed. + // So our checksum is just the checksum for the PEReference itself. + var serializer = new Serializer(solution.Workspace); + var checksum = serializer.CreateChecksum(reference, cancellationToken); + return checksum; + } + private static Task LoadOrCreateMetadataSymbolTreeInfoAsync( Solution solution, PortableExecutableReference reference, + Checksum checksum, bool loadOnly, CancellationToken cancellationToken) { var filePath = reference.FilePath; + return LoadOrCreateAsync( solution, + checksum, filePath, loadOnly, - create: version => CreateMetadataSymbolTreeInfo(solution, version, reference, cancellationToken), - keySuffix: "", - getVersion: info => info._version, - readObject: reader => ReadSymbolTreeInfo(reader, (version, names, nodes) => GetSpellCheckerTask(solution, version, filePath, names, nodes)), - writeObject: (w, i) => i.WriteTo(w), + createAsync: () => CreateMetadataSymbolTreeInfoAsync(solution, checksum, reference, cancellationToken), + keySuffix: "_Metadata", + getPersistedChecksum: info => info.Checksum, + readObject: reader => ReadSymbolTreeInfo(reader, (names, nodes) => GetSpellCheckerTask(solution, checksum, filePath, names, nodes)), cancellationToken: cancellationToken); } - private static SymbolTreeInfo CreateMetadataSymbolTreeInfo( - Solution solution, VersionStamp version, + private static Task CreateMetadataSymbolTreeInfoAsync( + Solution solution, Checksum checksum, PortableExecutableReference reference, CancellationToken cancellationToken) { - var creator = new MetadataInfoCreator(solution, version, reference, cancellationToken); - return creator.Create(); + var creator = new MetadataInfoCreator(solution, checksum, reference, cancellationToken); + return Task.FromResult(creator.Create()); } private struct MetadataInfoCreator : IDisposable @@ -158,7 +183,7 @@ private struct MetadataInfoCreator : IDisposable private static ObjectPool> s_stringListPool = new ObjectPool>(() => new List()); private readonly Solution _solution; - private readonly VersionStamp _version; + private readonly Checksum _checksum; private readonly PortableExecutableReference _reference; private readonly CancellationToken _cancellationToken; @@ -173,10 +198,10 @@ private struct MetadataInfoCreator : IDisposable private readonly List _allTypeDefinitions; public MetadataInfoCreator( - Solution solution, VersionStamp version, PortableExecutableReference reference, CancellationToken cancellationToken) + Solution solution, Checksum checksum, PortableExecutableReference reference, CancellationToken cancellationToken) { _solution = solution; - _version = version; + _checksum = checksum; _reference = reference; _cancellationToken = cancellationToken; _metadataReader = null; @@ -239,7 +264,7 @@ internal SymbolTreeInfo Create() var unsortedNodes = GenerateUnsortedNodes(); return SymbolTreeInfo.CreateSymbolTreeInfo( - _solution, _version, _reference.FilePath, unsortedNodes, _inheritanceMap); + _solution, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap); } public void Dispose() diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 7480fc8478569..d7a9b48c474f5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -1,7 +1,6 @@ // 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; -using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,31 +14,27 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - internal partial class SymbolTreeInfo + internal partial class SymbolTreeInfo : IObjectWritable { - private const string PrefixMetadataSymbolTreeInfo = "_"; - private const string SerializationFormat = "15"; + private const string PrefixMetadataSymbolTreeInfo = ""; + private const string SerializationFormat = "17"; /// /// Loads the SymbolTreeInfo for a given assembly symbol (metadata or project). If the /// info can't be loaded, it will be created (and persisted if possible). /// private static Task LoadOrCreateSourceSymbolTreeInfoAsync( - Solution solution, - IAssemblySymbol assembly, - string filePath, - bool loadOnly, - CancellationToken cancellationToken) + Project project, Checksum checksum, bool loadOnly, CancellationToken cancellationToken) { return LoadOrCreateAsync( - solution, - filePath, + project.Solution, + checksum, + project.FilePath, loadOnly, - create: version => CreateSourceSymbolTreeInfo(solution, version, assembly, filePath, cancellationToken), - keySuffix: "", - getVersion: info => info._version, - readObject: reader => ReadSymbolTreeInfo(reader, (version, names, nodes) => GetSpellCheckerTask(solution, version, filePath, names, nodes)), - writeObject: (w, i) => i.WriteTo(w), + createAsync: () => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), + keySuffix: "_Source", + getPersistedChecksum: info => info.Checksum, + readObject: reader => ReadSymbolTreeInfo(reader, (names, nodes) => GetSpellCheckerTask(project.Solution, checksum, project.FilePath, names, nodes)), cancellationToken: cancellationToken); } @@ -49,18 +44,19 @@ private static Task LoadOrCreateSourceSymbolTreeInfoAsync( /// private static Task LoadOrCreateSpellCheckerAsync( Solution solution, + Checksum checksum, string filePath, - Func create) + Func> createAsync) { return LoadOrCreateAsync( solution, + checksum, filePath, loadOnly: false, - create: create, - keySuffix: "SpellChecker", - getVersion: s => s.Version, + createAsync: createAsync, + keySuffix: "_SpellChecker", + getPersistedChecksum: s => s.Checksum, readObject: SpellChecker.ReadFrom, - writeObject: (w, i) => i.WriteTo(w), cancellationToken: CancellationToken.None); } @@ -70,30 +66,28 @@ private static Task LoadOrCreateSpellCheckerAsync( /// private static async Task LoadOrCreateAsync( Solution solution, + Checksum checksum, string filePath, bool loadOnly, - Func create, + Func> createAsync, string keySuffix, - Func getVersion, + Func getPersistedChecksum, Func readObject, - Action writeObject, - CancellationToken cancellationToken) where T : class + CancellationToken cancellationToken) where T : class, IObjectWritable { - // See if we can even use serialization. If not, we'll just have to make the value - // from scratch. - if (ShouldCreateFromScratch(solution, filePath, out var prefix, out var version, cancellationToken)) + if (checksum == null) { - return loadOnly ? null : create(VersionStamp.Default); + return loadOnly ? null : await createAsync().ConfigureAwait(false); } // Ok, we can use persistence. First try to load from the persistence service. - var persistentStorageService = solution.Workspace.Services.GetService(); + var persistentStorageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); T result; - using (var storage = persistentStorageService.GetStorage(solution)) + using (var storage = persistentStorageService.GetStorage(solution, checkBranchId: false)) { // Get the unique key to identify our data. - var key = PrefixMetadataSymbolTreeInfo + prefix + keySuffix; + var key = PrefixMetadataSymbolTreeInfo + keySuffix + "_" + filePath; using (var stream = await storage.ReadStreamAsync(key, cancellationToken).ConfigureAwait(false)) using (var reader = ObjectReader.TryGetReader(stream)) { @@ -103,7 +97,7 @@ private static async Task LoadOrCreateAsync( // If we're able to, and the version of the persisted data matches // our version, then we can reuse this instance. result = readObject(reader); - if (result != null && VersionStamp.CanReusePersistedVersion(version, getVersion(result))) + if (result != null && checksum == getPersistedChecksum(result)) { return result; } @@ -121,13 +115,13 @@ private static async Task LoadOrCreateAsync( } // Now, try to create a new instance and write it to the persistence service. - result = create(version); + result = await createAsync().ConfigureAwait(false); if (result != null) { using (var stream = SerializableBytes.CreateWritableStream()) using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) { - writeObject(writer, result); + result.WriteTo(writer); stream.Position = 0; await storage.WriteStreamAsync(key, stream, cancellationToken).ConfigureAwait(false); @@ -138,40 +132,10 @@ private static async Task LoadOrCreateAsync( return result; } - private static bool ShouldCreateFromScratch( - Solution solution, - string filePath, - out string prefix, - out VersionStamp version, - CancellationToken cancellationToken) - { - prefix = null; - version = default(VersionStamp); - - var service = solution.Workspace.Services.GetService(); - if (service == null) - { - return true; - } - - // check whether the assembly that belong to a solution is something we can serialize - if (!service.Serializable(solution, filePath)) - { - return true; - } - - if (!service.TryGetSerializationPrefixAndVersion(solution, filePath, out prefix, out version)) - { - return true; - } - - return false; - } - public void WriteTo(ObjectWriter writer) { writer.WriteString(SerializationFormat); - _version.WriteTo(writer); + Checksum.WriteTo(writer); writer.WriteString(_concatenatedNames); @@ -196,23 +160,24 @@ public void WriteTo(ObjectWriter writer) } } - internal static SymbolTreeInfo ReadSymbolTreeInfo_ForTestingPurposesOnly(ObjectReader reader) + internal static SymbolTreeInfo ReadSymbolTreeInfo_ForTestingPurposesOnly( + ObjectReader reader, Checksum checksum) { return ReadSymbolTreeInfo(reader, - (version, names, nodes) => Task.FromResult( - new SpellChecker(version, nodes.Select(n => new StringSlice(names, n.NameSpan))))); + (names, nodes) => Task.FromResult( + new SpellChecker(checksum, nodes.Select(n => new StringSlice(names, n.NameSpan))))); } private static SymbolTreeInfo ReadSymbolTreeInfo( ObjectReader reader, - Func> createSpellCheckerTask) + Func> createSpellCheckerTask) { try { var formatVersion = reader.ReadString(); if (string.Equals(formatVersion, SerializationFormat, StringComparison.Ordinal)) { - var version = VersionStamp.ReadFrom(reader); + var checksum = Checksum.ReadFrom(reader); var concatenatedNames = reader.ReadString(); @@ -241,8 +206,8 @@ private static SymbolTreeInfo ReadSymbolTreeInfo( } } - var spellCheckerTask = createSpellCheckerTask(version, concatenatedNames, nodes); - return new SymbolTreeInfo(version, concatenatedNames, nodes, spellCheckerTask, inheritanceMap); + var spellCheckerTask = createSpellCheckerTask(concatenatedNames, nodes); + return new SymbolTreeInfo(checksum, concatenatedNames, nodes, spellCheckerTask, inheritanceMap); } } catch diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 0139233a6bba8..ef70350e1803e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -1,9 +1,13 @@ -using System; +// 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; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols @@ -24,20 +28,57 @@ private static void FreeSymbolMap(MultiDictionary symbolMap) s_symbolMapPool.Free(symbolMap); } - public static async Task GetInfoForSourceAssemblyAsync( - Project project, CancellationToken cancellationToken) + public static Task GetInfoForSourceAssemblyAsync( + Project project, Checksum checksum, CancellationToken cancellationToken) { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + return LoadOrCreateSourceSymbolTreeInfoAsync( + project, checksum, loadOnly: false, cancellationToken: cancellationToken); + } + + public static async Task GetSourceSymbolsChecksumAsync(Project project, CancellationToken cancellationToken) + { + // The SymbolTree for source is built from the source-symbols from the project's compilation's + // assembly. Specifically, we only get the name, kind and parent/child relationship of all the + // child symbols. So we want to be able to reuse the index as long as none of these have + // changed. The only thing that can make those source-symbols change in that manner are if + // the text of any document changes, or if options for the project change. So we build our + // checksum out of that data. + var serializer = new Serializer(project.Solution.Workspace); + var projectStateChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + + // Order the documents by FilePath. Default ordering in the RemoteWorkspace is + // to be ordered by Guid (which is not consistent across VS sessions). + var textChecksumsTasks = project.Documents.OrderBy(d => d.FilePath).Select(async d => + { + var documentStateChecksum = await d.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return documentStateChecksum.Text; + }); - return await LoadOrCreateSourceSymbolTreeInfoAsync( - project.Solution, compilation.Assembly, project.FilePath, - loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false); + var compilationOptionsChecksum = projectStateChecksums.CompilationOptions; + var parseOptionsChecksum = projectStateChecksums.ParseOptions; + var textChecksums = await Task.WhenAll(textChecksumsTasks).ConfigureAwait(false); + + var allChecksums = ArrayBuilder.GetInstance(); + try + { + allChecksums.AddRange(textChecksums); + allChecksums.Add(compilationOptionsChecksum); + allChecksums.Add(parseOptionsChecksum); + + var checksum = Checksum.Create(nameof(SymbolTreeInfo), allChecksums); + return checksum; + } + finally + { + allChecksums.Free(); + } } - internal static SymbolTreeInfo CreateSourceSymbolTreeInfo( - Solution solution, VersionStamp version, IAssemblySymbol assembly, - string filePath, CancellationToken cancellationToken) + internal static async Task CreateSourceSymbolTreeInfoAsync( + Project project, Checksum checksum, CancellationToken cancellationToken) { + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var assembly = compilation.Assembly; if (assembly == null) { return null; @@ -49,7 +90,7 @@ internal static SymbolTreeInfo CreateSourceSymbolTreeInfo( GenerateSourceNodes(assembly.GlobalNamespace, unsortedNodes, s_getMembersNoPrivate); return CreateSymbolTreeInfo( - solution, version, filePath, unsortedNodes.ToImmutableAndFree(), + project.Solution, checksum, project.FilePath, unsortedNodes.ToImmutableAndFree(), inheritanceMap: new OrderPreservingMultiDictionary()); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs index f9de2f0bc78ae..592a9a94d1325 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs @@ -11,80 +11,93 @@ namespace Microsoft.CodeAnalysis.FindSymbols { internal sealed partial class SyntaxTreeIndex { - private readonly VersionStamp _version; - private readonly LiteralInfo _literalInfo; private readonly IdentifierInfo _identifierInfo; private readonly ContextInfo _contextInfo; private readonly DeclarationInfo _declarationInfo; private SyntaxTreeIndex( - VersionStamp version, + Checksum checksum, LiteralInfo literalInfo, IdentifierInfo identifierInfo, ContextInfo contextInfo, DeclarationInfo declarationInfo) { - Version = version; + this.Checksum = checksum; _literalInfo = literalInfo; _identifierInfo = identifierInfo; _contextInfo = contextInfo; _declarationInfo = declarationInfo; } - /// - /// snapshot based cache to guarantee same info is returned without re-calculating for - /// same solution snapshot. - /// - /// since document will be re-created per new solution, this should go away as soon as - /// there is any change on workspace. - /// - private static readonly ConditionalWeakTable s_infoCache - = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable s_documentToIndex = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable s_documentIdToIndex = new ConditionalWeakTable(); public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken) { Contract.Requires(document.IsFromPrimaryBranch()); - // we already have information. move on - if (await PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) + var checksum = await GetChecksumAsync(document, cancellationToken).ConfigureAwait(false); + + // Check if we've already created and persisted the index for this document. + if (await PrecalculatedAsync(document, checksum, cancellationToken).ConfigureAwait(false)) { return; } - var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); + // If not, create and save the index. + var data = await CreateIndexAsync(document, checksum, cancellationToken).ConfigureAwait(false); await data.SaveAsync(document, cancellationToken).ConfigureAwait(false); } - private static async Task GetIndexAsync( + public static async Task GetIndexAsync( Document document, - ConditionalWeakTable cache, - Func> generator, CancellationToken cancellationToken) { - if (cache.TryGetValue(document, out var info)) + // See if we already cached an index with this direct document index. If so we can just + // return it with no additional work. + if (!s_documentToIndex.TryGetValue(document, out var index)) { - return info; + index = await GetIndexWorkerAsync(document, cancellationToken).ConfigureAwait(false); + + // Populate our caches with this data. + s_documentToIndex.GetValue(document, _ => index); + s_documentIdToIndex.GetValue(document.Id, _ => index); } - info = await generator(document, cancellationToken).ConfigureAwait(false); - if (info != null) + return index; + } + + private static async Task GetIndexWorkerAsync( + Document document, + CancellationToken cancellationToken) + { + var checksum = await GetChecksumAsync(document, cancellationToken).ConfigureAwait(false); + + // Check if we have an index for a previous version of this document. If our + // checksums match, we can just use that. + if (s_documentIdToIndex.TryGetValue(document.Id, out var index) && + index.Checksum == checksum) + { + // The previous index we stored with this documentId is still valid. Just + // return that. + return index; + } + + // What we have in memory isn't valid. Try to load from the persistence service. + index = await LoadAsync(document, checksum, cancellationToken).ConfigureAwait(false); + if (index != null) { - return cache.GetValue(document, _ => info); + return index; } // alright, we don't have cached information, re-calculate them here. - var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); + index = await CreateIndexAsync(document, checksum, cancellationToken).ConfigureAwait(false); // okay, persist this info - await data.SaveAsync(document, cancellationToken).ConfigureAwait(false); + await index.SaveAsync(document, cancellationToken).ConfigureAwait(false); - return cache.GetValue(document, _ => data); + return index; } - - public static Task GetIndexAsync(Document document, CancellationToken cancellationToken) - => GetIndexAsync(document, s_infoCache, s_loadAsync, cancellationToken); - - private static Func> s_loadAsync = LoadAsync; } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs index 376ea7aa56007..1632f9d07d4d2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs @@ -23,7 +23,8 @@ internal sealed partial class SyntaxTreeIndex public static readonly ObjectPool> LongLiteralHashSetPool = new ObjectPool>(() => new HashSet(), 20); - private static async Task CreateInfoAsync(Document document, CancellationToken cancellationToken) + private static async Task CreateIndexAsync( + Document document, Checksum checksum, CancellationToken cancellationToken) { var syntaxFacts = document.GetLanguageService(); var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive; @@ -154,10 +155,8 @@ private static async Task CreateInfoAsync(Document document, Ca } } - var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - return new SyntaxTreeIndex( - version, + checksum, new LiteralInfo( new BloomFilter(FalsePositiveProbability, stringLiterals, longLiterals)), new IdentifierInfo( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs index 4a11652d7ee15..3c99216793b57 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs @@ -1,70 +1,59 @@ // 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; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Versions; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { internal sealed partial class SyntaxTreeIndex : IObjectWritable { - private const string PersistenceName = ""; - private const string SerializationFormat = "6"; + private const string PersistenceName = ""; + private const string SerializationFormat = "9"; - /// - /// in memory cache will hold onto any info related to opened documents in primary branch or all documents in forked branch - /// - /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. - /// - private static readonly ConditionalWeakTable> s_cache = - new ConditionalWeakTable>(); + public readonly Checksum Checksum; - public readonly VersionStamp Version; - - private void WriteVersion(ObjectWriter writer, string formatVersion) + private void WriteFormatAndChecksum(ObjectWriter writer, string formatVersion) { writer.WriteString(formatVersion); - this.Version.WriteTo(writer); + Checksum.WriteTo(writer); } - private static bool TryReadVersion(ObjectReader reader, string formatVersion, out VersionStamp version) + private static bool TryReadFormatAndChecksum( + ObjectReader reader, string formatVersion, out Checksum checksum) { - version = VersionStamp.Default; + checksum = null; if (reader.ReadString() != formatVersion) { return false; } - version = VersionStamp.ReadFrom(reader); + checksum = Checksum.ReadFrom(reader); return true; } private static async Task LoadAsync( - Document document, string persistenceName, string formatVersion, - Func readFrom, CancellationToken cancellationToken) + Document document, Checksum checksum, CancellationToken cancellationToken) { - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); - var syntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); + var solution = document.Project.Solution; + var persistentStorageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); try { // attempt to load from persisted state - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) - using (var stream = await storage.ReadStreamAsync(document, persistenceName, cancellationToken).ConfigureAwait(false)) + using (var storage = persistentStorageService.GetStorage(solution, checkBranchId: false)) + using (var stream = await storage.ReadStreamAsync(document, PersistenceName, cancellationToken).ConfigureAwait(false)) using (var reader = ObjectReader.TryGetReader(stream)) { if (reader != null) { - if (TryReadVersion(reader, formatVersion, out var persistVersion) && - document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistVersion)) + if (FormatAndChecksumMatches(reader, SerializationFormat, checksum)) { - return readFrom(reader, syntaxVersion); + return ReadFrom(reader, checksum); } } } @@ -77,24 +66,49 @@ private static async Task LoadAsync( return null; } - private static async Task SaveAsync( - Document document, string persistenceName, string formatVersion, SyntaxTreeIndex data, CancellationToken cancellationToken) + private static bool FormatAndChecksumMatches( + ObjectReader reader, string formatVersion, Checksum checksum) { - Contract.Requires(!await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)); + return TryReadFormatAndChecksum(reader, formatVersion, out var persistChecksum) && + persistChecksum == checksum; + } + + public static async Task GetChecksumAsync( + Document document, CancellationToken cancellationToken) + { + // Since we build the SyntaxTreeIndex from a SyntaxTree, we need our checksum to change + // any time the SyntaxTree could have changed. Right now, that can only happen if the + // text of the document changes, or the ParseOptions change. So we get the checksums + // for both of those, and merge them together to make the final checksum. + + var documentChecksumState = await document.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var textChecksum = documentChecksumState.Text; - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); + var parseOptions = document.Project.ParseOptions; + var serializer = new Serializer(document.Project.Solution.Workspace); + var parseOptionsChecksum = ChecksumCache.GetOrCreate( + parseOptions, _ => serializer.CreateChecksum(parseOptions, cancellationToken)); + + return Checksum.Create(nameof(SyntaxTreeIndex), new[] { textChecksum, parseOptionsChecksum }); + } + + private async Task SaveAsync( + Document document, CancellationToken cancellationToken) + { + var solution = document.Project.Solution; + var persistentStorageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); try { - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) + using (var storage = persistentStorageService.GetStorage(solution, checkBranchId: false)) using (var stream = SerializableBytes.CreateWritableStream()) using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) { - data.WriteVersion(writer, formatVersion); - data.WriteTo(writer); + this.WriteFormatAndChecksum(writer, SerializationFormat); + this.WriteTo(writer); stream.Position = 0; - return await storage.WriteStreamAsync(document, persistenceName, stream, cancellationToken).ConfigureAwait(false); + return await storage.WriteStreamAsync(document, PersistenceName, stream, cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (IOUtilities.IsNormalIOException(e)) @@ -105,24 +119,22 @@ private static async Task SaveAsync( return false; } - private static async Task PrecalculatedAsync(Document document, string persistenceName, string formatVersion, CancellationToken cancellationToken) + private static async Task PrecalculatedAsync( + Document document, Checksum checksum, CancellationToken cancellationToken) { - Contract.Requires(document.IsFromPrimaryBranch()); - - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); - var syntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); + var solution = document.Project.Solution; + var persistentStorageService = (IPersistentStorageService2)solution.Workspace.Services.GetService(); // check whether we already have info for this document try { - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) - using (var stream = await storage.ReadStreamAsync(document, persistenceName, cancellationToken).ConfigureAwait(false)) + using (var storage = persistentStorageService.GetStorage(solution, checkBranchId: false)) + using (var stream = await storage.ReadStreamAsync(document, PersistenceName, cancellationToken).ConfigureAwait(false)) using (var reader = ObjectReader.TryGetReader(stream)) { if (reader != null) { - return TryReadVersion(reader, formatVersion, out var persistVersion) && - document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistVersion); + return FormatAndChecksumMatches(reader, SerializationFormat, checksum); } } } @@ -142,7 +154,8 @@ public void WriteTo(ObjectWriter writer) _declarationInfo.WriteTo(writer); } - private static SyntaxTreeIndex ReadFrom(ObjectReader reader, VersionStamp version) + private static SyntaxTreeIndex ReadFrom( + ObjectReader reader, Checksum checksum) { var literalInfo = LiteralInfo.TryReadFrom(reader); var identifierInfo = IdentifierInfo.TryReadFrom(reader); @@ -155,118 +168,7 @@ private static SyntaxTreeIndex ReadFrom(ObjectReader reader, VersionStamp versio } return new SyntaxTreeIndex( - version, literalInfo.Value, identifierInfo.Value, contextInfo.Value, declarationInfo.Value); - } - - private Task SaveAsync(Document document, CancellationToken cancellationToken) - => SaveAsync(document, s_cache, PersistenceName, SerializationFormat, cancellationToken); - - private async Task SaveAsync( - Document document, - ConditionalWeakTable> cache, - string persistenceName, - string serializationFormat, - CancellationToken cancellationToken) - { - var workspace = document.Project.Solution.Workspace; - var infoTable = GetInfoTable(document.Project.Solution.BranchId, workspace, cache); - - // if it is forked document - if (await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)) - { - infoTable.Remove(document.Id); - infoTable.GetValue(document.Id, _ => this); - return false; - } - - // okay, cache this info if it is from opened document or persistence failed. - var persisted = await SaveAsync(document, persistenceName, serializationFormat, this, cancellationToken).ConfigureAwait(false); - if (!persisted || document.IsOpen()) - { - var primaryInfoTable = GetInfoTable(workspace.PrimaryBranchId, workspace, cache); - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => this); - } - - return persisted; - } - - private static Task LoadAsync(Document document, CancellationToken cancellationToken) - => LoadAsync(document, ReadFrom, s_cache, PersistenceName, SerializationFormat, cancellationToken); - - private static async Task LoadAsync( - Document document, - Func reader, - ConditionalWeakTable> cache, - string persistenceName, - string serializationFormat, - CancellationToken cancellationToken) - { - var infoTable = cache.GetValue( - document.Project.Solution.BranchId, - _ => new ConditionalWeakTable()); - var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - // first look to see if we already have the info in the cache - if (infoTable.TryGetValue(document.Id, out var info) && info.Version == version) - { - return info; - } - - // cache is invalid. remove it - infoTable.Remove(document.Id); - - // check primary cache to see whether we have valid info there - var primaryInfoTable = cache.GetValue( - document.Project.Solution.Workspace.PrimaryBranchId, - _ => new ConditionalWeakTable()); - if (primaryInfoTable.TryGetValue(document.Id, out info) && info.Version == version) - { - return info; - } - - // check whether we can get it from persistence service - info = await LoadAsync(document, persistenceName, serializationFormat, reader, cancellationToken).ConfigureAwait(false); - if (info != null) - { - // save it in the cache. persisted info is always from primary branch. no reason to save it to the branched document cache. - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => info); - return info; - } - - // well, we don't have this information. - return null; - } - - private static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) - => PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); - - private static ConditionalWeakTable GetInfoTable( - BranchId branchId, - Workspace workspace, - ConditionalWeakTable> cache) - { - return cache.GetValue(branchId, id => - { - if (id == workspace.PrimaryBranchId) - { - workspace.DocumentClosed += (sender, e) => - { - if (!e.Document.IsFromPrimaryBranch()) - { - return; - } - - if (cache.TryGetValue(e.Document.Project.Solution.BranchId, out var infoTable)) - { - // remove closed document from primary branch from live cache. - infoTable.Remove(e.Document.Id); - } - }; - } - - return new ConditionalWeakTable(); - }); + checksum, literalInfo.Value, identifierInfo.Value, contextInfo.Value, declarationInfo.Value); } } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs index a5d59a6e588b6..bb04aa4567cf2 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs @@ -71,7 +71,7 @@ internal interface ISemanticFactsService : ILanguageService bool CanReplaceWithRValue(SemanticModel semanticModel, SyntaxNode expression, CancellationToken cancellationToken); - string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize = false); + string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize, CancellationToken cancellationToken); ISymbol GetDeclaredSymbol(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Log/FunctionId.cs b/src/Workspaces/Core/Portable/Log/FunctionId.cs index 328e8278f11ee..608bc297dbe10 100644 --- a/src/Workspaces/Core/Portable/Log/FunctionId.cs +++ b/src/Workspaces/Core/Portable/Log/FunctionId.cs @@ -371,5 +371,6 @@ internal enum FunctionId CodeLens_FindReferenceMethodsAsync, CodeLens_GetFullyQualifiedName, RemoteHostClientService_Restarted, + CodeAnalysisService_GetDesignerAttributesAsync, } } diff --git a/src/Workspaces/Core/Portable/Packaging/IPackageInstallerService.cs b/src/Workspaces/Core/Portable/Packaging/IPackageInstallerService.cs index 46b94400ed46d..40b136736450f 100644 --- a/src/Workspaces/Core/Portable/Packaging/IPackageInstallerService.cs +++ b/src/Workspaces/Core/Portable/Packaging/IPackageInstallerService.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Packaging { internal interface IPackageInstallerService : IWorkspaceService { - bool IsEnabled { get; } + bool IsEnabled(ProjectId projectId); bool IsInstalled(Workspace workspace, ProjectId projectId, string packageName); diff --git a/src/Workspaces/Core/Portable/Serialization/AssemblySerializationInfoService.cs b/src/Workspaces/Core/Portable/Serialization/AssemblySerializationInfoService.cs deleted file mode 100644 index 5ef2c77778d29..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/AssemblySerializationInfoService.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Serialization -{ - [ExportWorkspaceService(typeof(IAssemblySerializationInfoService), ServiceLayer.Default)] - [Shared] - internal class AssemblySerializationInfoService : IAssemblySerializationInfoService - { - public bool Serializable(Solution solution, string assemblyFilePath) - { - return false; - } - - public bool TryGetSerializationPrefixAndVersion(Solution solution, string assemblyFilePath, out string prefix, out VersionStamp version) - { - prefix = string.Empty; - version = VersionStamp.Default; - - return false; - } - } -} diff --git a/src/Workspaces/Core/Portable/Serialization/IAssemblySerializationInfoService.cs b/src/Workspaces/Core/Portable/Serialization/IAssemblySerializationInfoService.cs deleted file mode 100644 index 26ae70e84d2b1..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/IAssemblySerializationInfoService.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.Serialization -{ - internal interface IAssemblySerializationInfoService : IWorkspaceService - { - bool Serializable(Solution solution, string assemblyFilePath); - bool TryGetSerializationPrefixAndVersion(Solution solution, string assemblyFilePath, out string prefix, out VersionStamp version); - } -} diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index a546ba643499e..f40c71e5f3d64 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -253,6 +253,17 @@ public static bool IsOrdinaryMethod(this ISymbol symbol) return (symbol as IMethodSymbol)?.MethodKind == MethodKind.Ordinary; } + public static bool IsOrdinaryMethodOrLocalFunction(this ISymbol symbol) + { + if (!(symbol is IMethodSymbol method)) + { + return false; + } + + return method.MethodKind == MethodKind.Ordinary + || method.MethodKind == MethodKind.LocalFunction; + } + public static bool IsDelegateType(this ISymbol symbol) { return symbol is ITypeSymbol && ((ITypeSymbol)symbol).TypeKind == TypeKind.Delegate; diff --git a/src/Workspaces/Core/Portable/Utilities/AsyncLazy`1.cs b/src/Workspaces/Core/Portable/Utilities/AsyncLazy`1.cs index 53f8ca15a3ad1..a443cd6985437 100644 --- a/src/Workspaces/Core/Portable/Utilities/AsyncLazy`1.cs +++ b/src/Workspaces/Core/Portable/Utilities/AsyncLazy`1.cs @@ -179,6 +179,12 @@ public override T GetValue(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + // If the value is already available, return it immediately + if (TryGetValue(out T value)) + { + return value; + } + Request request = null; AsynchronousComputationToStart? newAsynchronousComputation = null; @@ -297,7 +303,14 @@ public override Task GetValueAsync(CancellationToken cancellationToken) // Optimization: if we're already cancelled, do not pass go if (cancellationToken.IsCancellationRequested) { - return new Task(() => default(T), cancellationToken); + return Task.FromCanceled(cancellationToken); + } + + // Avoid taking the lock if a cached value is available + var cachedResult = _cachedResult; + if (cachedResult != null) + { + return cachedResult; } Request request; diff --git a/src/Workspaces/Core/Portable/Utilities/ObjectPools/PooledObject.cs b/src/Workspaces/Core/Portable/Utilities/ObjectPools/PooledObject.cs index 005c6719204ce..f0e5d5bd071cf 100644 --- a/src/Workspaces/Core/Portable/Utilities/ObjectPools/PooledObject.cs +++ b/src/Workspaces/Core/Portable/Utilities/ObjectPools/PooledObject.cs @@ -36,32 +36,50 @@ public void Dispose() #region factory public static PooledObject Create(ObjectPool pool) { - return new PooledObject(pool, Allocator, Releaser); + return new PooledObject( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } public static PooledObject> Create(ObjectPool> pool) { - return new PooledObject>(pool, Allocator, Releaser); + return new PooledObject>( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } public static PooledObject> Create(ObjectPool> pool) { - return new PooledObject>(pool, Allocator, Releaser); + return new PooledObject>( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } public static PooledObject> Create(ObjectPool> pool) { - return new PooledObject>(pool, Allocator, Releaser); + return new PooledObject>( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } public static PooledObject> Create(ObjectPool> pool) { - return new PooledObject>(pool, Allocator, Releaser); + return new PooledObject>( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } public static PooledObject> Create(ObjectPool> pool) { - return new PooledObject>(pool, Allocator, Releaser); + return new PooledObject>( + pool, + p => Allocator(p), + (p, sb) => Releaser(p, sb)); } #endregion diff --git a/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs b/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs index f4438e12f0eb3..31035b894eba2 100644 --- a/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs +++ b/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs @@ -10,21 +10,21 @@ namespace Roslyn.Utilities { - internal class SpellChecker + internal class SpellChecker : IObjectWritable { - private const string SerializationFormat = "2"; + private const string SerializationFormat = "3"; - public VersionStamp Version { get; } + public Checksum Checksum { get; } private readonly BKTree _bkTree; - public SpellChecker(VersionStamp version, BKTree bKTree) + public SpellChecker(Checksum checksum, BKTree bKTree) { - Version = version; + Checksum = checksum; _bkTree = bKTree; } - public SpellChecker(VersionStamp version, IEnumerable corpus) - : this(version, BKTree.Create(corpus)) + public SpellChecker(Checksum checksum, IEnumerable corpus) + : this(checksum, BKTree.Create(corpus)) { } @@ -42,10 +42,10 @@ public IList FindSimilarWords(string value, bool substringsAreSimilar) return array; } - internal void WriteTo(ObjectWriter writer) + void IObjectWritable.WriteTo(ObjectWriter writer) { writer.WriteString(SerializationFormat); - Version.WriteTo(writer); + Checksum.WriteTo(writer); _bkTree.WriteTo(writer); } @@ -56,11 +56,11 @@ internal static SpellChecker ReadFrom(ObjectReader reader) var formatVersion = reader.ReadString(); if (string.Equals(formatVersion, SerializationFormat, StringComparison.Ordinal)) { - var version = VersionStamp.ReadFrom(reader); + var checksum = Checksum.ReadFrom(reader); var bkTree = BKTree.ReadFrom(reader); if (bkTree != null) { - return new SpellChecker(version, bkTree); + return new SpellChecker(checksum, bkTree); } } } diff --git a/src/Workspaces/Core/Portable/Utilities/StringEscapeEncoder.cs b/src/Workspaces/Core/Portable/Utilities/StringEscapeEncoder.cs index 02e3d0655fdd5..a139d10249530 100644 --- a/src/Workspaces/Core/Portable/Utilities/StringEscapeEncoder.cs +++ b/src/Workspaces/Core/Portable/Utilities/StringEscapeEncoder.cs @@ -16,9 +16,9 @@ public static string Escape(this string text, char escapePrefix, params char[] p { var prefixIndex = text.IndexOf(escapePrefix, startIndex); var prohibitIndex = text.IndexOfAny(prohibitedCharacters, startIndex); - var index = prefixIndex > 0 && prohibitIndex > 0 ? Math.Min(prefixIndex, prohibitIndex) - : prefixIndex > 0 ? prefixIndex - : prohibitIndex > 0 ? prohibitIndex + var index = prefixIndex >= 0 && prohibitIndex >= 0 ? Math.Min(prefixIndex, prohibitIndex) + : prefixIndex >= 0 ? prefixIndex + : prohibitIndex >= 0 ? prohibitIndex : -1; if (index < 0) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageService.cs index a5c3054978c98..dfebcf54a6d39 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageService.cs @@ -9,4 +9,9 @@ public interface IPersistentStorageService : IWorkspaceService { IPersistentStorage GetStorage(Solution solution); } + + internal interface IPersistentStorageService2 : IPersistentStorageService + { + IPersistentStorage GetStorage(Solution solution, bool checkBranchId); + } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs index 705cd73bff87c..d183c46945090 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs @@ -2,7 +2,7 @@ namespace Microsoft.CodeAnalysis.Host { - internal class NoOpPersistentStorageService : IPersistentStorageService + internal class NoOpPersistentStorageService : IPersistentStorageService2 { public static readonly IPersistentStorageService Instance = new NoOpPersistentStorageService(); @@ -12,5 +12,8 @@ private NoOpPersistentStorageService() public IPersistentStorage GetStorage(Solution solution) => NoOpPersistentStorage.Instance; + + public IPersistentStorage GetStorage(Solution solution, bool checkBranchId) + => NoOpPersistentStorage.Instance; } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 499a088c4cf3e..ee80c165b5996 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -133,7 +133,7 @@ private static async Task ComputeLatestDocumentVersionAsync(Immuta { // this may produce a version that is out of sync with the actual Document versions. var latestVersion = VersionStamp.Default; - foreach (var doc in documentStates.Values) + foreach (var (_, doc) in documentStates) { cancellationToken.ThrowIfCancellationRequested(); @@ -144,7 +144,7 @@ private static async Task ComputeLatestDocumentVersionAsync(Immuta } } - foreach (var additionalDoc in additionalDocumentStates.Values) + foreach (var (_, additionalDoc) in additionalDocumentStates) { cancellationToken.ThrowIfCancellationRequested(); @@ -180,7 +180,7 @@ private static async Task ComputeLatestDocumentTopLevelChangeVersi { // this may produce a version that is out of sync with the actual Document versions. var latestVersion = VersionStamp.Default; - foreach (var doc in documentStates.Values) + foreach (var (_, doc) in documentStates) { cancellationToken.ThrowIfCancellationRequested(); @@ -188,7 +188,7 @@ private static async Task ComputeLatestDocumentTopLevelChangeVersi latestVersion = version.GetNewerVersion(latestVersion); } - foreach (var additionalDoc in additionalDocumentStates.Values) + foreach (var (_, additionalDoc) in additionalDocumentStates) { cancellationToken.ThrowIfCancellationRequested(); @@ -420,7 +420,7 @@ public ProjectState UpdateParseOptions(ParseOptions options) // update parse options for all documents too var docMap = _documentStates; - foreach (var docId in _documentStates.Keys) + foreach (var (docId, _) in _documentStates) { var oldDocState = this.GetDocumentState(docId); var newDocState = oldDocState.UpdateParseOptions(options); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index e2929c3881bfc..a2f483256f9c4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -372,7 +372,7 @@ public ProjectState GetProjectState(IAssemblySymbol assemblySymbol, Cancellation } // TODO: Remove this loop when we add source assembly symbols to s_assemblyOrModuleSymbolToProjectMap - foreach (var state in _projectIdToProjectStateMap.Values) + foreach (var (_, state) in _projectIdToProjectStateMap) { if (this.TryGetCompilation(state.Id, out var compilation)) { diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 110ac003a3424..c679598918023 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -162,6 +162,10 @@ public Solution CurrentSolution /// /// Sets the of this workspace. This method does not raise a event. /// + /// + /// This method does not guarantee that linked files will have the same contents. Callers + /// should enforce that policy before passing in the new solution. + /// protected Solution SetCurrentSolution(Solution solution) { var currentSolution = Volatile.Read(ref _latestSolution); @@ -830,35 +834,97 @@ protected internal void OnDocumentInfoChanged(DocumentId documentId, DocumentInf /// protected internal void OnDocumentTextChanged(DocumentId documentId, SourceText newText, PreservationMode mode) { - using (_serializationLock.DisposableWait()) - { - CheckDocumentIsInCurrentSolution(documentId); - - var oldSolution = this.CurrentSolution; - var newSolution = this.SetCurrentSolution(oldSolution.WithDocumentText(documentId, newText, mode)); - - var newDocument = newSolution.GetDocument(documentId); - this.OnDocumentTextChanged(newDocument); - - this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.DocumentChanged, oldSolution, newSolution, documentId: documentId); - } + OnAnyDocumentTextChanged( + documentId, + newText, + mode, + CheckDocumentIsInCurrentSolution, + (solution, docId) => solution.GetRelatedDocumentIds(docId), + (solution, docId, text, preservationMode) => solution.WithDocumentText(docId, text, preservationMode), + WorkspaceChangeKind.DocumentChanged, + isCodeDocument: true); } /// /// Call this method when the text of a document is updated in the host environment. /// protected internal void OnAdditionalDocumentTextChanged(DocumentId documentId, SourceText newText, PreservationMode mode) + { + OnAnyDocumentTextChanged( + documentId, + newText, + mode, + CheckAdditionalDocumentIsInCurrentSolution, + (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents + (solution, docId, text, preservationMode) => solution.WithAdditionalDocumentText(docId, text, preservationMode), + WorkspaceChangeKind.AdditionalDocumentChanged, + isCodeDocument: false); + } + + /// + /// When a s text is changed, we need to make sure all of the linked + /// files also have their content updated in the new solution before applying it to the + /// workspace to avoid the workspace having solutions with linked files where the contents + /// do not match. + /// + private void OnAnyDocumentTextChanged( + DocumentId documentId, + SourceText newText, + PreservationMode mode, + Action checkIsInCurrentSolution, + Func> getRelatedDocuments, + Func updateSolutionWithText, + WorkspaceChangeKind changeKind, + bool isCodeDocument) { using (_serializationLock.DisposableWait()) { - CheckAdditionalDocumentIsInCurrentSolution(documentId); + checkIsInCurrentSolution(documentId); - var oldSolution = this.CurrentSolution; - var newSolution = this.SetCurrentSolution(oldSolution.WithAdditionalDocumentText(documentId, newText, mode)); + var originalSolution = CurrentSolution; + var updatedSolution = CurrentSolution; + var previousSolution = updatedSolution; - var newDocument = newSolution.GetAdditionalDocument(documentId); + var linkedDocuments = getRelatedDocuments(updatedSolution, documentId); + var updatedDocumentIds = new List(); - this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.AdditionalDocumentChanged, oldSolution, newSolution, documentId: documentId); + foreach (var linkedDocument in linkedDocuments) + { + previousSolution = updatedSolution; + updatedSolution = updateSolutionWithText(updatedSolution, linkedDocument, newText, mode); + if (previousSolution != updatedSolution) + { + updatedDocumentIds.Add(linkedDocument); + } + } + + // In the case of linked files, we may have already updated all of the linked + // documents during an earlier call to this method. We may have no work to do here. + if (updatedDocumentIds.Count > 0) + { + var newSolution = SetCurrentSolution(updatedSolution); + + // Prior to the unification of the callers of this method, the + // OnAdditionalDocumentTextChanged method did not fire any sort of synchronous + // update notification event, so we preserve that behavior here. + if (isCodeDocument) + { + foreach (var updatedDocumentId in updatedDocumentIds) + { + var newDocument = newSolution.GetDocument(updatedDocumentId); + OnDocumentTextChanged(newDocument); + } + } + + foreach (var updatedDocumentInfo in updatedDocumentIds) + { + RaiseWorkspaceChangedEventAsync( + changeKind, + originalSolution, + newSolution, + documentId: updatedDocumentInfo); + } + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeEventArgs.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeEventArgs.cs index 5ec88b92a5a26..17d7c29240d36 100644 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeEventArgs.cs +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeEventArgs.cs @@ -4,11 +4,32 @@ namespace Microsoft.CodeAnalysis { + /// + /// The describing any kind of workspace change. + /// + /// + /// When linked files are edited, one document change event is fired per linked file. All of + /// these events contain the same , and they all contain the same + /// . This is so that we can trigger document change events on all + /// affected documents without reporting intermediate states in which the linked file contents + /// do not match. + /// public class WorkspaceChangeEventArgs : EventArgs { public WorkspaceChangeKind Kind { get; } + + /// + /// If linked documents are being changed, there may be multiple events with the same + /// and . + /// public Solution OldSolution { get; } + + /// + /// If linked documents are being changed, there may be multiple events with the same + /// and . + /// public Solution NewSolution { get; } + public ProjectId ProjectId { get; } public DocumentId DocumentId { get; } diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeKind.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeKind.cs index d21a78307e5b8..f6c08064a1c04 100644 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeKind.cs +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceChangeKind.cs @@ -68,6 +68,14 @@ public enum WorkspaceChangeKind /// /// A document in the current solution was changed. /// + /// + /// When linked files are edited, one event is fired per + /// linked file. All of these events contain the same OldSolution, and they all contain + /// the same NewSolution. This is so that we can trigger document change events on all + /// affected documents without reporting intermediate states in which the linked file + /// contents do not match. Each event does not represent + /// an incremental update from the previous event in this special case. + /// DocumentChanged = 12, /// diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs index 720e84fcccc5d..0b86dcca007e6 100644 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs @@ -17,5 +17,6 @@ public static class WorkspaceKind internal const string AnyCodeRoslynWorkspace = nameof(AnyCodeRoslynWorkspace); internal const string RemoteWorkspace = nameof(RemoteWorkspace); + internal const string RemoteTemporaryWorkspace = nameof(RemoteTemporaryWorkspace); } } diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 784e5d5cf9d74..74ab71906e6bf 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -607,8 +607,6 @@ - - diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs index db3b052ac75df..af5f62459c334 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.cs @@ -1,6 +1,7 @@ // 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; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading; @@ -536,14 +537,11 @@ await Assert.ThrowsAnyAsync(async () => public async Task TestSymbolTreeInfoSerialization() { var solution = GetSolution(WorkspaceKind.SingleClass); - var compilation = await solution.Projects.First().GetCompilationAsync(); - var assembly = compilation.GetSpecialType(SpecialType.System_Byte).ContainingAssembly; - ////var assembly = compilation.Assembly; + var project = solution.Projects.First(); // create symbol tree info from assembly - var version = VersionStamp.Create(); - var info = SymbolTreeInfo.CreateSourceSymbolTreeInfo( - solution, version, assembly, "", cancellationToken: CancellationToken.None); + var info = await SymbolTreeInfo.CreateSourceSymbolTreeInfoAsync( + project, Checksum.Null, cancellationToken: CancellationToken.None); using (var writerStream = new MemoryStream()) { @@ -555,7 +553,8 @@ public async Task TestSymbolTreeInfoSerialization() using (var readerStream = new MemoryStream(writerStream.ToArray())) using (var reader = ObjectReader.TryGetReader(readerStream)) { - var readInfo = SymbolTreeInfo.ReadSymbolTreeInfo_ForTestingPurposesOnly(reader); + var readInfo = SymbolTreeInfo.ReadSymbolTreeInfo_ForTestingPurposesOnly( + reader, Checksum.Null); info.AssertEquivalentTo(readInfo); } diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs index f6ae8152dc923..b719c9bb38521 100644 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs +++ b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs @@ -7,9 +7,12 @@ namespace Microsoft.CodeAnalysis.UnitTests.Persistence { [ExportWorkspaceService(typeof(IPersistentStorageService), "Test"), Shared] - public class TestPersistenceService : IPersistentStorageService + public class TestPersistenceService : IPersistentStorageService2 { public IPersistentStorage GetStorage(Solution solution) => NoOpPersistentStorage.Instance; + + public IPersistentStorage GetStorage(Solution solution, bool checkBranchId) + => NoOpPersistentStorage.Instance; } } \ No newline at end of file diff --git a/src/Workspaces/CoreTest/UtilityTest/StringEscapingTests.cs b/src/Workspaces/CoreTest/UtilityTest/StringEscapingTests.cs index 84dfe9bd4228b..6d7778f845c5f 100644 --- a/src/Workspaces/CoreTest/UtilityTest/StringEscapingTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/StringEscapingTests.cs @@ -1,8 +1,5 @@ // 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; -using System.IO; -using System.Threading; using Roslyn.Utilities; using Xunit; @@ -17,6 +14,7 @@ public void TestEscaping() Assert.Equal($"abc${(int)'?':X2}", "abc?".Escape('$', '?')); Assert.Equal($"abc${(int)'$':X2}", "abc$".Escape('$', '?')); Assert.Equal($"abc${(int)'?':X2}def${(int)'!':X2}", "abc?def!".Escape('$', '?', '!')); + Assert.Equal($"${(int)'?':X2}${(int)'!':X2}ab", "?!ab".Escape('$', '?', '!')); } [Fact] @@ -26,6 +24,7 @@ public void TestUnescaping() Assert.Equal("abc?", $"abc${(int)'?':X2}".Unescape('$')); Assert.Equal("abc$", $"abc${(int)'$':X2}".Unescape('$')); Assert.Equal("abc?def!", $"abc${(int)'?':X2}def${(int)'!':X2}".Unescape('$')); + Assert.Equal("?!ab", $"${(int)'?':X2}${(int)'!':X2}ab".Unescape('$')); } } } \ No newline at end of file diff --git a/src/Workspaces/CoreTest/WorkspaceTests/MSBuildWorkspaceTests.cs b/src/Workspaces/CoreTest/WorkspaceTests/MSBuildWorkspaceTests.cs index cef5a03d23ca3..7b32f25925d16 100644 --- a/src/Workspaces/CoreTest/WorkspaceTests/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceTests/MSBuildWorkspaceTests.cs @@ -580,6 +580,27 @@ public void TestOpenProject_VisualBasic_WithoutOutputPath() Assert.NotEmpty(project.OutputFilePath); } + [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + public void TestOpenProject_VisualBasic_WithLanguageVersion15_3() + { + CreateFiles(GetMultiProjectSolutionFiles() + .ReplaceFileElement(@"VisualBasicProject\VisualBasicProject.vbproj", "LangVersion", "15.3")); + + var project = MSBuildWorkspace.Create().OpenProjectAsync(GetSolutionFileName(@"VisualBasicProject\VisualBasicProject.vbproj")).Result; + Assert.Equal(VB.LanguageVersion.VisualBasic15_3, ((VB.VisualBasicParseOptions)project.ParseOptions).LanguageVersion); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + public void TestOpenProject_VisualBasic_WithLatestLanguageVersion() + { + CreateFiles(GetMultiProjectSolutionFiles() + .ReplaceFileElement(@"VisualBasicProject\VisualBasicProject.vbproj", "LangVersion", "Latest")); + + var project = MSBuildWorkspace.Create().OpenProjectAsync(GetSolutionFileName(@"VisualBasicProject\VisualBasicProject.vbproj")).Result; + Assert.Equal(VB.LanguageVersion.VisualBasic15_3, ((VB.VisualBasicParseOptions)project.ParseOptions).LanguageVersion); + Assert.Equal(VB.LanguageVersion.Latest, ((VB.VisualBasicParseOptions)project.ParseOptions).SpecifiedLanguageVersion); + } + [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] public void TestOpenProject_VisualBasic_WithoutAssemblyName() { diff --git a/src/Workspaces/CoreTestUtilities/TestFiles/VisualBasicProject_VisualBasicProject.vbproj b/src/Workspaces/CoreTestUtilities/TestFiles/VisualBasicProject_VisualBasicProject.vbproj index 4485d47d75b54..53e7391baea7a 100644 --- a/src/Workspaces/CoreTestUtilities/TestFiles/VisualBasicProject_VisualBasicProject.vbproj +++ b/src/Workspaces/CoreTestUtilities/TestFiles/VisualBasicProject_VisualBasicProject.vbproj @@ -38,6 +38,7 @@ On + 15 Binary diff --git a/src/Workspaces/Remote/Core/Services/TemporaryWorkspace.cs b/src/Workspaces/Remote/Core/Services/TemporaryWorkspace.cs index 1104ecc98a206..58d32c9c2fdc3 100644 --- a/src/Workspaces/Remote/Core/Services/TemporaryWorkspace.cs +++ b/src/Workspaces/Remote/Core/Services/TemporaryWorkspace.cs @@ -11,10 +11,8 @@ namespace Microsoft.CodeAnalysis.Remote /// internal class TemporaryWorkspace : Workspace { - public const string WorkspaceKind_TemporaryWorkspace = "TemporaryWorkspace"; - public TemporaryWorkspace(Solution solution) - : base(RoslynServices.HostServices, workspaceKind: TemporaryWorkspace.WorkspaceKind_TemporaryWorkspace) + : base(RoslynServices.HostServices, workspaceKind: WorkspaceKind.RemoteTemporaryWorkspace) { Options = Options.WithChangedOption(CacheOptions.RecoverableTreeLengthThreshold, 0); @@ -22,7 +20,7 @@ public TemporaryWorkspace(Solution solution) } public TemporaryWorkspace(SolutionInfo solutionInfo) - : base(RoslynServices.HostServices, workspaceKind: TemporaryWorkspace.WorkspaceKind_TemporaryWorkspace) + : base(RoslynServices.HostServices, workspaceKind: WorkspaceKind.RemoteTemporaryWorkspace) { Options = Options.WithChangedOption(CacheOptions.RecoverableTreeLengthThreshold, 0); diff --git a/src/Workspaces/Remote/Core/Services/TemporaryWorkspaceOptionsServiceFactory.cs b/src/Workspaces/Remote/Core/Services/TemporaryWorkspaceOptionsServiceFactory.cs index ec90535b93bd2..dc7820f29b9f5 100644 --- a/src/Workspaces/Remote/Core/Services/TemporaryWorkspaceOptionsServiceFactory.cs +++ b/src/Workspaces/Remote/Core/Services/TemporaryWorkspaceOptionsServiceFactory.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Remote { - [ExportWorkspaceServiceFactory(typeof(IOptionService), TemporaryWorkspace.WorkspaceKind_TemporaryWorkspace), Shared] + [ExportWorkspaceServiceFactory(typeof(IOptionService), WorkspaceKind.RemoteTemporaryWorkspace), Shared] internal class TemporaryWorkspaceOptionsServiceFactory : IWorkspaceServiceFactory { private readonly ImmutableArray> _providers; diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_DesignerAttributes.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_DesignerAttributes.cs index be3807b621789..f1cfbaa0565c2 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_DesignerAttributes.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_DesignerAttributes.cs @@ -1,11 +1,9 @@ // 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; -using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DesignerAttributes; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Extensions; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; namespace Microsoft.CodeAnalysis.Remote @@ -18,35 +16,16 @@ internal partial class CodeAnalysisService : IRemoteDesignerAttributeService /// /// This will be called by ServiceHub/JsonRpc framework /// - public async Task ScanDesignerAttributesAsync(DocumentId documentId) + public async Task ScanDesignerAttributesAsync(ProjectId projectId) { - using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_GetTodoCommentsAsync, documentId.ProjectId.DebugName, CancellationToken)) + using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_GetDesignerAttributesAsync, projectId.DebugName, CancellationToken)) { - try - { - var solution = await GetSolutionAsync().ConfigureAwait(false); - var document = solution.GetDocument(documentId); + var solution = await GetSolutionAsync().ConfigureAwait(false); + var project = solution.GetProject(projectId); + var data = await AbstractDesignerAttributeService.TryAnalyzeProjectInCurrentProcessAsync( + project, CancellationToken).ConfigureAwait(false); - var service = document.GetLanguageService(); - if (service != null) - { - // todo comment service supported - return await service.ScanDesignerAttributesAsync(document, CancellationToken).ConfigureAwait(false); - } - } - catch (IOException) - { - // stream to send over result has closed before we - // had chance to check cancellation - } - catch (OperationCanceledException) - { - // rpc connection has closed. - // this can happen if client side cancelled the - // operation - } - - return new DesignerAttributeResult(designerAttributeArgument: null, containsErrors: true, notApplicable: true); + return data.Values.ToArray(); } } } diff --git a/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj b/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj index d7bbf9fdc831e..db2cf36f964f2 100644 --- a/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj +++ b/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj @@ -11,6 +11,7 @@ netstandard1.3 portable-net45+win8;dotnet true + $(NoWarn);40057 diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/SemanticModelExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/SemanticModelExtensions.vb index 832a5baf62d0f..534bad45f99f8 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/SemanticModelExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/SemanticModelExtensions.vb @@ -80,17 +80,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Public Function GenerateNameForArgument(semanticModel As SemanticModel, - argument As ArgumentSyntax) As String - Dim result = GenerateNameForArgumentWorker(semanticModel, argument) + argument As ArgumentSyntax, + cancellationToken As CancellationToken) As String + Dim result = GenerateNameForArgumentWorker(semanticModel, argument, cancellationToken) Return If(String.IsNullOrWhiteSpace(result), s_defaultParameterName, result) End Function Private Function GenerateNameForArgumentWorker(semanticModel As SemanticModel, - argument As ArgumentSyntax) As String + argument As ArgumentSyntax, + cancellationToken As CancellationToken) As String If argument.IsNamed Then Return DirectCast(argument, SimpleArgumentSyntax).NameColonEquals.Name.Identifier.ValueText ElseIf Not argument.IsOmitted Then - Return semanticModel.GenerateNameForExpression(argument.GetExpression()) + Return semanticModel.GenerateNameForExpression( + argument.GetExpression(), capitalize:=False, cancellationToken:=cancellationToken) Else Return s_defaultParameterName End If @@ -103,7 +106,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Public Function GenerateNameForExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, - Optional capitalize As Boolean = False) As String + capitalize As Boolean, + cancellationToken As CancellationToken) As String ' Try to find a usable name node that we can use to name the ' parameter. If we have an expression that has a name as part of it ' then we try to use that part. @@ -122,40 +126,80 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions End If End While + ' there was nothing in the expression to signify a name. If we're in an argument + ' location, then try to choose a name based on the argument name. + Dim argumentName = TryGenerateNameForArgumentExpression( + semanticModel, expression, cancellationToken) + If argumentName IsNot Nothing Then + Return If(capitalize, argumentName.ToPascalCase(), argumentName.ToCamelCase()) + End If + ' Otherwise, figure out the type of the expression and generate a name from that ' instead. - Dim info = semanticModel.GetTypeInfo(expression) + Dim info = semanticModel.GetTypeInfo(expression, cancellationToken) ' If we can't determine the type, then fallback to some placeholders. Dim [type] = info.Type Return [type].CreateParameterName(capitalize) End Function + Private Function TryGenerateNameForArgumentExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, cancellationToken As CancellationToken) As String + Dim topExpression = expression.WalkUpParentheses() + If TypeOf topExpression.Parent Is ArgumentSyntax Then + Dim argument = DirectCast(topExpression.Parent, ArgumentSyntax) + Dim simpleArgument = TryCast(argument, SimpleArgumentSyntax) + + If simpleArgument?.NameColonEquals IsNot Nothing Then + Return simpleArgument.NameColonEquals.Name.Identifier.ValueText + End If + + Dim argumentList = TryCast(argument.Parent, ArgumentListSyntax) + If argumentList IsNot Nothing Then + Dim index = argumentList.Arguments.IndexOf(argument) + Dim member = TryCast(semanticModel.GetSymbolInfo(argumentList.Parent, cancellationToken).Symbol, IMethodSymbol) + If member IsNot Nothing AndAlso index < member.Parameters.Length Then + Dim parameter = member.Parameters(index) + If parameter.Type.TypeKind <> TypeKind.TypeParameter Then + Return parameter.Name + End If + End If + End If + End If + + Return Nothing + End Function + Public Function GenerateParameterNames(semanticModel As SemanticModel, arguments As ArgumentListSyntax, - Optional reservedNames As IEnumerable(Of String) = Nothing) As ImmutableArray(Of ParameterName) + reservedNames As IEnumerable(Of String), + cancellationToken As CancellationToken) As ImmutableArray(Of ParameterName) If arguments Is Nothing Then Return ImmutableArray(Of ParameterName).Empty End If - Return GenerateParameterNames(semanticModel, arguments.Arguments.ToList(), reservedNames) + Return GenerateParameterNames( + semanticModel, arguments.Arguments.ToList(), + reservedNames, cancellationToken) End Function Public Function GenerateParameterNames(semanticModel As SemanticModel, arguments As IList(Of ArgumentSyntax), - Optional reservedNames As IEnumerable(Of String) = Nothing) As ImmutableArray(Of ParameterName) + reservedNames As IEnumerable(Of String), + cancellationToken As CancellationToken) As ImmutableArray(Of ParameterName) reservedNames = If(reservedNames, SpecializedCollections.EmptyEnumerable(Of String)) Return semanticModel.GenerateParameterNames( arguments, - Function(s) Not reservedNames.Any(Function(n) CaseInsensitiveComparison.Equals(s, n))) + Function(s) Not reservedNames.Any(Function(n) CaseInsensitiveComparison.Equals(s, n)), + cancellationToken) End Function Public Function GenerateParameterNames(semanticModel As SemanticModel, arguments As IList(Of ArgumentSyntax), - canUse As Func(Of String, Boolean)) As ImmutableArray(Of ParameterName) + canUse As Func(Of String, Boolean), + cancellationToken As CancellationToken) As ImmutableArray(Of ParameterName) If arguments.Count = 0 Then Return ImmutableArray(Of ParameterName).Empty End If @@ -166,7 +210,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Select arg IsNot Nothing AndAlso arg.NameColonEquals IsNot Nothing Into ToList() - Dim parameterNames = arguments.Select(Function(a) semanticModel.GenerateNameForArgument(a)).ToList() + Dim parameterNames = arguments.Select(Function(a) semanticModel.GenerateNameForArgument(a, cancellationToken)).ToList() Return NameGenerator.EnsureUniqueness(parameterNames, isFixed, canUse). Select(Function(name, index) New ParameterName(name, isFixed(index))). ToImmutableArray() diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb index 7ee14d53fbe15..1578b13e26a32 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb @@ -188,6 +188,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return Contract.FailWithReturn(Of SyntaxList(Of StatementSyntax))("unknown statements container!") End Function + + Friend Function IsAsyncSupportedFunctionSyntax(node As SyntaxNode) As Boolean + Select Case node?.Kind() + Case _ + SyntaxKind.FunctionBlock, + SyntaxKind.SubBlock, + SyntaxKind.MultiLineFunctionLambdaExpression, + SyntaxKind.MultiLineSubLambdaExpression, + SyntaxKind.SingleLineFunctionLambdaExpression, + SyntaxKind.SingleLineSubLambdaExpression + Return True + End Select + Return False + End Function + Friend Function IsMultiLineLambda(node As SyntaxNode) As Boolean Return SyntaxFacts.IsMultiLineLambdaExpression(node.Kind()) diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb index 54263d427dae9..e01a23b9984d6 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb @@ -124,8 +124,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Function GenerateNameForExpression(semanticModel As SemanticModel, expression As SyntaxNode, - Optional capitalize As Boolean = False) As String Implements ISemanticFactsService.GenerateNameForExpression - Return semanticModel.GenerateNameForExpression(DirectCast(expression, ExpressionSyntax), capitalize) + capitalize As Boolean, + cancellationToken As CancellationToken) As String Implements ISemanticFactsService.GenerateNameForExpression + Return semanticModel.GenerateNameForExpression( + DirectCast(expression, ExpressionSyntax), capitalize, cancellationToken) End Function Public Function GetDeclaredSymbol(semanticModel As SemanticModel, token As SyntaxToken, cancellationToken As CancellationToken) As ISymbol Implements ISemanticFactsService.GetDeclaredSymbol diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb index 9618d49a3d092..9edca4167d799 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb @@ -53,7 +53,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return parent.TypeSwitch( Function(addRemoveHandlerStatement As AddRemoveHandlerStatementSyntax) InferTypeInAddRemoveHandlerStatementSyntax(addRemoveHandlerStatement, expression), - Function(argument As ArgumentSyntax) InferTypeInArgumentList(TryCast(argument.Parent, ArgumentListSyntax), argument), + Function(argument As ArgumentSyntax) InferTypeInArgument(argument), Function(arrayCreationExpression As ArrayCreationExpressionSyntax) InferTypeInArrayCreationExpression(arrayCreationExpression), Function(arrayRank As ArrayRankSpecifierSyntax) InferTypeInArrayRankSpecifier(), Function(arrayType As ArrayTypeSyntax) InferTypeInArrayType(arrayType), @@ -124,7 +124,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim parent = token.Parent Return parent.TypeSwitch( - Function(argument As ArgumentSyntax) InferTypeInArgumentList(TryCast(argument.Parent, ArgumentListSyntax), previousToken:=token), + Function(argument As ArgumentSyntax) InferTypeInArgument(argument, previousToken:=token), Function(argumentList As ArgumentListSyntax) InferTypeInArgumentList(argumentList, previousToken:=token), Function(arrayCreationExpression As ArrayCreationExpressionSyntax) InferTypeInArrayCreationExpression(arrayCreationExpression), Function(arrayRank As ArrayRankSpecifierSyntax) InferTypeInArrayRankSpecifier(), @@ -171,13 +171,42 @@ Namespace Microsoft.CodeAnalysis.VisualBasic SpecializedCollections.EmptyEnumerable(Of TypeInferenceInfo)()) End Function + Private Function InferTypeInArgument(argument As ArgumentSyntax, + Optional previousToken As SyntaxToken = Nothing) As IEnumerable(Of TypeInferenceInfo) + If TypeOf argument.Parent Is ArgumentListSyntax Then + Return InferTypeInArgumentList( + DirectCast(argument.Parent, ArgumentListSyntax), argument, previousToken) + End If + + If TypeOf argument.Parent Is TupleExpressionSyntax Then + Return InferTypeInTupleExpression( + DirectCast(argument.Parent, TupleExpressionSyntax), + DirectCast(argument, SimpleArgumentSyntax)) + End If + + Return SpecializedCollections.EmptyEnumerable(Of TypeInferenceInfo) + End Function + + Private Function InferTypeInTupleExpression(tupleExpression As TupleExpressionSyntax, + argument As SimpleArgumentSyntax) As IEnumerable(Of TypeInferenceInfo) + Dim index = tupleExpression.Arguments.IndexOf(argument) + Dim parentTypes = InferTypes(tupleExpression) + + Return parentTypes.Select(Function(TypeInfo) TypeInfo.InferredType). + OfType(Of INamedTypeSymbol)(). + Where(Function(namedType) namedType.IsTupleType AndAlso index < namedType.TupleElements.Length). + Select(Function(tupleType) New TypeInferenceInfo(tupleType.TupleElements(index).Type)) + End Function + Private Function InferTypeInArgumentList(argumentList As ArgumentListSyntax, - Optional argumentOpt As ArgumentSyntax = Nothing, Optional previousToken As SyntaxToken = Nothing) As IEnumerable(Of TypeInferenceInfo) + Optional argumentOpt As ArgumentSyntax = Nothing, + Optional previousToken As SyntaxToken = Nothing) As IEnumerable(Of TypeInferenceInfo) If argumentList Is Nothing Then Return SpecializedCollections.EmptyEnumerable(Of TypeInferenceInfo)() End If If argumentList.Parent IsNot Nothing Then + If argumentList.IsParentKind(SyntaxKind.ArrayCreationExpression) Then Return SpecializedCollections.SingletonEnumerable(New TypeInferenceInfo(Compilation.GetSpecialType(SpecialType.System_Int32))) ElseIf argumentList.IsParentKind(SyntaxKind.InvocationExpression) Then