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..a09755352405d 100644 --- a/build/Targets/Imports.targets +++ b/build/Targets/Imports.targets @@ -41,6 +41,11 @@ false $(OutputPath)Dlls\$(MSBuildProjectName)\ + + + + + @@ -133,6 +138,8 @@ true false false + + $(NuGetPackageRoot)\Microsoft.DotNet.IBCMerge\$(MicrosoftDotNetIBCMerge)\lib\net45\ibcmerge.exe @@ -423,16 +430,24 @@ - + + + + ConsoleToMSBuild="true" + Condition="Exists('$(IbcMergePath)')"> @@ -440,22 +455,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/BasicCommandLineTest.vbproj b/src/Compilers/VisualBasic/Test/CommandLine/BasicCommandLineTest.vbproj index 26b6b5e233155..3e1744bafea98 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/BasicCommandLineTest.vbproj +++ b/src/Compilers/VisualBasic/Test/CommandLine/BasicCommandLineTest.vbproj @@ -12,6 +12,7 @@ false v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest 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/BasicCompilerEmitTest.vbproj b/src/Compilers/VisualBasic/Test/Emit/BasicCompilerEmitTest.vbproj index b3b892af154ae..b63d0f9d64a4f 100644 --- a/src/Compilers/VisualBasic/Test/Emit/BasicCompilerEmitTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Emit/BasicCompilerEmitTest.vbproj @@ -11,6 +11,7 @@ Roslyn.Compilers.VisualBasic.Emit.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb index 05b2da99f587c..63170568905e5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb +++ b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb @@ -7863,6 +7863,336 @@ BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Strin End Sub + + Public Sub TupleCTypeNullableConversionWithTypelessTuple() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertNoDiagnostics() + CompileAndVerify(comp, expectedOutput:="(1, )") + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim node = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().Single() + Assert.Equal("(1, Nothing)", node.ToString()) + Assert.Null(model.GetTypeInfo(node).Type) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningNullableTuple, model.GetConversion(node).Kind) + + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).Type.ToTestDisplayString()) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).ConvertedType.ToTestDisplayString()) + + End Sub + + + Public Sub TupleDirectCastNullableConversionWithTypelessTuple() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertNoDiagnostics() + CompileAndVerify(comp, expectedOutput:="(1, )") + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim node = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().Single() + Assert.Equal("(1, Nothing)", node.ToString()) + Assert.Null(model.GetTypeInfo(node).Type) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningNullableTuple, model.GetConversion(node).Kind) + + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).Type.ToTestDisplayString()) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).ConvertedType.ToTestDisplayString()) + + End Sub + + + Public Sub TupleTryCastNullableConversionWithTypelessTuple() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertTheseDiagnostics( +BC30792: 'TryCast' operand must be reference type, but '(Integer, String)?' is a value type. + Dim x As (Integer, String)? = TryCast((1, Nothing), (Integer, String)?) + ~~~~~~~~~~~~~~~~~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim node = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().Single() + Assert.Equal("(1, Nothing)", node.ToString()) + Assert.Null(model.GetTypeInfo(node).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(node).Kind) + + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).Type.ToTestDisplayString()) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node.Parent).ConvertedType.ToTestDisplayString()) + + End Sub + + + Public Sub TupleTryCastNullableConversionWithTypelessTuple2() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs) + + comp.AssertTheseDiagnostics( +BC30311: Value of type '(Integer, Object)' cannot be converted to 'C(Of Integer, T)'. + Dim x = TryCast((0, Nothing), C(Of Integer, T)) + ~~~~~~~~~~~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim node = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().Single() + Assert.Equal("(0, Nothing)", node.ToString()) + Assert.Null(model.GetTypeInfo(node).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(node).Kind) + + Assert.Equal("C(Of System.Int32, T)", model.GetTypeInfo(node.Parent).Type.ToTestDisplayString()) + Assert.Equal("C(Of System.Int32, T)", model.GetTypeInfo(node.Parent).ConvertedType.ToTestDisplayString()) + + End Sub + + + Public Sub TupleImplicitNullableConversionWithTypelessTuple() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertNoDiagnostics() + CompileAndVerify(comp, expectedOutput:="(1, )") + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim node = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().Single() + Assert.Equal("(1, Nothing)", node.ToString()) + Assert.Null(model.GetTypeInfo(node).Type) + Assert.Equal("System.Nullable(Of (System.Int32, System.String))", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningNullableTuple, model.GetConversion(node).Kind) + + End Sub + + + Public Sub ImplicitConversionOnTypelessTupleWithUserConversion() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertTheseDiagnostics( +BC30311: Value of type '(Integer, Object)' cannot be converted to 'C?'. + Dim y As C? = (2, Nothing) + ~~~~~~~~~~~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim firstTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(0) + Assert.Equal("(1, Nothing)", firstTuple.ToString()) + Assert.Null(model.GetTypeInfo(firstTuple).Type) + Assert.Equal("C", model.GetTypeInfo(firstTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.Narrowing Or ConversionKind.UserDefined, model.GetConversion(firstTuple).Kind) + + Dim secondTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(1) + Assert.Equal("(2, Nothing)", secondTuple.ToString()) + Assert.Null(model.GetTypeInfo(secondTuple).Type) + Assert.Equal("System.Nullable(Of C)", model.GetTypeInfo(secondTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.DelegateRelaxationLevelNone, model.GetConversion(secondTuple).Kind) + + End Sub + + + Public Sub DirectCastOnTypelessTupleWithUserConversion() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertTheseDiagnostics( +BC30311: Value of type '(Integer, Object)' cannot be converted to 'C'. + Dim x = DirectCast((1, Nothing), C) + ~~~~~~~~~~~~ +BC30311: Value of type '(Integer, Object)' cannot be converted to 'C?'. + Dim y = DirectCast((2, Nothing), C?) + ~~~~~~~~~~~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim firstTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(0) + Assert.Equal("(1, Nothing)", firstTuple.ToString()) + Assert.Null(model.GetTypeInfo(firstTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(firstTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(firstTuple).Kind) + + Dim secondTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(1) + Assert.Equal("(2, Nothing)", secondTuple.ToString()) + Assert.Null(model.GetTypeInfo(secondTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(secondTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(secondTuple).Kind) + + End Sub + + + Public Sub TryCastOnTypelessTupleWithUserConversion() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertTheseDiagnostics( +BC30792: 'TryCast' operand must be reference type, but 'C' is a value type. + Dim x = TryCast((1, Nothing), C) + ~ +BC30792: 'TryCast' operand must be reference type, but 'C?' is a value type. + Dim y = TryCast((2, Nothing), C?) + ~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim firstTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(0) + Assert.Equal("(1, Nothing)", firstTuple.ToString()) + Assert.Null(model.GetTypeInfo(firstTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(firstTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(firstTuple).Kind) + + Dim secondTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(1) + Assert.Equal("(2, Nothing)", secondTuple.ToString()) + Assert.Null(model.GetTypeInfo(secondTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(secondTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(secondTuple).Kind) + + End Sub + + + Public Sub CTypeOnTypelessTupleWithUserConversion() + Dim comp = CreateCompilationWithMscorlibAndVBRuntime( + + +, additionalRefs:=s_valueTupleRefs, options:=TestOptions.DebugExe) + + comp.AssertTheseDiagnostics( +BC30311: Value of type '(Integer, Object)' cannot be converted to 'C?'. + Dim y = CType((2, Nothing), C?) + ~~~~~~~~~~~~ + ) + + Dim tree = comp.SyntaxTrees.Single() + Dim model = comp.GetSemanticModel(tree) + Dim firstTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(0) + Assert.Equal("(1, Nothing)", firstTuple.ToString()) + Assert.Null(model.GetTypeInfo(firstTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(firstTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(firstTuple).Kind) + + Dim secondTuple = tree.GetRoot().DescendantNodes().OfType(Of TupleExpressionSyntax)().ElementAt(1) + Assert.Equal("(2, Nothing)", secondTuple.ToString()) + Assert.Null(model.GetTypeInfo(secondTuple).Type) + Assert.Equal("(System.Int32, System.Object)", model.GetTypeInfo(secondTuple).ConvertedType.ToTestDisplayString()) + Assert.Equal(ConversionKind.WideningTuple, model.GetConversion(secondTuple).Kind) + + End Sub + Public Sub TupleTargetTypeLambda() @@ -8261,6 +8591,35 @@ End Module End Sub + + + + Public Sub GetSymbolInfo_01() + Dim source = " + Class C + Shared Sub Main() + Dim x1 = (Alice:=1, ""hello"") + + Dim Alice = x1.Alice + End Sub +End Class + " + + Dim tree = Parse(source, options:=TestOptions.Regular) + Dim comp = CreateCompilationWithMscorlib(tree) + + Dim model = comp.GetSemanticModel(tree, ignoreAccessibility:=False) + Dim nodes = tree.GetCompilationUnitRoot().DescendantNodes() + + Dim nc = nodes.OfType(Of NameColonEqualsSyntax)().ElementAt(0) + + Dim sym = model.GetSymbolInfo(nc.Name) + + Assert.Equal("Alice", sym.Symbol.Name) + Assert.Equal(SymbolKind.Field, sym.Symbol.Kind) ' Incorrectly returns Local + Assert.Equal(nc.Name.GetLocation(), sym.Symbol.Locations(0)) ' Incorrect location + End Sub + Public Sub RetargetTupleErrorType() Dim libComp = CreateCompilationWithMscorlibAndVBRuntime( 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/Emit/EditAndContinue/EditAndContinueTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb index e7dda329a77fd..7542cc44b18b0 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb @@ -18,6 +18,107 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Public Class EditAndContinueTests Inherits EditAndContinueTestBase + + Public Sub SemanticErrors_MethodBody() + Dim source0 = MarkedSource(" +Class C + Shared Sub E() + Dim x As Integer = 1 + System.Console.WriteLine(x) + End Sub + + Shared Sub G() + System.Console.WriteLine(1) + End Sub +End Class +") + Dim source1 = MarkedSource(" +Class C + Shared Sub E() + Dim x = Unknown(2) + System.Console.WriteLine(x) + End Sub + + Shared Sub G() + System.Console.WriteLine(2) + End Sub +End Class +") + Dim compilation0 = CreateCompilationWithMscorlib(source0.Tree, options:=ComSafeDebugDll) + Dim compilation1 = compilation0.WithSource(source1.Tree) + + Dim e0 = compilation0.GetMember(Of MethodSymbol)("C.E") + Dim e1 = compilation1.GetMember(Of MethodSymbol)("C.E") + Dim g0 = compilation0.GetMember(Of MethodSymbol)("C.G") + Dim g1 = compilation1.GetMember(Of MethodSymbol)("C.G") + + Dim v0 = CompileAndVerify(compilation0) + Dim md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData) + Dim generation0 = EmitBaseline.CreateInitialBaseline(md0, AddressOf v0.CreateSymReader().GetEncMethodDebugInfo) + + ' Semantic errors are reported only for the bodies of members being emitted. + Dim diffError = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(New SemanticEdit(SemanticEditKind.Update, e0, e1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables:=True))) + + diffError.EmitResult.Diagnostics.Verify( + Diagnostic(ERRID.ERR_NameNotDeclared1, "Unknown").WithArguments("Unknown").WithLocation(4, 17)) + + Dim diffGood = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(New SemanticEdit(SemanticEditKind.Update, g0, g1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables:=True))) + + diffGood.EmitResult.Diagnostics.Verify() + diffGood.VerifyIL("C.G", " +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""Sub System.Console.WriteLine(Integer)"" + IL_0007: nop + IL_0008: ret +}") + End Sub + + + Public Sub SemanticErrors_Declaration() + Dim source0 = MarkedSource(" +Class C + Sub G() + System.Console.WriteLine(1) + End Sub +End Class +") + Dim source1 = MarkedSource(" +Class C + Sub G() + System.Console.WriteLine(1) + End Sub +End Class + +Class Bad + Inherits Bad +End Class +") + Dim compilation0 = CreateCompilationWithMscorlib(source0.Tree, options:=ComSafeDebugDll) + Dim compilation1 = compilation0.WithSource(source1.Tree) + + Dim g0 = compilation0.GetMember(Of MethodSymbol)("C.G") + Dim g1 = compilation1.GetMember(Of MethodSymbol)("C.G") + + Dim v0 = CompileAndVerify(compilation0) + Dim md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData) + Dim generation0 = EmitBaseline.CreateInitialBaseline(md0, AddressOf v0.CreateSymReader().GetEncMethodDebugInfo) + + Dim diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(New SemanticEdit(SemanticEditKind.Update, g0, g1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables:=True))) + + diff.EmitResult.Diagnostics.Verify( + Diagnostic(ERRID.ERR_TypeInItsInheritsClause1, "Bad").WithArguments("Bad").WithLocation(9, 12)) + End Sub + Public Sub ModifyMethod_WithTuples() Dim source0 = @@ -5183,5 +5284,8 @@ End Class") } ") End Sub + + + End Class End Namespace 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..44e47678f3728 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj @@ -11,6 +11,7 @@ Roslyn.Compilers.VisualBasic.Semantic.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest @@ -107,6 +108,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/Test/Symbol/BasicCompilerSymbolTest.vbproj b/src/Compilers/VisualBasic/Test/Symbol/BasicCompilerSymbolTest.vbproj index 1659ea54b14cc..d522cbc0c274d 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/BasicCompilerSymbolTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Symbol/BasicCompilerSymbolTest.vbproj @@ -11,6 +11,7 @@ Roslyn.Compilers.VisualBasic.Symbol.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/Compilers/VisualBasic/Test/Syntax/BasicCompilerSyntaxTest.vbproj b/src/Compilers/VisualBasic/Test/Syntax/BasicCompilerSyntaxTest.vbproj index cbfc5084f0e54..d2a065d42983b 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/BasicCompilerSyntaxTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Syntax/BasicCompilerSyntaxTest.vbproj @@ -11,6 +11,7 @@ Roslyn.Compilers.VisualBasic.Syntax.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest 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 cc0cccb814266..24d00d4664012 100644 --- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj +++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj @@ -146,6 +146,7 @@ Library Microsoft.CodeAnalysis.Editor.CSharp.UnitTests Roslyn.Services.Editor.CSharp.UnitTests + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest @@ -294,7 +295,6 @@ - @@ -307,8 +307,8 @@ - - + + @@ -397,6 +397,13 @@ + + + + + + + @@ -432,13 +439,13 @@ - - - - - - - + + + + + + + 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/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.cs index 2fe206b531079..d868634b125b9 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.cs @@ -1363,7 +1363,7 @@ await TestInRegularAndScriptAsync( @"internal interface ILanguageServiceHost { /// - /// Gets the active workspace project context that provides access to the language service for the active configured project. + /// Sets the active workspace project context that provides access to the language service for the active configured project. /// /// /// An value that provides access to the language service for the active configured project. @@ -1376,11 +1376,11 @@ object [||]ActiveProjectContext @"internal interface ILanguageServiceHost { /// - /// Gets the active workspace project context that provides access to the language service for the active configured project. + /// Sets the active workspace project context that provides access to the language service for the active configured project. /// - /// + /// /// An value that provides access to the language service for the active configured project. - /// + /// void SetActiveProjectContext(object value); }", ignoreTrivia: false); } @@ -1393,7 +1393,7 @@ await TestInRegularAndScriptAsync( @"internal interface ILanguageServiceHost { /// - /// Gets the active workspace project context that provides access to the language service for the active configured project. + /// Gets or sets the active workspace project context that provides access to the language service for the active configured project. /// /// /// An value that provides access to the language service for the active configured project. @@ -1406,17 +1406,246 @@ object [||]ActiveProjectContext @"internal interface ILanguageServiceHost { /// - /// Gets the active workspace project context that provides access to the language service for the active configured project. + /// Gets or sets the active workspace project context that provides access to the language service for the active configured project. /// /// /// An value that provides access to the language service for the active configured project. /// object GetActiveProjectContext(); + + /// + /// Gets or sets the active workspace project context that provides access to the language service for the active configured project. + /// + /// + /// An value that provides access to the language service for the active configured project. + /// + void SetActiveProjectContext(object value); +}", ignoreTrivia: false); + } + + [WorkItem(18234, "https://github.com/dotnet/roslyn/issues/18234")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestDocumentationComment4() + { + await TestInRegularAndScriptAsync( +@"internal interface ILanguageServiceHost +{ + /// + /// Sets . + /// + /// + object [||]ActiveProjectContext + { + set; + } +} +internal struct AStruct +{ + /// + private int x; +}", +@"internal interface ILanguageServiceHost +{ + /// + /// Sets . + /// + /// + void SetActiveProjectContext(object value); +} +internal struct AStruct +{ + /// + private int x; +}", ignoreTrivia: false); + } + + [WorkItem(18234, "https://github.com/dotnet/roslyn/issues/18234")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestDocumentationComment5() + { + await TestInRegularAndScriptAsync( +@"internal interface ILanguageServiceHost +{ + /// + /// Gets or sets . + /// + /// + object [||]ActiveProjectContext + { + get; set; + } +} +internal struct AStruct +{ + /// + private int x; +}", +@"internal interface ILanguageServiceHost +{ + /// + /// Gets or sets . + /// + /// + object GetActiveProjectContext(); + + /// + /// Gets or sets . + /// + /// void SetActiveProjectContext(object value); +} +internal struct AStruct +{ + /// + private int x; +}", ignoreTrivia: false); + } + + [WorkItem(18234, "https://github.com/dotnet/roslyn/issues/18234")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestDocumentationComment6() + { + await TestInRegularAndScriptAsync( +@"internal interface ISomeInterface +{ + /// + ISomeInterface [||]Context + { + set; + } +} +internal struct AStruct +{ + /// + private int x; +}", +@"internal interface ISomeInterface +{ + /// + void SetContext(ISomeInterface value); +} +internal struct AStruct +{ + /// + private int x; +}", ignoreTrivia: false); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestWithDirectives1() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int [||]Prop + { + get + { +#if true + return 0; +#else + return 1; +#endif + } + } +}", + @"class C +{ + private int GetProp() + { +#if true + return 0; +#else + return 1; +#endif + } +}", ignoreTrivia: false); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestWithDirectives2() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int [||]Prop + { + get + { +#if true + return 0; +#else + return 1; +#endif + } + } +}", + @"class C +{ + private int GetProp() => +#if true + 0; +#else + return 1; +#endif +}", ignoreTrivia: false, + options: PreferExpressionBodiedMethods); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestWithDirectives3() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int [||]Prop => +#if true + 0; +#else + 1; +#endif +}", +@"class C +{ + private int GetProp() => +#if true + 0; +#else + 1; +#endif }", ignoreTrivia: false); } + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplacePropertyWithMethods)] + public async Task TestWithDirectives4() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int [||]Prop => +#if true + 0; +#else + 1; +#endif +}", +@"class C +{ + private int GetProp() => +#if true + 0; +#else + 1; +#endif +}", ignoreTrivia: false, + options: PreferExpressionBodiedMethods); + } + private IDictionary PreferExpressionBodiedMethods => OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement)); } -} \ 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 bd8d373b97b57..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; @@ -306,5 +308,40 @@ class D : C internal override void [|m|]() { } }", new TestParameters(options: MethodNamesArePascalCase)); } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + [WorkItem(19106, "https://github.com/dotnet/roslyn/issues/19106")] + public async Task TestMissingOnSymbolsWithNoName() + { + await TestMissingInRegularAndScriptAsync( +@" +namespace Microsoft.CodeAnalysis.Host +{ + 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/NamingStyles/NamingStylesTests_OptionSets.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests_OptionSets.cs index c28a080ee3c88..8ccb9885166c1 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests_OptionSets.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests_OptionSets.cs @@ -21,9 +21,12 @@ public partial class NamingStylesTests : AbstractCSharpDiagnosticProviderBasedUs private IDictionary ParameterNamesAreCamelCase => Options(new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), ParameterNamesAreCamelCaseOption()); - private IDictionary PropertyNamesArePascalCase => + private IDictionary PropertyNamesArePascalCase => Options(new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), PropertyNamesArePascalCaseOption()); + private IDictionary InterfaceNamesStartWithI => + Options(new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), InterfacesNamesStartWithIOption()); + private IDictionary Options(OptionKey option, object value) { var options = new Dictionary @@ -117,14 +120,14 @@ private NamingStylePreferences ParameterNamesAreCamelCaseOption() NamingStyleID = namingStyle.ID, EnforcementLevel = DiagnosticSeverity.Error }; - - var info = new NamingStylePreferences( + + var info = new NamingStylePreferences( ImmutableArray.Create(symbolSpecification), ImmutableArray.Create(namingStyle), ImmutableArray.Create(namingRule)); return info; - } + } private NamingStylePreferences PropertyNamesArePascalCaseOption() { @@ -157,5 +160,37 @@ private NamingStylePreferences PropertyNamesArePascalCaseOption() return info; } + + private NamingStylePreferences InterfacesNamesStartWithIOption() + { + var symbolSpecification = new SymbolSpecification( + null, + "Name", + ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(TypeKind.Interface)), + ImmutableArray.Empty, + ImmutableArray.Empty); + + var namingStyle = new NamingStyle( + Guid.NewGuid(), + capitalizationScheme: Capitalization.PascalCase, + name: "Name", + prefix: "I", + suffix: "", + wordSeparator: ""); + + var namingRule = new SerializableNamingRule() + { + SymbolSpecificationID = symbolSpecification.ID, + NamingStyleID = namingStyle.ID, + EnforcementLevel = DiagnosticSeverity.Error + }; + + var info = new NamingStylePreferences( + ImmutableArray.Create(symbolSpecification), + ImmutableArray.Create(namingStyle), + ImmutableArray.Create(namingRule)); + + return info; + } } } \ 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/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 15845eed701aa..288f7948c828d 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -496,8 +496,8 @@ public static void Main() } } - [Fact] - public async Task AnalyzeDocumentAsync_SemanticError_Change() + [Fact, WorkItem(10683, "https://github.com/dotnet/roslyn/issues/10683")] + public async Task AnalyzeDocumentAsync_SemanticErrorInMethodBody_Change() { string source1 = @" class C @@ -521,6 +521,46 @@ public static void Main() "; var analyzer = new CSharpEditAndContinueAnalyzer(); + using (var workspace = TestWorkspace.CreateCSharp(source1)) + { + var documentId = workspace.CurrentSolution.Projects.First().Documents.First().Id; + var oldSolution = workspace.CurrentSolution; + var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + + var baseActiveStatements = ImmutableArray.Create(); + var result = await analyzer.AnalyzeDocumentAsync(oldSolution, baseActiveStatements, newSolution.GetDocument(documentId), default(CancellationToken)); + + Assert.True(result.HasChanges); + + // no declaration errors (error in method body is only reported when emitting): + Assert.False(result.HasChangesAndErrors); + Assert.False(result.HasChangesAndCompilationErrors); + } + } + + [Fact, WorkItem(10683, "https://github.com/dotnet/roslyn/issues/10683")] + public async Task AnalyzeDocumentAsync_SemanticErrorInDeclaration_Change() + { + string source1 = @" +class C +{ + public static void Main(Bar x) + { + System.Console.WriteLine(1); + } +} +"; + string source2 = @" +class C +{ + public static void Main(Bar x) + { + System.Console.WriteLine(2); + } +} +"; + var analyzer = new CSharpEditAndContinueAnalyzer(); + using (var workspace = TestWorkspace.CreateCSharp(source1)) { var documentId = workspace.CurrentSolution.Projects.First().Documents.First().Id; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs index 11943c1ccf4e9..877df16e72d2e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue; +using Microsoft.CodeAnalysis.Test.Utilities; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { @@ -58,16 +59,34 @@ internal static void VerifySemanticDiagnostics( this EditScript editScript, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - VerifySemantics(editScript, ActiveStatementsDescription.Empty, null, expectedDiagnostics); + VerifySemanticDiagnostics(editScript, null, expectedDiagnostics); + } + + internal static void VerifySemanticDiagnostics( + this EditScript editScript, + DiagnosticDescription expectedDeclarationError, + params RudeEditDiagnosticDescription[] expectedDiagnostics) + { + VerifySemantics(editScript, ActiveStatementsDescription.Empty, null, expectedDeclarationError, expectedDiagnostics); + } + + internal static void VerifySemantics( + this EditScript editScript, + ActiveStatementsDescription activeStatements, + SemanticEditDescription[] expectedSemanticEdits, + params RudeEditDiagnosticDescription[] expectedDiagnostics) + { + VerifySemantics(editScript, activeStatements, expectedSemanticEdits, null, expectedDiagnostics); } internal static void VerifySemantics( this EditScript editScript, ActiveStatementsDescription activeStatements, SemanticEditDescription[] expectedSemanticEdits, + DiagnosticDescription expectedDeclarationError, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - VerifySemantics(editScript, activeStatements, null, null, expectedSemanticEdits, expectedDiagnostics); + VerifySemantics(editScript, activeStatements, null, null, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics); } internal static void VerifySemantics( @@ -76,6 +95,7 @@ internal static void VerifySemantics( IEnumerable additionalOldSources, IEnumerable additionalNewSources, SemanticEditDescription[] expectedSemanticEdits, + DiagnosticDescription expectedDeclarationError, params RudeEditDiagnosticDescription[] expectedDiagnostics) { CSharpEditAndContinueTestHelpers.Instance.VerifySemantics( @@ -84,6 +104,7 @@ internal static void VerifySemantics( additionalOldSources, additionalNewSources, expectedSemanticEdits, + expectedDeclarationError, expectedDiagnostics); } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditStatementTests.cs index 50a769bea0d33..152b61439997e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditStatementTests.cs @@ -4416,7 +4416,7 @@ void F() } // Add corresponding test to VB - [WpfFact(Skip = "TODO")] + [Fact(Skip = "TODO")] public void Lambdas_Update_Signature_CustomModifiers1() { var delegateSource = @" @@ -4502,6 +4502,43 @@ void F() edits.VerifySemanticDiagnostics(); } + [Fact] + public void Lambdas_Signature_SemanticErrors() + { + var src1 = @" +using System; + +class C +{ + void G(Func f) {} + + void F() + { + G(a => 1); + } +} +"; + var src2 = @" +using System; + +class C +{ + void G(Func f) {} + + void F() + { + G(a => 2); + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + // (6,17): error CS0246: The type or namespace name 'Unknown' could not be found (are you missing a using directive or an assembly reference?) + // void G(Func f) {} + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Unknown").WithArguments("Unknown").WithLocation(6, 17)); + } + [Fact] public void Lambdas_Update_DelegateType1() { @@ -7530,6 +7567,7 @@ static IEnumerable F() null, null, null, + null, new[] { Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "static IEnumerable F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute") @@ -8111,12 +8149,13 @@ static async Task F() var edits = GetTopEdits(src1, src2); CSharpEditAndContinueTestHelpers.InstanceMinAsync.VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - null, - null, - null, - new[] + editScript: edits, + activeStatements: ActiveStatementsDescription.Empty, + additionalNewSources: null, + additionalOldSources: null, + expectedSemanticEdits: null, + expectedDeclarationError: null, + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "static async Task F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute") }); @@ -8159,6 +8198,43 @@ static async Task F() null); } + [Fact] + public void SemanticError_AwaitInPropertyAccessor() + { + string src1 = @" +using System.Threading.Tasks; + +class C +{ + public Task P + { + get + { + await Task.Delay(1); + return 1; + } + } +} +"; + string src2 = @" +using System.Threading.Tasks; + +class C +{ + public Task P + { + get + { + await Task.Delay(2); + return 1; + } + } +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics(); + } + #endregion #region Out Var diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditTopLevelTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditTopLevelTests.cs index 926c182fa6e7b..d2aa6170d44f5 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditTopLevelTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/RudeEditTopLevelTests.cs @@ -3853,13 +3853,14 @@ public void StaticCtor_Partial_Delete() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single()) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3874,13 +3875,14 @@ public void InstanceCtor_Partial_DeletePrivate() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3895,13 +3897,14 @@ public void InstanceCtor_Partial_DeletePublic() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3916,11 +3919,12 @@ public void InstanceCtor_Partial_DeletePrivateToPublic() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - null, - Diagnostic(RudeEditKind.Delete, "partial class C", FeaturesResources.constructor)); + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: null, + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", FeaturesResources.constructor) }, + expectedDeclarationError: null); } [Fact] @@ -3935,13 +3939,14 @@ public void StaticCtor_Partial_Insert() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3956,13 +3961,14 @@ public void InstanceCtor_Partial_InsertPublic() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3977,13 +3983,14 @@ public void InstanceCtor_Partial_InsertPrivate() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -3998,13 +4005,14 @@ public void InstanceCtor_Partial_InsertInternal() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - new[] + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + }, + expectedDeclarationError: null); } [Fact] @@ -4019,11 +4027,12 @@ public void InstanceCtor_Partial_InsertPrivateToPublic() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - null, - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()")); + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits: null, + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") }, + expectedDeclarationError: null); } [Fact] @@ -4038,11 +4047,12 @@ public void InstanceCtor_Partial_InsertPrivateToInternal() var edits = GetTopEdits(srcA1, srcA2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { srcB1 }, - new[] { srcB2 }, - null, - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()")); + activeStatements: ActiveStatementsDescription.Empty, + additionalOldSources: new[] { srcB1 }, + additionalNewSources: new[] { srcB2 }, + expectedSemanticEdits:null, + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()") }, + expectedDeclarationError: null); } [Fact] @@ -4313,6 +4323,76 @@ public void Insert_ExternConstruct() Diagnostic(RudeEditKind.InsertExtern, "public extern C()", FeaturesResources.constructor)); } + [Fact(Skip = "https://github.com/dotnet/roslyn/pull/18940")] + public void ParameterlessConstructor_SemanticError_Delete1() + { + string src1 = @" +class C +{ + D() {} +} +"; + string src2 = @" +class C +{ +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/pull/18940")] + public void ParameterlessConstructor_SemanticError_Delete_OutsideOfClass1() + { + string src1 = @" +C() {} +"; + string src2 = @" +"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Constructor_SemanticError_Partial() + { + string src1 = @" +partial class C +{ + partial void C(int x); +} + +partial class C +{ + partial void C(int x) + { + System.Console.WriteLine(1); + } +} +"; + string src2 = @" +partial class C +{ + partial void C(int x); +} + +partial class C +{ + partial void C(int x) + { + System.Console.WriteLine(2); + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + // (4,18): error CS0542: 'C': member names cannot be the same as their enclosing type + // partial void C(int x); + Diagnostic(ErrorCode.ERR_MemberNameSameAsType, "C").WithArguments("C").WithLocation(4, 18)); + } + #endregion #region Fields and Properties with Initializers @@ -5622,6 +5702,40 @@ class C }); } + [Fact] + public void PropertyWithInitializer_SemanticError_Partial() + { + string src1 = @" +partial class C +{ + partial int P => 1; +} + +partial class C +{ + partial int P => 1; +} +"; + string src2 = @" +partial class C +{ + partial int P => 1; +} + +partial class C +{ + partial int P => 1; + + public C() { } +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics( + // (4,17): error CS0753: Only methods, classes, structs, or interfaces may be partial + // partial int P => 1; + Diagnostic(ErrorCode.ERR_PartialMethodOnlyMethods, "P").WithLocation(4, 17)); + } + #endregion #region Fields diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index a8e9136120885..379b276f6592c 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/UseExpressionBody/UseExpressionBodyForAccessorsTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForAccessorsAnalyzerTests.cs similarity index 81% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForAccessorsTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForAccessorsAnalyzerTests.cs index 1e33d62be4ee3..e9b450dbbf328 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForAccessorsTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForAccessorsAnalyzerTests.cs @@ -18,8 +18,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody public class UseExpressionBodyForAccessorsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForAccessorsDiagnosticAnalyzer(), - new UseExpressionBodyForAccessorsCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => OptionsSet( @@ -33,8 +32,11 @@ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProvider SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement), SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement)); - private IDictionary UseBlockBody => - Option(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement); + private IDictionary UseBlockBodyIncludingPropertiesAndIndexers => + OptionsSet( + SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement), + SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement), + SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.NeverWithSuggestionEnforcement)); [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestUseExpressionBody1() @@ -60,9 +62,9 @@ int Foo } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] - public async Task TestMissingIfPropertyIsOn() + public async Task TestUpdatePropertyInsteadOfAccessor() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @"class C { int Foo @@ -72,7 +74,11 @@ int Foo [|return|] Bar(); } } -}", new TestParameters(options: UseExpressionBodyIncludingPropertiesAndIndexers)); +}", +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyIncludingPropertiesAndIndexers)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -99,9 +105,9 @@ int this[int i] } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] - public async Task TestMissingIfIndexerIsOn() + public async Task TestUpdateIndexerIfIndexerAndAccessorCanBeUpdated() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @"class C { int this[int i] @@ -111,7 +117,11 @@ int this[int i] [|return|] Bar(); } } -}", new TestParameters(options: UseExpressionBodyIncludingPropertiesAndIndexers)); +}", +@"class C +{ + int this[int i] => Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyIncludingPropertiesAndIndexers)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -140,31 +150,14 @@ int Foo [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestMissingWithOnlySetter() { - await TestActionCountAsync( -@"class C -{ - int Foo - { - set => [|Bar|](); - } -}", count: 1, parameters: new TestParameters(options: UseExpressionBody)); - - // There is a hidden diagnostic that still offers to convert expression-body to block-body. - await TestInRegularAndScriptAsync( + await TestMissingAsync( @"class C { int Foo { set => [|Bar|](); } -}", -@"class C -{ - int Foo - { - set { Bar(); } - } -}", options: UseExpressionBody); +}"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -222,8 +215,8 @@ await TestInRegularAndScriptAsync( int Foo { get [|=>|] Bar(); - } - }", + } +}", @"class C { int Foo @@ -233,7 +226,7 @@ int Foo return Bar(); } } -}", options: UseBlockBody); +}", options: UseBlockBodyIncludingPropertiesAndIndexers); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -256,7 +249,7 @@ int Foo Bar(); } } -}", options: UseBlockBody); +}", options: UseBlockBodyIncludingPropertiesAndIndexers); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -279,7 +272,7 @@ int Foo throw new NotImplementedException(); } } -}", options: UseBlockBody); +}", options: UseBlockBodyIncludingPropertiesAndIndexers); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] @@ -302,7 +295,7 @@ int Foo throw new NotImplementedException(); // comment } } -}", ignoreTrivia: false, options: UseBlockBody); +}", ignoreTrivia: false, options: UseBlockBodyIncludingPropertiesAndIndexers); } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConstructorsTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConstructorsAnalyzerTests.cs similarity index 94% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConstructorsTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConstructorsAnalyzerTests.cs index 179293cba2105..b4a82cbdc90de 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConstructorsTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConstructorsAnalyzerTests.cs @@ -14,11 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForConstructorsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForConstructorsAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForConstructorsDiagnosticAnalyzer(), - new UseExpressionBodyForConstructorsCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => Option(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConversionOperatorsTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConversionOperatorsAnalyzerTests.cs similarity index 93% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConversionOperatorsTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConversionOperatorsAnalyzerTests.cs index e3096b8f56537..87faef0c1780c 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForConversionOperatorsTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForConversionOperatorsAnalyzerTests.cs @@ -14,11 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForConversionOperatorsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForConversionOperatorsAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer(), - new UseExpressionBodyForConversionOperatorsCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForIndexerTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForIndexersAnalyzerTests.cs similarity index 96% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForIndexerTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForIndexersAnalyzerTests.cs index 99ed686376cdf..7fad114993704 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForIndexerTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForIndexersAnalyzerTests.cs @@ -14,10 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForIndexersTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForIndexersAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForIndexersDiagnosticAnalyzer(), new UseExpressionBodyForIndexersCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => OptionsSet( diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForMethodsTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForMethodsAnalyzerTests.cs similarity index 98% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForMethodsTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForMethodsAnalyzerTests.cs index 8d07ffd193e07..a7204a1f19e08 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForMethodsTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForMethodsAnalyzerTests.cs @@ -14,10 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForMethodsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForMethodsAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForMethodsDiagnosticAnalyzer(), new UseExpressionBodyForMethodsCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForOperatorsTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForOperatorsAnalyzerTests.cs similarity index 94% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForOperatorsTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForOperatorsAnalyzerTests.cs index 93e0e72e657b5..0b622cf69e3b7 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForOperatorsTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForOperatorsAnalyzerTests.cs @@ -14,11 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForOperatorsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForOperatorsAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForOperatorsDiagnosticAnalyzer(), - new UseExpressionBodyForOperatorsCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForPropertiesTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForPropertiesAnalyzerTests.cs similarity index 69% rename from src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForPropertiesTests.cs rename to src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForPropertiesAnalyzerTests.cs index c6b72054ffbfb..d2e21eb196482 100644 --- a/src/EditorFeatures/CSharpTest/UseExpressionBody/UseExpressionBodyForPropertiesTests.cs +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Analyzer/UseExpressionBodyForPropertiesAnalyzerTests.cs @@ -14,10 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody { - public class UseExpressionBodyForPropertiesTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class UseExpressionBodyForPropertiesAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new UseExpressionBodyForPropertiesDiagnosticAnalyzer(), new UseExpressionBodyForPropertiesCodeFixProvider()); + => (new UseExpressionBodyDiagnosticAnalyzer(), new UseExpressionBodyCodeFixProvider()); private IDictionary UseExpressionBody => OptionsSet( @@ -244,5 +244,125 @@ await TestInRegularAndScriptAsync( public string OtherThing => ""Pickles""; }", ignoreTrivia: false, options: UseExpressionBody); } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestDirectivesInBlockBody1() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { +#if true + [|return|] Bar(); +#else + return Baz(); +#endif + } + } +}", + +@"class C +{ + int Foo => +#if true + Bar(); +#else + return Baz(); +#endif + +}", ignoreTrivia: false, + parameters: new TestParameters(options: UseExpressionBody)); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestDirectivesInBlockBody2() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { +#if false + return Bar(); +#else + [|return|] Baz(); +#endif + } + } +}", + +@"class C +{ + int Foo => +#if false + return Bar(); +#else + Baz(); +#endif + +}", ignoreTrivia: false, + parameters: new TestParameters(options: UseExpressionBody)); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestMissingWithDirectivesInExpressionBody1() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + int Foo [|=>|] +#if true + Bar(); +#else + Baz(); +#endif +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [WorkItem(19235, "https://github.com/dotnet/roslyn/issues/19235")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestMissingWithDirectivesInExpressionBody2() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + int Foo [|=>|] +#if false + Bar(); +#else + Baz(); +#endif +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [WorkItem(19193, "https://github.com/dotnet/roslyn/issues/19193")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestMoveTriviaFromExpressionToReturnStatement() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int Foo(int i) [|=>|] + //comment + i * i; +}", +@"class C +{ + int Foo(int i) + { + //comment + return i * i; + } +}", ignoreTrivia: false, + options: UseBlockBody); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForAccessorsRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForAccessorsRefactoringTests.cs new file mode 100644 index 0000000000000..8629b5840e255 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForAccessorsRefactoringTests.cs @@ -0,0 +1,155 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForAccessorsRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBodyForAccessors_BlockBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithNoneEnforcement)); + + private IDictionary UseExpressionBodyForAccessors_ExpressionBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement)); + + private IDictionary UseBlockBodyForAccessors_ExpressionBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement)); + + private IDictionary UseBlockBodyForAccessors_BlockBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithNoneEnforcement)); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestUpdatePropertyIfPropertyWantsBlockAndAccesorWantsExpression() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody2() + { + await TestMissingAsync( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + return [||]Bar(); + } + } +}", +@"class C +{ + int Foo { get => Bar(); } +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferExpressionBodyForPropertyIfPropertyAndAccessorBothPreferExpressions() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + return [||]Bar(); + } + } +}", +@"class C +{ + int Foo => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + int Foo { get => [||]Bar(); } +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedForPropertyIfUserPrefersBlockPropertiesAndHasBlockProperty() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo { get => [||]Bar(); } +}", + +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferForPropertyIfPropertyPrefersBlockButCouldBecomeExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo { get => [||]Bar(); } +}", +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_BlockBodyForProperties)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConstructorsRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConstructorsRefactoringTests.cs new file mode 100644 index 0000000000000..45db214ff5557 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConstructorsRefactoringTests.cs @@ -0,0 +1,93 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForConstructorsRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); + + private IDictionary UseBlockBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.NeverWithNoneEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + public C() + { + [||]Bar(); + } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public C() + { + [||]Bar(); + } +}", +@"class C +{ + public C() => Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + public C() + { + return () => { [||] }; + } +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + public C() => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public C() => [||]Bar(); +}", +@"class C +{ + public C() { Bar(); } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConversionOperatorsRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConversionOperatorsRefactoringTests.cs new file mode 100644 index 0000000000000..9f033cf4536e1 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForConversionOperatorsRefactoringTests.cs @@ -0,0 +1,93 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForConversionOperatorsRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); + + private IDictionary UseBlockBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.NeverWithNoneEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + public static implicit operator bool(C c1) + { + [||]Bar(); + } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public static implicit operator bool(C c1) + { + [||]Bar(); + } +}", +@"class C +{ + public static implicit operator bool(C c1) => Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + public static implicit operator bool(C c1) + { + return () => { [||] }; + } +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + public static implicit operator bool(C c1) => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public static implicit operator bool(C c1) => [||]Bar(); +}", +@"class C +{ + public static implicit operator bool(C c1) { return Bar(); } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForIndexersRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForIndexersRefactoringTests.cs new file mode 100644 index 0000000000000..5941b5f57b714 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForIndexersRefactoringTests.cs @@ -0,0 +1,102 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForIndexersRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); + + private IDictionary UseBlockBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.NeverWithNoneEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + int this[int i] + { + get + { + [||]return Bar(); + } + } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + +[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int this[int i] + { + get + { + [||]return Bar(); + } + } +}", +@"class C +{ + int this[int i] => Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + Action Foo[int i] + { + get + { + return () => { [||] }; + } + } +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + int this[int i] => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int this[int i] => [||]Bar(); +}", +@"class C +{ + int this[int i] { get => Bar(); } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForMethodsRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForMethodsRefactoringTests.cs new file mode 100644 index 0000000000000..1c49ca1f1af05 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForMethodsRefactoringTests.cs @@ -0,0 +1,93 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForMethodsRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); + + private IDictionary UseBlockBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.NeverWithNoneEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + void Foo() + { + [||]Bar(); + } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + void Foo() + { + [||]Bar(); + } +}", +@"class C +{ + void Foo() => Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + Action Foo() + { + return () => { [||] }; + } +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + void Foo() => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + void Foo() => [||]Bar(); +}", +@"class C +{ + void Foo() { Bar(); } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForOperatorsRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForOperatorsRefactoringTests.cs new file mode 100644 index 0000000000000..93e0e4e32453c --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForOperatorsRefactoringTests.cs @@ -0,0 +1,93 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForOperatorsRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement); + + private IDictionary UseBlockBody => + this.Option(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.NeverWithNoneEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + public static bool operator +(C c1, C c2) + { + [||]Bar(); + } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public static bool operator +(C c1, C c2) + { + [||]Bar(); + } +}", +@"class C +{ + public static bool operator +(C c1, C c2) => Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + public static bool operator +(C c1, C c2) + { + return () => { [||] }; + } +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + public static bool operator +(C c1, C c2) => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBody)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + public static bool operator +(C c1, C c2) => [||]Bar(); +}", +@"class C +{ + public static bool operator +(C c1, C c2) { return Bar(); } +}", parameters: new TestParameters(options: UseExpressionBody)); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForPropertiesRefactoringTests.cs b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForPropertiesRefactoringTests.cs new file mode 100644 index 0000000000000..159586a930e42 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyForPropertiesRefactoringTests.cs @@ -0,0 +1,183 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody +{ + public class UseExpressionBodyForPropertiesRefactoringTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseExpressionBodyCodeRefactoringProvider(); + + private IDictionary UseExpressionBodyForAccessors_BlockBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithNoneEnforcement)); + + private IDictionary UseExpressionBodyForAccessors_ExpressionBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement)); + + private IDictionary UseBlockBodyForAccessors_ExpressionBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithNoneEnforcement)); + + private IDictionary UseBlockBodyForAccessors_BlockBodyForProperties => + OptionsSet( + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + this.SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithNoneEnforcement)); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersExpressionBodiesAndInBlockBody() + { + await TestMissingAsync( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestUpdateAccessorIfAccessWantsBlockAndPropertyWantsExpression() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", +@"class C +{ + int Foo + { + get => Bar(); + } +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersBlockBodiesAndInBlockBody2() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo + { + get + { + [||]return Bar(); + } + } +}", +@"class C +{ + int Foo => Bar(); +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedInLambda() + { + await TestMissingAsync( +@"class C +{ + Action Foo + { + get + { + return () => { [||] }; + } + } +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody() + { + await TestMissingAsync( +@"class C +{ + int Foo => [||]Bar(); +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestNotOfferedIfUserPrefersBlockBodiesAndInExpressionBody2() + { + await TestMissingAsync( +@"class C +{ + int Foo => [||]Bar(); +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_BlockBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo => [||]Bar(); +}", +@"class C +{ + int Foo { get => Bar(); } +}", parameters: new TestParameters(options: UseExpressionBodyForAccessors_ExpressionBodyForProperties)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOfferedIfUserPrefersExpressionBodiesAndInExpressionBody2() + { + await TestInRegularAndScript1Async( +@"class C +{ + int Foo => [||]Bar(); +}", +@"class C +{ + int Foo { get { return Bar(); } } +}", parameters: new TestParameters(options: UseBlockBodyForAccessors_ExpressionBodyForProperties)); + } + } +} \ 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/CSharpTest2/CSharpEditorServicesTest2.csproj b/src/EditorFeatures/CSharpTest2/CSharpEditorServicesTest2.csproj index 9547822821180..ec235f71db4fb 100644 --- a/src/EditorFeatures/CSharpTest2/CSharpEditorServicesTest2.csproj +++ b/src/EditorFeatures/CSharpTest2/CSharpEditorServicesTest2.csproj @@ -142,6 +142,7 @@ Library Microsoft.CodeAnalysis.Editor.CSharp.UnitTests Roslyn.Services.Editor.CSharp2.UnitTests + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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..5fd0df6032926 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -13,6 +13,7 @@ v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest @@ -224,6 +225,9 @@ + + + diff --git a/src/EditorFeatures/Test2/EditorServicesTest2.vbproj b/src/EditorFeatures/Test2/EditorServicesTest2.vbproj index d0b690759a7a0..53a950f293939 100644 --- a/src/EditorFeatures/Test2/EditorServicesTest2.vbproj +++ b/src/EditorFeatures/Test2/EditorServicesTest2.vbproj @@ -13,6 +13,7 @@ v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest 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/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index c64ac7fd1b24d..c3865e799b69d 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -205,6 +205,7 @@ internal void VerifySemantics( IEnumerable additionalOldSources = null, IEnumerable additionalNewSources = null, SemanticEditDescription[] expectedSemanticEdits = null, + DiagnosticDescription expectedDeclarationError = null, RudeEditDiagnosticDescription[] expectedDiagnostics = null) { var editMap = Analyzer.BuildEditMap(editScript); @@ -234,18 +235,6 @@ internal void VerifySemantics( var oldCompilation = CreateLibraryCompilation("Old", oldTrees); var newCompilation = CreateLibraryCompilation("New", newTrees); - if (oldCompilation is CSharpCompilation) - { - oldCompilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); - newCompilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); - } - else - { - // TODO: verify all compilation diagnostics like C# does (tests need to be updated) - oldTrees.SelectMany(tree => tree.GetDiagnostics()).Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); - newTrees.SelectMany(tree => tree.GetDiagnostics()).Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); - } - var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); @@ -297,8 +286,13 @@ internal void VerifySemantics( newModel, actualSemanticEdits, diagnostics, + out var firstDeclarationErrorOpt, default(CancellationToken)); + var actualDeclarationErrors = (firstDeclarationErrorOpt != null) ? new[] { firstDeclarationErrorOpt } : Array.Empty(); + var expectedDeclarationErrors = (expectedDeclarationError != null) ? new[] { expectedDeclarationError } : Array.Empty(); + actualDeclarationErrors.Verify(expectedDeclarationErrors); + diagnostics.Verify(newSource, expectedDiagnostics); if (expectedSemanticEdits == null) 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/BasicEditorServicesTest.vbproj b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj index 800d051d8af7b..5960dd18eb7f3 100644 --- a/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj +++ b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj @@ -14,6 +14,7 @@ v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/InlineTemporary/InlineTemporaryTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/InlineTemporary/InlineTemporaryTests.vb index ee0735d40dc86..5d369567f276e 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/InlineTemporary/InlineTemporaryTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/InlineTemporary/InlineTemporaryTests.vb @@ -4397,6 +4397,29 @@ Class C } End Sub End Class +" + Await TestInRegularAndScriptAsync(code, expected, ignoreTrivia:=False) + End Function + + + + Public Async Function TupleElementNameIsNotReplaced() As Task + ' The name of the named element has bad symbol info and gets replaced with (1 + 2) + Dim code = " +Class C + Sub M() + Dim [||]i = 1 + 2 + Dim t = (i, i:=3) + End Sub +End Class +" + + Dim expected = " +Class C + Sub M() + Dim t = (1 + 2, i:=3) + End Sub +End Class " Await TestInRegularAndScriptAsync(code, expected, ignoreTrivia:=False) End Function 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/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.vb index 7b5de5152a9cb..43dc0b88e8055 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/ReplacePropertyWithMethods/ReplacePropertyWithMethodsTests.vb @@ -415,7 +415,7 @@ end class", end class") End Function - + Public Async Function TestDocumentationComment1() As Task Await TestInRegularAndScriptAsync( @@ -439,13 +439,13 @@ End Interface", End Interface", ignoreTrivia:=False) End Function - + Public Async Function TestDocumentationComment2() As Task Await TestInRegularAndScriptAsync( "Interface ILanguageServiceHost ''' - ''' Gets the active workspace project context that provides access to the language service for the active configured project. + ''' Sets the active workspace project context that provides access to the language service for the active configured project. ''' ''' ''' An that provides access to the language service for the active configured project. @@ -454,22 +454,22 @@ End Interface", ignoreTrivia:=False) End Interface", "Interface ILanguageServiceHost ''' - ''' Gets the active workspace project context that provides access to the language service for the active configured project. + ''' Sets the active workspace project context that provides access to the language service for the active configured project. ''' - ''' + ''' ''' An that provides access to the language service for the active configured project. - ''' + ''' Sub SetActiveProjectContext(Value As Object) End Interface", ignoreTrivia:=False) End Function - + Public Async Function TestDocumentationComment3() As Task Await TestInRegularAndScriptAsync( "Interface ILanguageServiceHost ''' - ''' Gets the active workspace project context that provides access to the language service for the active configured project. + ''' Gets or sets the active workspace project context that provides access to the language service for the active configured project. ''' ''' ''' An that provides access to the language service for the active configured project. @@ -478,14 +478,103 @@ End Interface", ignoreTrivia:=False) End Interface", "Interface ILanguageServiceHost ''' - ''' Gets the active workspace project context that provides access to the language service for the active configured project. + ''' Gets or sets the active workspace project context that provides access to the language service for the active configured project. ''' ''' ''' An that provides access to the language service for the active configured project. ''' Function GetActiveProjectContext() As Object + ''' + ''' Gets or sets the active workspace project context that provides access to the language service for the active configured project. + ''' + ''' + ''' An that provides access to the language service for the active configured project. + ''' Sub SetActiveProjectContext(Value As Object) End Interface", ignoreTrivia:=False) End Function + + + + Public Async Function TestDocumentationComment4() As Task + Await TestInRegularAndScriptAsync( +"Interface ILanguageServiceHost + ''' + ''' Sets . + ''' + ''' + WriteOnly Property [||]ActiveProjectContext As Object +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", +"Interface ILanguageServiceHost + ''' + ''' Sets . + ''' + ''' + Sub SetActiveProjectContext(Value As Object) +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", ignoreTrivia:=False) + End Function + + + + Public Async Function TestDocumentationComment5() As Task + Await TestInRegularAndScriptAsync( +"Interface ILanguageServiceHost + ''' + ''' Gets or sets . + ''' + ''' + Property [||]ActiveProjectContext As Object +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", +"Interface ILanguageServiceHost + ''' + ''' Gets or sets . + ''' + ''' + Function GetActiveProjectContext() As Object + ''' + ''' Gets or sets . + ''' + ''' + Sub SetActiveProjectContext(Value As Object) +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", ignoreTrivia:=False) + End Function + + + + Public Async Function TestDocumentationComment6() As Task + Await TestInRegularAndScriptAsync( +"Interface ISomeInterface(Of T) + ''' + WriteOnly Property [||]Context As ISomeInterface(Of T) +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", +"Interface ISomeInterface(Of T) + ''' + Sub SetContext(Value As ISomeInterface(Of T)) +End Interface +Structure AStruct + ''' + Private X As Integer +End Structure", ignoreTrivia:=False) + End Function End Class 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/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb index 8190e3ef3cf52..156d1ff6cf1c7 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb @@ -5,6 +5,7 @@ Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue +Imports Microsoft.CodeAnalysis.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests @@ -47,7 +48,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, ActiveStatementsDescription.Empty, Nothing, expectedDiagnostics) + VerifySemanticDiagnostics(editScript, Nothing, expectedDiagnostics) + End Sub + + + Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), + expectedDeclarationError As DiagnosticDescription, + ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + VerifySemantics(editScript, ActiveStatementsDescription.Empty, Nothing, expectedDeclarationError, expectedDiagnostics) End Sub @@ -55,7 +63,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests activeStatements As ActiveStatementsDescription, expectedSemanticEdits As SemanticEditDescription(), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, activeStatements, Nothing, Nothing, expectedSemanticEdits, expectedDiagnostics) + VerifySemantics(editScript, activeStatements, Nothing, Nothing, expectedSemanticEdits, Nothing, expectedDiagnostics) + End Sub + + + Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), + activeStatements As ActiveStatementsDescription, + expectedSemanticEdits As SemanticEditDescription(), + expectedDeclarationError As DiagnosticDescription, + ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + VerifySemantics(editScript, activeStatements, Nothing, Nothing, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) + End Sub + + + Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), + activeStatements As ActiveStatementsDescription, + additionalOldSources As IEnumerable(Of String), + additionalNewSources As IEnumerable(Of String), + expectedSemanticEdits As SemanticEditDescription(), + ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + VerifySemantics(editScript, activeStatements, additionalOldSources, additionalNewSources, expectedSemanticEdits, Nothing, expectedDiagnostics) End Sub @@ -64,6 +91,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests additionalOldSources As IEnumerable(Of String), additionalNewSources As IEnumerable(Of String), expectedSemanticEdits As SemanticEditDescription(), + expectedDeclarationError As DiagnosticDescription, ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) VisualBasicEditAndContinueTestHelpers.Instance.VerifySemantics( editScript, @@ -71,6 +99,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests additionalOldSources, additionalNewSources, expectedSemanticEdits, + expectedDeclarationError, expectedDiagnostics) End Sub End Module diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb index dda84edfbf765..5b08ad8d7f037 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb @@ -3418,7 +3418,7 @@ End Class Dim src1 = " Imports System Class C - Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) + Readonly Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) Get Return New Func(Of Integer, Integer)(Function(a3) a1 + a2) End Get @@ -3428,7 +3428,7 @@ End Class Dim src2 = " Imports System Class C - Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) + Readonly Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) Get Return New Func(Of Integer, Integer)(Function(a3) a2) End Get @@ -3714,7 +3714,7 @@ End Class Dim src1 = " Imports System Class C - Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) + Readonly Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) Get Return New Func(Of Integer, Integer)(Function(a3) a2) End Get @@ -3724,7 +3724,7 @@ End Class Dim src2 = " Imports System Class C - Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) + Readonly Property Item(a1 As Integer, a2 As Integer) As Func(Of Integer, Integer) Get Return New Func(Of Integer, Integer)(Function(a3) a1 + a2) End Get @@ -4041,12 +4041,12 @@ Imports System Partial Class C Dim x As Integer = 1 - Partial Sub F() ' def + Private Partial Sub F() ' def End Sub End Class Partial Class C - Partial Sub F() ' impl + Private Sub F() ' impl Dim f = New Func(Of Integer, Integer)(Function(a) a) End Sub End Class @@ -4056,19 +4056,19 @@ Imports System Partial Class C Dim x As Integer = 1 - Partial Sub F() ' def + Private Partial Sub F() ' def End Sub End Class Partial Class C - Partial Sub F() ' impl + Private Sub F() ' impl Dim f = New Func(Of Integer, Integer)(Function(a) a + x) End Sub End Class " Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.CapturingVariable, "F", "Me").WithFirstLine("Partial Sub F() ' impl")) + Diagnostic(RudeEditKind.CapturingVariable, "F", "Me").WithFirstLine("Private Sub F() ' impl")) End Sub @@ -4497,6 +4497,40 @@ End Class edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.RenamingCapturedVariable, "y", "x", "y")) End Sub + + + Public Sub Lambdas_Signature_SemanticErrors() + Dim src1 = " +Imports System + +Class C + + Sub G(f As Func(Of Unknown, Unknown)) + End Sub + + Sub F() + G(Function(a) 1) + End Sub +End Class +" + Dim src2 = " +Imports System + +Class C + + Sub G(f As Func(Of Unknown, Unknown)) + End Sub + + Sub F() + G(Function(a) 2) + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(ERRID.ERR_UndefinedType1, "Unknown").WithArguments("Unknown").WithLocation(6, 24)) + End Sub + #End Region #Region "Queries" @@ -5976,12 +6010,13 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.Instance40.VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - Nothing, - Nothing, - Nothing, - {Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}) + editScript:=edits, + activeStatements:=ActiveStatementsDescription.Empty, + additionalOldSources:=Nothing, + additionalNewSources:=Nothing, + expectedSemanticEdits:=Nothing, + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}, + expectedDeclarationError:=Nothing) End Sub @@ -6006,12 +6041,13 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.Instance40.VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - Nothing, - Nothing, - Nothing, - Nothing) + editScript:=edits, + activeStatements:=ActiveStatementsDescription.Empty, + additionalOldSources:=Nothing, + additionalNewSources:=Nothing, + expectedSemanticEdits:=Nothing, + expectedDiagnostics:=Nothing, + expectedDeclarationError:=Nothing) End Sub #End Region @@ -6092,12 +6128,13 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.InstanceMinAsync.VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - Nothing, - Nothing, - Nothing, - {Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}) + editScript:=edits, + activeStatements:=ActiveStatementsDescription.Empty, + additionalOldSources:=Nothing, + additionalNewSources:=Nothing, + expectedSemanticEdits:=Nothing, + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}, + expectedDeclarationError:=Nothing) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb index 029a86a725198..9008acc521dbb 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb @@ -1308,7 +1308,7 @@ End Class Public Sub NestedClass_InsertMemberWithInitializer1() Dim src1 = "Public Class C : End Class" - Dim src2 = "Public Class C : Private Class D : Public Property P As New List(Of String) : End Class : End Class" + Dim src2 = "Public Class C : Private Class D : Public Property P As New Object : End Class : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, @@ -1318,7 +1318,7 @@ End Class Public Sub NestedClass_InsertMemberWithInitializer2() Dim src1 = "Public Module C : End Module" - Dim src2 = "Public Module C : Private Class D : Property P As New List(Of String) : End Class : End Module" + Dim src2 = "Public Module C : Private Class D : Property P As New Object : End Class : End Module" Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, @@ -1767,14 +1767,14 @@ End Class Public Sub MethodInsert_PrivateWithAttribute() Dim src1 = "Class C : End Class" - Dim src2 = "Class C : " & vbLf & "Private Sub F : End Sub : End Class" + Dim src2 = "Class C : " & vbLf & "Private Sub F : End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Insert [Private Sub F : End Sub]@11", - "Insert [Private Sub F]@11", - "Insert []@11", - "Insert [A]@12") + "Insert [Private Sub F : End Sub]@11", + "Insert [Private Sub F]@11", + "Insert []@11", + "Insert [System.Obsolete]@12") edits.VerifySemantics(ActiveStatementsDescription.Empty, {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"))}) @@ -2926,7 +2926,8 @@ Partial Class C Return 0 End Function - Dim A1(F(Function(a1) a1 + 1)), A2 As Integer = F(Function(a2) a2 + 1) + Dim A1(F(Function(a1) a1 + 1)) + Dim A2 As Integer = F(Function(a2) a2 + 1) Dim A3, A4 As New Func(Of Integer, Integer)(Function(a34) a34 + 1) Dim A5(F(Function(a51) a51 + 1), F(Function(a52) a52 + 1)) As Integer End Class @@ -2947,7 +2948,8 @@ Partial Class C Return 0 End Function - Dim A1(F(Function(a1) a1 + 1)), A2 As Integer = F(Function(a2) a2 + 1) + Dim A1(F(Function(a1) a1 + 1)) + Dim A2 As Integer = F(Function(a2) a2 + 1) Dim A3, A4 As New Func(Of Integer, Integer)(Function(a34) a34 + 1) Dim A5(F(Function(a51) a51 + 1), F(Function(a52) a52 + 1)) As Integer End Class @@ -3181,6 +3183,39 @@ End Class ' {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C").Constructors.Single(), syntaxMap(0))}) End Sub + + + Public Sub Constructor_SemanticError_Partial() + Dim src1 = " +Partial Class C + Partial Sub New(x As Integer) + End Sub +End Class + +Class C + Partial Sub New(x As Integer) + System.Console.WriteLine(1) + End Sub +End Class + +" + Dim src2 = " +Partial Class C + Partial Sub New(x As Integer) + End Sub +End Class + +Class C + Partial Sub New(x As Integer) + System.Console.WriteLine(2) + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(ERRID.ERR_ConstructorCannotBeDeclaredPartial, "Partial").WithArguments("Partial").WithLocation(3, 5)) + End Sub + #End Region #Region "Declare" @@ -4205,19 +4240,6 @@ End Class {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - - Public Sub Field_InitializerUpdate2() - Dim src1 = "Class C : Dim a, b As Integer = 0 : End Class" - Dim src2 = "Class C : Dim a, b As Integer = 1 : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [a, b As Integer = 0]@14 -> [a, b As Integer = 1]@14") - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) - End Sub - Public Sub Property_Instance_InitializerUpdate() Dim src1 = "Class C : Property a As Integer = 0 : End Class" @@ -4259,12 +4281,12 @@ End Class Public Sub Field_InitializerUpdate_AsNew1() - Dim src1 = "Class C : Dim a As New D(1) : End Class" - Dim src2 = "Class C : Dim a As New D(2) : End Class" + Dim src1 = "Class C : Dim a As New Decimal(1) : End Class" + Dim src2 = "Class C : Dim a As New Decimal(2) : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [a As New D(1)]@14 -> [a As New D(2)]@14") + "Update [a As New Decimal(1)]@14 -> [a As New Decimal(2)]@14") edits.VerifySemantics(ActiveStatementsDescription.Empty, {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) @@ -4272,12 +4294,12 @@ End Class Public Sub Field_InitializerUpdate_AsNew2() - Dim src1 = "Class C : Dim a, b As New C(1) : End Class" - Dim src2 = "Class C : Dim a, b As New C(2) : End Class" + Dim src1 = "Class C : Dim a, b As New Decimal(1) : End Class" + Dim src2 = "Class C : Dim a, b As New Decimal(2) : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [a, b As New C(1)]@14 -> [a, b As New C(2)]@14") + "Update [a, b As New Decimal(1)]@14 -> [a, b As New Decimal(2)]@14") edits.VerifySemantics(ActiveStatementsDescription.Empty, {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) @@ -4285,12 +4307,12 @@ End Class Public Sub Property_InitializerUpdate_AsNew() - Dim src1 = "Class C : Property a As New D(1) : End Class" - Dim src2 = "Class C : Property a As New D(2) : End Class" + Dim src1 = "Class C : Property a As New Decimal(1) : End Class" + Dim src2 = "Class C : Property a As New Decimal(2) : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Property a As New D(1)]@10 -> [Property a As New D(2)]@10") + "Update [Property a As New Decimal(1)]@10 -> [Property a As New Decimal(2)]@10") edits.VerifySemantics(ActiveStatementsDescription.Empty, {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) @@ -4348,19 +4370,6 @@ End Class {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - - Public Sub Property_StructInitializerUpdate_Delete() - Dim src1 = "Structure C : Property a As Integer = 0 : End Structure" - Dim src2 = "Structure C : Property a As Integer : End Structure" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Property a As Integer = 0]@14 -> [Property a As Integer]@14") - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) - End Sub - Public Sub Field_InitializerUpdate_Insert() Dim src1 = "Class C : Dim a As Integer : End Class" @@ -4436,15 +4445,15 @@ End Class Public Sub FieldUpdate_ModuleCtorUpdate1() - Dim src1 = "Module C : Dim a As Integer : " & vbLf & "Shared Sub New() : End Sub : End Module" + Dim src1 = "Module C : Dim a As Integer : " & vbLf & "Sub New() : End Sub : End Module" Dim src2 = "Module C : Dim a As Integer = 0 : End Module" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( "Update [a As Integer]@15 -> [a As Integer = 0]@15", - "Delete [Shared Sub New() : End Sub]@31", - "Delete [Shared Sub New()]@31", - "Delete [()]@45") + "Delete [Sub New() : End Sub]@31", + "Delete [Sub New()]@31", + "Delete [()]@38") edits.VerifySemantics(ActiveStatementsDescription.Empty, {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) @@ -4560,8 +4569,8 @@ End Class Public Sub PropertyUpdate_ModuleCtorUpdate2() - Dim src1 = "Module C : Property a As Integer : " & vbLf & "Shared Sub New() : End Sub : End Module" - Dim src2 = "Module C : Property a As Integer = 0 : " & vbLf & "Shared Sub New() : End Sub : End Module" + Dim src1 = "Module C : Property a As Integer : " & vbLf & "Sub New() : End Sub : End Module" + Dim src2 = "Module C : Property a As Integer = 0 : " & vbLf & "Sub New() : End Sub : End Module" Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, @@ -4966,11 +4975,11 @@ End Class Public Sub PrivateFieldInsert2() Dim src1 = "Class C : Private a As Integer = 1 : End Class" - Dim src2 = "Class C : Private a, b As Integer = 1 : End Class" + Dim src2 = "Class C : Private a, b As Integer : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [a As Integer = 1]@18 -> [a, b As Integer = 1]@18", + "Update [a As Integer = 1]@18 -> [a, b As Integer]@18", "Insert [b]@21") edits.VerifySemantics(ActiveStatementsDescription.Empty, @@ -5860,6 +5869,51 @@ End Class" {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(0), syntaxMap(0)), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(1), syntaxMap(0))}) End Sub + + + Public Sub PropertyWithInitializer_SemanticError_Partial() + Dim src1 = " +Partial Class C + Partial Public ReadOnly Property NewProperty() As String + Get + Return 1 + End Get + End Property +End Class + +Partial Class C + Partial Public ReadOnly Property NewProperty() As String + Get + Return 1 + End Get + End Property +End Class +" + Dim src2 = " +Partial Class C + Partial Public ReadOnly Property NewProperty() As String + Get + Return 1 + End Get + End Property +End Class + +Partial Class C + Partial Public ReadOnly Property NewProperty() As String + Get + Return 1 + End Get + End Property + + Sub New() + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(ERRID.ERR_BadPropertyFlags1, "Partial").WithArguments("Partial").WithLocation(3, 5)) + End Sub + #End Region #Region "Events" @@ -5950,29 +6004,32 @@ End Class" Public Sub EventInsert_IntoLayoutClass_Sequential() - Dim src1 = Class C End Class -]]>.Value - Dim src2 = Class C Private Custom Event c As Action - AddHandler + AddHandler(value As Action) End AddHandler - RemoveHandler + RemoveHandler(value As Action) End RemoveHandler + + RaiseEvent() + End RaiseEvent End Event End Class -]]>.Value +" Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics() End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 4efae5fee2da2..35e6f7c488fd0 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -543,8 +543,8 @@ End Class End Using End Function - - Public Async Function AnalyzeDocumentAsync_SemanticError_Change() As Threading.Tasks.Task + + Public Async Function AnalyzeDocumentAsync_SemanticErrorInMethodBody_Change() As Task Dim source1 = " Class C Public Shared Sub Main() @@ -571,6 +571,38 @@ End Class Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatementSpan)() Dim result = Await analyzer.AnalyzeDocumentAsync(oldSolution, baseActiveStatements, newSolution.GetDocument(documentId), Nothing) + ' no declaration errors (error in method body is only reported when emitting) + Assert.False(result.HasChangesAndErrors) + Assert.False(result.HasChangesAndCompilationErrors) + End Using + End Function + + + Public Async Function AnalyzeDocumentAsync_SemanticErrorInDeclaration_Change() As Task + Dim source1 = " +Class C + Public Shared Sub Main(x As Bar) + System.Console.WriteLine(1) + End Sub +End Class +" + Dim source2 = " +Class C + Public Shared Sub Main(x As Bar) + System.Console.WriteLine(2) + End Sub +End Class +" + + Dim analyzer = New VisualBasicEditAndContinueAnalyzer() + Using workspace = TestWorkspace.CreateVisualBasic(source1) + Dim documentId = workspace.CurrentSolution.Projects.First().Documents.First().Id + Dim oldSolution = workspace.CurrentSolution + Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) + + Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatementSpan)() + Dim result = Await analyzer.AnalyzeDocumentAsync(oldSolution, baseActiveStatements, newSolution.GetDocument(documentId), Nothing) + Assert.True(result.HasChanges) Assert.True(result.HasChangesAndErrors) Assert.True(result.HasChangesAndCompilationErrors) diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj index e0a13de03a276..f59399c4d4b17 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj @@ -12,6 +12,7 @@ Roslyn.ExpressionEvaluator.CSharp.ExpressionCompiler.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest true 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/CSharp/Test/ResultProvider/CSharpResultProviderTest.csproj b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/CSharpResultProviderTest.csproj index 25a836918cd25..983e564b02121 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/CSharpResultProviderTest.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/CSharpResultProviderTest.csproj @@ -12,6 +12,7 @@ Roslyn.ExpressionEvaluator.CSharp.ResultProvider.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest true diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/FunctionResolverTest.csproj b/src/ExpressionEvaluator/Core/Test/FunctionResolver/FunctionResolverTest.csproj index b5ce328308265..73357b3711099 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/FunctionResolverTest.csproj +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/FunctionResolverTest.csproj @@ -13,6 +13,7 @@ true v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj index ab92292b3745e..b7846be22202e 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj @@ -11,6 +11,7 @@ Roslyn.ExpressionEvaluator.VisualBasic.ExpressionCompiler.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj index 7e3fb7a31760a..2545a1f138d1e 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj @@ -12,6 +12,7 @@ v4.6 win7 Default + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj index 2f8b171323145..07d23798af9ff 100644 --- a/src/Features/CSharp/Portable/CSharpFeatures.csproj +++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj @@ -62,7 +62,7 @@ - + @@ -86,7 +86,6 @@ - @@ -96,18 +95,16 @@ - - - - - - - - - - - - + + + + + + + + + + @@ -403,10 +400,8 @@ - - - - + + @@ -454,6 +449,8 @@ + + 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..153e12affca1b 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; } @@ -3226,7 +3271,7 @@ private static bool DeclareSameIdentifiers(SeparatedSyntaxList diagnostics) + internal override void ReportMemberBodySemanticRudeEdits(SemanticModel oldModel, SyntaxNode oldNode, SemanticModel newModel, SyntaxNode newNode, List diagnostics) { var foundNode = FindUnsupportedV7Switch(oldModel, oldNode, diagnostics); if (foundNode != null) 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/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs b/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs index e170f83e29286..d2609f8f9c02f 100644 --- a/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs +++ b/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs @@ -95,7 +95,7 @@ public SyntaxNode ConvertMethodsToProperty( } else if (getAccessor.Body != null && getAccessor.Body.TryConvertToExpressionBody( - parseOptions, expressionBodyPreference, + parseOptions, expressionBodyPreference, out var arrowExpression, out var semicolonToken)) { return propertyDeclaration.WithExpressionBody(arrowExpression) @@ -106,10 +106,12 @@ public SyntaxNode ConvertMethodsToProperty( } else { - if (propertyDeclaration.ExpressionBody != null) + if (propertyDeclaration.ExpressionBody != null && + propertyDeclaration.ExpressionBody.TryConvertToBlock( + propertyDeclaration.SemicolonToken, + createReturnStatementForExpression: true, + block: out var block)) { - var block = propertyDeclaration.ExpressionBody.ConvertToBlock( - propertyDeclaration.SemicolonToken, createReturnStatementForExpression: true); var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithBody(block); @@ -181,7 +183,7 @@ private static AccessorDeclarationSyntax UseExpressionOrBlockBodyIfDesired( if (accessorDeclaration?.Body != null && expressionBodyPreference != ExpressionBodyPreference.Never) { if (accessorDeclaration.Body.TryConvertToExpressionBody( - parseOptions, expressionBodyPreference, + parseOptions, expressionBodyPreference, out var arrowExpression, out var semicolonToken)) { return accessorDeclaration.WithBody(null) @@ -192,13 +194,16 @@ private static AccessorDeclarationSyntax UseExpressionOrBlockBodyIfDesired( } else if (accessorDeclaration?.ExpressionBody != null && expressionBodyPreference == ExpressionBodyPreference.Never) { - var block = accessorDeclaration.ExpressionBody.ConvertToBlock( - accessorDeclaration.SemicolonToken, - createReturnStatementForExpression: accessorDeclaration.Kind() == SyntaxKind.GetAccessorDeclaration); - return accessorDeclaration.WithExpressionBody(null) - .WithSemicolonToken(default(SyntaxToken)) - .WithBody(block) - .WithAdditionalAnnotations(Formatter.Annotation); + if (accessorDeclaration.ExpressionBody.TryConvertToBlock( + accessorDeclaration.SemicolonToken, + createReturnStatementForExpression: accessorDeclaration.Kind() == SyntaxKind.GetAccessorDeclaration, + block: out var block)) + { + return accessorDeclaration.WithExpressionBody(null) + .WithSemicolonToken(default(SyntaxToken)) + .WithBody(block) + .WithAdditionalAnnotations(Formatter.Annotation); + } } return accessorDeclaration; diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToParamRewriter.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToParamRewriter.cs new file mode 100644 index 0000000000000..e3e529ba9672f --- /dev/null +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToParamRewriter.cs @@ -0,0 +1,35 @@ +// 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.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.ReplacePropertyWithMethods +{ + internal partial class CSharpReplacePropertyWithMethodsService + { + private class ConvertValueToParamRewriter : CSharpSyntaxRewriter + { + public static readonly CSharpSyntaxRewriter Instance = new ConvertValueToParamRewriter(); + + private ConvertValueToParamRewriter() + { + } + + private XmlNameSyntax ConvertToParam(XmlNameSyntax name) + => name.ReplaceToken(name.LocalName, SyntaxFactory.Identifier("param")); + + public override SyntaxNode VisitXmlElementStartTag(XmlElementStartTagSyntax node) + { + if (!IsValueName(node.Name)) + return base.VisitXmlElementStartTag(node); + + return node.ReplaceNode(node.Name, ConvertToParam(node.Name)) + .AddAttributes(SyntaxFactory.XmlNameAttribute("value")); + } + + public override SyntaxNode VisitXmlElementEndTag(XmlElementEndTagSyntax node) + => IsValueName(node.Name) + ? node.ReplaceNode(node.Name, ConvertToParam(node.Name)) + : base.VisitXmlElementEndTag(node); + } + } +} diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToReturnsRewriter.cs similarity index 89% rename from src/Features/CSharp/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.cs rename to src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToReturnsRewriter.cs index c42bee0bfce7f..fac0141a9293c 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.ConvertValueToReturnsRewriter.cs @@ -17,10 +17,6 @@ private ConvertValueToReturnsRewriter() private XmlNameSyntax ConvertToReturns(XmlNameSyntax name) => name.ReplaceToken(name.LocalName, SyntaxFactory.Identifier("returns")); - private static bool IsValueName(XmlNameSyntax name) - => name.Prefix == null && - name.LocalName.ValueText == "value"; - public override SyntaxNode VisitXmlElementStartTag(XmlElementStartTagSyntax node) => IsValueName(node.Name) ? node.ReplaceNode(node.Name, ConvertToReturns(node.Name)) diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs index f47268dd13f8c..0350d8a38e722 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ReplacePropertyWithMethods { [ExportLanguageService(typeof(IReplacePropertyWithMethodsService), LanguageNames.CSharp), Shared] internal partial class CSharpReplacePropertyWithMethodsService : - AbstractReplacePropertyWithMethodsService + AbstractReplacePropertyWithMethodsService { public override SyntaxNode GetPropertyDeclaration(SyntaxToken token) { @@ -98,20 +98,16 @@ private List ConvertPropertyToMembers( documentOptions, parseOptions, generator, propertyDeclaration, propertyBackingField, getMethod, desiredGetMethodName, - copyLeadingTrivia: true, cancellationToken: cancellationToken)); } var setMethod = property.SetMethod; if (setMethod != null) { - // Set-method only gets the leading trivia of the property if we didn't copy - // that trivia to the get-method. result.Add(GetSetMethod( documentOptions, parseOptions, generator, propertyDeclaration, propertyBackingField, setMethod, desiredSetMethodName, - copyLeadingTrivia: getMethod == null, cancellationToken: cancellationToken)); } @@ -126,14 +122,15 @@ private static SyntaxNode GetSetMethod( IFieldSymbol propertyBackingField, IMethodSymbol setMethod, string desiredSetMethodName, - bool copyLeadingTrivia, CancellationToken cancellationToken) { var methodDeclaration = GetSetMethodWorker( generator, propertyDeclaration, propertyBackingField, setMethod, desiredSetMethodName, cancellationToken); - methodDeclaration = CopyLeadingTrivia(propertyDeclaration, methodDeclaration, copyLeadingTrivia); + // The analyzer doesn't report diagnostics when the trivia contains preprocessor directives, so it's safe + // to copy the complete leading trivia to both generated methods. + methodDeclaration = CopyLeadingTrivia(propertyDeclaration, methodDeclaration, ConvertValueToParamRewriter.Instance); return UseExpressionOrBlockBodyIfDesired( documentOptions, parseOptions, methodDeclaration, @@ -182,14 +179,13 @@ private static SyntaxNode GetGetMethod( IFieldSymbol propertyBackingField, IMethodSymbol getMethod, string desiredGetMethodName, - bool copyLeadingTrivia, CancellationToken cancellationToken) { var methodDeclaration = GetGetMethodWorker( generator, propertyDeclaration, propertyBackingField, getMethod, desiredGetMethodName, cancellationToken); - methodDeclaration = CopyLeadingTrivia(propertyDeclaration, methodDeclaration, copyLeadingTrivia); + methodDeclaration = CopyLeadingTrivia(propertyDeclaration, methodDeclaration, ConvertValueToReturnsRewriter.Instance); return UseExpressionOrBlockBodyIfDesired( documentOptions, parseOptions, methodDeclaration, @@ -199,32 +195,27 @@ private static SyntaxNode GetGetMethod( private static MethodDeclarationSyntax CopyLeadingTrivia( PropertyDeclarationSyntax propertyDeclaration, MethodDeclarationSyntax methodDeclaration, - bool copyLeadingTrivia) + CSharpSyntaxRewriter documentationCommentRewriter) { - if (copyLeadingTrivia) - { - var leadingTrivia = propertyDeclaration.GetLeadingTrivia(); - return methodDeclaration.WithLeadingTrivia(leadingTrivia.Select(ConvertTrivia)); - } - - return methodDeclaration; + var leadingTrivia = propertyDeclaration.GetLeadingTrivia(); + return methodDeclaration.WithLeadingTrivia(leadingTrivia.Select(trivia => ConvertTrivia(trivia, documentationCommentRewriter))); } - private static SyntaxTrivia ConvertTrivia(SyntaxTrivia trivia) + private static SyntaxTrivia ConvertTrivia(SyntaxTrivia trivia, CSharpSyntaxRewriter rewriter) { if (trivia.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia || trivia.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia) { - return ConvertDocumentationComment(trivia); + return ConvertDocumentationComment(trivia, rewriter); } return trivia; } - private static SyntaxTrivia ConvertDocumentationComment(SyntaxTrivia trivia) + private static SyntaxTrivia ConvertDocumentationComment(SyntaxTrivia trivia, CSharpSyntaxRewriter rewriter) { var structure = trivia.GetStructure(); - var updatedStructure = (StructuredTriviaSyntax)ConvertValueToReturnsRewriter.Instance.Visit(structure); + var updatedStructure = (StructuredTriviaSyntax)rewriter.Visit(structure); return SyntaxFactory.Trivia(updatedStructure); } @@ -246,12 +237,14 @@ private static SyntaxNode UseExpressionOrBlockBodyIfDesired( } else if (methodDeclaration?.ExpressionBody != null && expressionBodyPreference == ExpressionBodyPreference.Never) { - var block = methodDeclaration?.ExpressionBody.ConvertToBlock( - methodDeclaration.SemicolonToken, createReturnStatementForExpression); - return methodDeclaration.WithExpressionBody(null) - .WithSemicolonToken(default(SyntaxToken)) - .WithBody(block) - .WithAdditionalAnnotations(Formatter.Annotation); + if (methodDeclaration.ExpressionBody.TryConvertToBlock( + methodDeclaration.SemicolonToken, createReturnStatementForExpression, out var block)) + { + return methodDeclaration.WithExpressionBody(null) + .WithSemicolonToken(default(SyntaxToken)) + .WithBody(block) + .WithAdditionalAnnotations(Formatter.Annotation); + } } return methodDeclaration; @@ -299,12 +292,41 @@ private static MethodDeclarationSyntax GetGetMethodWorker( return methodDeclaration; } + /// + /// Used by the documentation comment rewriters to identify top-level <value> nodes. + /// + private static bool IsValueName(XmlNameSyntax name) + => name.Prefix == null && + name.LocalName.ValueText == "value"; + public override SyntaxNode GetPropertyNodeToReplace(SyntaxNode propertyDeclaration) { // For C# we'll have the property declaration that we want to replace. return propertyDeclaration; } + protected override NameMemberCrefSyntax TryGetCrefSyntax(IdentifierNameSyntax identifierName) + => identifierName.Parent as NameMemberCrefSyntax; + + protected override NameMemberCrefSyntax CreateCrefSyntax(NameMemberCrefSyntax originalCref, SyntaxToken identifierToken, SyntaxNode parameterType) + { + CrefParameterListSyntax parameterList; + if (parameterType is TypeSyntax typeSyntax) + { + var parameter = SyntaxFactory.CrefParameter(typeSyntax); + parameterList = SyntaxFactory.CrefParameterList(SyntaxFactory.SingletonSeparatedList(parameter)); + } + else + { + parameterList = SyntaxFactory.CrefParameterList(); + } + + // XmlCrefAttribute replaces with {T}, which is required for C# documentation comments + var crefAttribute = SyntaxFactory.XmlCrefAttribute( + SyntaxFactory.NameMemberCref(SyntaxFactory.IdentifierName(identifierToken), parameterList)); + return (NameMemberCrefSyntax)crefAttribute.Cref; + } + protected override ExpressionSyntax UnwrapCompoundAssignment( SyntaxNode compoundAssignment, ExpressionSyntax readExpression) { 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/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyCodeFixProvider.cs deleted file mode 100644 index f001d140d9cb6..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyCodeFixProvider.cs +++ /dev/null @@ -1,181 +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.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - internal abstract partial class AbstractUseExpressionBodyCodeFixProvider : - SyntaxEditorBasedCodeFixProvider - where TDeclaration : SyntaxNode - { - private readonly Option> _option; - private readonly string _useExpressionBodyTitle; - private readonly string _useBlockBodyTitle; - - public sealed override ImmutableArray FixableDiagnosticIds { get; } - - protected AbstractUseExpressionBodyCodeFixProvider( - string diagnosticId, - Option> option, - string useExpressionBodyTitle, - string useBlockBodyTitle) - { - FixableDiagnosticIds = ImmutableArray.Create(diagnosticId); - _option = option; - _useExpressionBodyTitle = useExpressionBodyTitle; - _useBlockBodyTitle = useBlockBodyTitle; - } - - protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic) - => diagnostic.Severity != DiagnosticSeverity.Hidden; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var diagnostic = context.Diagnostics.First(); - var documentOptionSet = await context.Document.GetOptionsAsync(context.CancellationToken).ConfigureAwait(false); - - var priority = diagnostic.Severity == DiagnosticSeverity.Hidden - ? CodeActionPriority.Low - : CodeActionPriority.Medium; - - context.RegisterCodeFix( - new MyCodeAction(diagnostic.GetMessage(), priority, c => FixAsync(context.Document, diagnostic, c)), - diagnostic); - } - - protected override async Task FixAllAsync( - Document document, ImmutableArray diagnostics, - SyntaxEditor editor, CancellationToken cancellationToken) - { - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - - foreach (var diagnostic in diagnostics) - { - cancellationToken.ThrowIfCancellationRequested(); - AddEdits(editor, diagnostic, options, cancellationToken); - } - } - - private void AddEdits( - SyntaxEditor editor, Diagnostic diagnostic, - OptionSet options, CancellationToken cancellationToken) - { - var declarationLocation = diagnostic.AdditionalLocations[0]; - var declaration = (TDeclaration)declarationLocation.FindNode(cancellationToken); - - var updatedDeclaration = this.Update(declaration, options) - .WithAdditionalAnnotations(Formatter.Annotation); - - editor.ReplaceNode(declaration, updatedDeclaration); - } - - private TDeclaration Update(TDeclaration declaration, OptionSet options) - { - var preferExpressionBody = GetBody(declaration) != null; - if (preferExpressionBody) - { - GetBody(declaration).TryConvertToExpressionBody(declaration.SyntaxTree.Options, - ExpressionBodyPreference.WhenPossible, out var expressionBody, out var semicolonToken); - - var trailingTrivia = semicolonToken.TrailingTrivia - .Where(t => t.Kind() != SyntaxKind.EndOfLineTrivia) - .Concat(declaration.GetTrailingTrivia()); - semicolonToken = semicolonToken.WithTrailingTrivia(trailingTrivia); - - return WithSemicolonToken( - WithExpressionBody( - WithBody(declaration, null), - expressionBody), - semicolonToken); - } - else - { - return WithSemicolonToken( - WithExpressionBody( - WithGenerateBody(declaration, options), - null), - default(SyntaxToken)); - } - } - - protected abstract bool CreateReturnStatementForExpression(TDeclaration declaration); - - protected abstract SyntaxToken GetSemicolonToken(TDeclaration declaration); - protected abstract ArrowExpressionClauseSyntax GetExpressionBody(TDeclaration declaration); - protected abstract BlockSyntax GetBody(TDeclaration declaration); - - protected abstract TDeclaration WithSemicolonToken(TDeclaration declaration, SyntaxToken token); - protected abstract TDeclaration WithExpressionBody(TDeclaration declaration, ArrowExpressionClauseSyntax expressionBody); - protected abstract TDeclaration WithBody(TDeclaration declaration, BlockSyntax body); - - protected virtual TDeclaration WithGenerateBody( - TDeclaration declaration, OptionSet options) - { - var expressionBody = GetExpressionBody(declaration); - var semicolonToken = GetSemicolonToken(declaration); - var block = expressionBody.ConvertToBlock( - GetSemicolonToken(declaration), - CreateReturnStatementForExpression(declaration)); - - return WithBody(declaration, block); - } - - protected TDeclaration WithAccessorList( - TDeclaration declaration, OptionSet options) - { - var expressionBody = GetExpressionBody(declaration); - var semicolonToken = GetSemicolonToken(declaration); - - var expressionBodyPreference = options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors).Value; - - AccessorDeclarationSyntax accessor; - if (expressionBodyPreference != ExpressionBodyPreference.Never) - { - accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithExpressionBody(expressionBody) - .WithSemicolonToken(semicolonToken); - } - else - { - var block = expressionBody.ConvertToBlock( - GetSemicolonToken(declaration), - CreateReturnStatementForExpression(declaration)); - accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, block); - } - - return WithAccessorList(declaration, SyntaxFactory.AccessorList( - SyntaxFactory.SingletonList(accessor))); - } - - protected virtual TDeclaration WithAccessorList(TDeclaration declaration, AccessorListSyntax accessorListSyntax) - { - throw new NotImplementedException(); - } - - private class MyCodeAction : CodeAction.DocumentChangeAction - { - internal override CodeActionPriority Priority { get; } - - public MyCodeAction(string title, CodeActionPriority priority, Func> createChangedDocument) - : base(title, createChangedDocument) - { - this.Priority = priority; - } - } - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyDiagnosticAnalyzer.cs deleted file mode 100644 index 778d9f0565173..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/AbstractUseExpressionBodyDiagnosticAnalyzer.cs +++ /dev/null @@ -1,172 +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.Immutable; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - internal abstract class AbstractUseExpressionBodyDiagnosticAnalyzer : - AbstractCodeStyleDiagnosticAnalyzer - where TDeclaration : SyntaxNode - { - private readonly ImmutableArray _syntaxKinds; - private readonly Option> _option; - private readonly LocalizableString _expressionBodyTitle; - private readonly LocalizableString _blockBodyTitle; - - public override bool OpenFileOnly(Workspace workspace) => false; - - protected AbstractUseExpressionBodyDiagnosticAnalyzer( - string diagnosticId, - LocalizableString expressionBodyTitle, - LocalizableString blockBodyTitle, - ImmutableArray syntaxKinds, - Option> option) - : base(diagnosticId, expressionBodyTitle) - { - _syntaxKinds = syntaxKinds; - _option = option; - _expressionBodyTitle = expressionBodyTitle; - _blockBodyTitle = blockBodyTitle; - } - - public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; - - protected override void InitializeWorker(AnalysisContext context) - => context.RegisterSyntaxNodeAction(AnalyzeSyntax, _syntaxKinds); - - private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) - { - var options = context.Options; - var syntaxTree = context.Node.SyntaxTree; - var cancellationToken = context.CancellationToken; - var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); - if (optionSet == null) - { - return; - } - - var diagnostic = AnalyzeSyntax(optionSet, (TDeclaration)context.Node); - if (diagnostic != null) - { - context.ReportDiagnostic(diagnostic); - } - } - - internal virtual Diagnostic AnalyzeSyntax(OptionSet optionSet, TDeclaration declaration) - { - // Note: we will always offer to convert a block to an expression-body (and vice versa) - // if possible. All the user preference does is determine if we show them anything in - // the UI (i.e. suggestion dots, or a squiggle) to let them know that they can make the - // change. - // - // This way, users can turn off the option so they don't get notified, but they can still - // make the transformation on a case by case basis. - // - // Note: if we decide to hide any adornments, then we also broaden the location where the - // fix is available. That way the user can go to any place in the member and choose to - // convert it. Otherwise, they'd have no idea what the 'right' location was to invoke - // the conversion. - // - // Also, if the diagnostic is hidden, we'll lower the priority of the code action. We - // always want it to be available. But we don't want it to override issues that are - // actually being reported in the UI. - - var preferExpressionBodiedOption = optionSet.GetOption(_option); - - var expressionBody = GetExpressionBody(declaration); - - if (expressionBody == null) - { - // They don't have an expression body. See if we can convert into one. - // If so, offer the conversion (with the proper severity depending on their options). - var options = declaration.SyntaxTree.Options; - var body = GetBody(declaration); - if (body.TryConvertToExpressionBody(options, ExpressionBodyPreference.WhenOnSingleLine, out var expressionWhenOnSingleLine, out var semicolonWhenOnSingleLine)) - { - // See if it can be converted to an expression and is on a single line. If so, - // we'll show the diagnostic if either 'use expression body' preference is set. - var severity = - preferExpressionBodiedOption.Value == ExpressionBodyPreference.WhenOnSingleLine || preferExpressionBodiedOption.Value == ExpressionBodyPreference.WhenPossible - ? preferExpressionBodiedOption.Notification.Value - : DiagnosticSeverity.Hidden; - - return GetDiagnostic(declaration, severity); - } - - if (body.TryConvertToExpressionBody(options, ExpressionBodyPreference.WhenPossible, out var expressionWhenPossible, out var semicolonWhenPossible)) - { - // It wasn't an expression that was on a single line. But it was something we - // could convert to an expression body. We'll show the diagnostic only if - // the option to report when possible is set. - var severity = - preferExpressionBodiedOption.Value == ExpressionBodyPreference.WhenPossible - ? preferExpressionBodiedOption.Notification.Value - : DiagnosticSeverity.Hidden; - - return GetDiagnostic(declaration, severity); - } - - // Can't be converted. - return null; - } - else - { - // They have an expression body. These can always be converted into blocks. - // Offer to convert this to a block, with the appropriate severity based - // on their options. - var severity = preferExpressionBodiedOption.Value != ExpressionBodyPreference.Never - ? DiagnosticSeverity.Hidden - : preferExpressionBodiedOption.Notification.Value; - - var location = severity == DiagnosticSeverity.Hidden - ? declaration.GetLocation() - : expressionBody.GetLocation(); - - var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); - return Diagnostic.Create( - CreateDescriptorWithTitle(_blockBodyTitle, severity, GetCustomTags(severity)), - location, additionalLocations: additionalLocations); - } - } - - private Diagnostic GetDiagnostic(TDeclaration declaration, DiagnosticSeverity severity) - { - var location = severity == DiagnosticSeverity.Hidden - ? declaration.GetLocation() - : GetBody(declaration).Statements[0].GetLocation(); - - var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); - return Diagnostic.Create( - CreateDescriptorWithTitle(_expressionBodyTitle, severity, GetCustomTags(severity)), - location, additionalLocations: additionalLocations); - } - - private static string[] GetCustomTags(DiagnosticSeverity severity) - => severity == DiagnosticSeverity.Hidden - ? new[] { WellKnownDiagnosticTags.NotConfigurable } - : Array.Empty(); - - protected static BlockSyntax GetBodyFromSingleGetAccessor(AccessorListSyntax accessorList) - { - if (accessorList != null && - accessorList.Accessors.Count == 1 && - accessorList.Accessors[0].AttributeLists.Count == 0 && - accessorList.Accessors[0].IsKind(SyntaxKind.GetAccessorDeclaration)) - { - return accessorList.Accessors[0].Body; - } - - return null; - } - - protected abstract BlockSyntax GetBody(TDeclaration declaration); - protected abstract ArrowExpressionClauseSyntax GetExpressionBody(TDeclaration declaration); - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsDiagnosticAnalyzer.cs deleted file mode 100644 index ab61cb708e8bb..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsDiagnosticAnalyzer.cs +++ /dev/null @@ -1,61 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForAccessorsDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - private readonly UseExpressionBodyForPropertiesDiagnosticAnalyzer propertyAnalyzer = new UseExpressionBodyForPropertiesDiagnosticAnalyzer(); - private readonly UseExpressionBodyForIndexersDiagnosticAnalyzer indexerAnalyzer = new UseExpressionBodyForIndexersDiagnosticAnalyzer(); - - public UseExpressionBodyForAccessorsDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForAccessorsDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_accessors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_accessors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedAccessors) - { - } - - protected override BlockSyntax GetBody(AccessorDeclarationSyntax declaration) - => declaration.Body; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(AccessorDeclarationSyntax declaration) - => declaration.ExpressionBody; - - internal override Diagnostic AnalyzeSyntax(OptionSet optionSet, AccessorDeclarationSyntax accessor) - { - // We don't want to double report. So don't report a diagnostic if the property/indexer - // analyzer is going to report a diagnostic here. - var grandParent = accessor.Parent.Parent; - - if (grandParent.IsKind(SyntaxKind.PropertyDeclaration)) - { - var propertyDeclaration = (PropertyDeclarationSyntax)grandParent; - var diagnostic = propertyAnalyzer.AnalyzeSyntax(optionSet, propertyDeclaration); - if (diagnostic != null && diagnostic.Severity != DiagnosticSeverity.Hidden) - { - return null; - } - } - else if (grandParent.IsKind(SyntaxKind.IndexerDeclaration)) - { - var indexerDeclaration = (IndexerDeclarationSyntax)grandParent; - var diagnostic = indexerAnalyzer.AnalyzeSyntax(optionSet, indexerDeclaration); - if (diagnostic != null && diagnostic.Severity != DiagnosticSeverity.Hidden) - { - return null; - } - } - - return base.AnalyzeSyntax(optionSet, accessor); - } - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsDiagnosticAnalyzer.cs deleted file mode 100644 index 4c4164e556491..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForConstructorsDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForConstructorsDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForConstructorsDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_constructors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_constructors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.ConstructorDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedConstructors) - { - } - - protected override BlockSyntax GetBody(ConstructorDeclarationSyntax declaration) - => declaration.Body; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(ConstructorDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs similarity index 68% rename from src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs index 773fe62a663dd..e6d8d4e046e17 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Accessors/UseExpressionBodyForAccessorsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs @@ -1,32 +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.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForAccessorsCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForAccessorsHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForAccessorsCodeFixProvider() + public static readonly UseExpressionBodyForAccessorsHelper Instance = new UseExpressionBodyForAccessorsHelper(); + + private UseExpressionBodyForAccessorsHelper() : base(IDEDiagnosticIds.UseExpressionBodyForAccessorsDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_accessors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_accessors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, - FeaturesResources.Use_expression_body_for_accessors, - FeaturesResources.Use_block_body_for_accessors) + ImmutableArray.Create(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(AccessorDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(AccessorDeclarationSyntax declaration) + => declaration.Body; protected override ArrowExpressionClauseSyntax GetExpressionBody(AccessorDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(AccessorDeclarationSyntax declaration) - => declaration.Body; + protected override SyntaxToken GetSemicolonToken(AccessorDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override AccessorDeclarationSyntax WithSemicolonToken(AccessorDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConstructorsHelper.cs similarity index 68% rename from src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConstructorsHelper.cs index e6a68134537c8..dbbea2f234c9e 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Constructors/UseExpressionBodyForConstructorsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConstructorsHelper.cs @@ -1,32 +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.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForConstructorsCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForConstructorsHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForConstructorsCodeFixProvider() + public static readonly UseExpressionBodyForConstructorsHelper Instance = new UseExpressionBodyForConstructorsHelper(); + + private UseExpressionBodyForConstructorsHelper() : base(IDEDiagnosticIds.UseExpressionBodyForConstructorsDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_constructors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_constructors), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, - FeaturesResources.Use_expression_body_for_constructors, - FeaturesResources.Use_block_body_for_constructors) + ImmutableArray.Create(SyntaxKind.ConstructorDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(ConstructorDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(ConstructorDeclarationSyntax declaration) + => declaration.Body; protected override ArrowExpressionClauseSyntax GetExpressionBody(ConstructorDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(ConstructorDeclarationSyntax declaration) - => declaration.Body; + protected override SyntaxToken GetSemicolonToken(ConstructorDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override ConstructorDeclarationSyntax WithSemicolonToken(ConstructorDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConversionOperatorsHelper.cs similarity index 68% rename from src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConversionOperatorsHelper.cs index 5ce5e92d575c6..9c485bc20365b 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForConversionOperatorsHelper.cs @@ -1,32 +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.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForConversionOperatorsCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForConversionOperatorsHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForConversionOperatorsCodeFixProvider() + public static readonly UseExpressionBodyForConversionOperatorsHelper Instance = new UseExpressionBodyForConversionOperatorsHelper(); + + private UseExpressionBodyForConversionOperatorsHelper() : base(IDEDiagnosticIds.UseExpressionBodyForConversionOperatorsDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedOperators, - FeaturesResources.Use_expression_body_for_operators, - FeaturesResources.Use_block_body_for_operators) + ImmutableArray.Create(SyntaxKind.ConversionOperatorDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(ConversionOperatorDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(ConversionOperatorDeclarationSyntax declaration) + => declaration.Body; protected override ArrowExpressionClauseSyntax GetExpressionBody(ConversionOperatorDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(ConversionOperatorDeclarationSyntax declaration) - => declaration.Body; + protected override SyntaxToken GetSemicolonToken(ConversionOperatorDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override ConversionOperatorDeclarationSyntax WithSemicolonToken(ConversionOperatorDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); @@ -40,5 +42,4 @@ protected override ConversionOperatorDeclarationSyntax WithBody(ConversionOperat protected override bool CreateReturnStatementForExpression(ConversionOperatorDeclarationSyntax declaration) => true; } - } \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForIndexersHelper.cs similarity index 73% rename from src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForIndexersHelper.cs index 310f7b1af3a00..8150a2ef945ed 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForIndexersHelper.cs @@ -1,8 +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.Composition; -using Microsoft.CodeAnalysis.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -10,25 +9,28 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForIndexersCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForIndexersHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForIndexersCodeFixProvider() + public static readonly UseExpressionBodyForIndexersHelper Instance = new UseExpressionBodyForIndexersHelper(); + + private UseExpressionBodyForIndexersHelper() : base(IDEDiagnosticIds.UseExpressionBodyForIndexersDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_indexers), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_indexers), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, - FeaturesResources.Use_expression_body_for_indexers, - FeaturesResources.Use_block_body_for_indexers) + ImmutableArray.Create(SyntaxKind.IndexerDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(IndexerDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(IndexerDeclarationSyntax declaration) + => GetBodyFromSingleGetAccessor(declaration.AccessorList); protected override ArrowExpressionClauseSyntax GetExpressionBody(IndexerDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(IndexerDeclarationSyntax declaration) - => declaration.AccessorList?.Accessors[0].Body; + protected override SyntaxToken GetSemicolonToken(IndexerDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override IndexerDeclarationSyntax WithSemicolonToken(IndexerDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForMethodsHelper.cs similarity index 70% rename from src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForMethodsHelper.cs index 34383d537ce6d..57df442633c55 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForMethodsHelper.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.Composition; -using Microsoft.CodeAnalysis.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9,25 +8,28 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForMethodsCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForMethodsHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForMethodsCodeFixProvider() + public static readonly UseExpressionBodyForMethodsHelper Instance = new UseExpressionBodyForMethodsHelper(); + + private UseExpressionBodyForMethodsHelper() : base(IDEDiagnosticIds.UseExpressionBodyForMethodsDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_methods), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_methods), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedMethods, - FeaturesResources.Use_expression_body_for_methods, - FeaturesResources.Use_block_body_for_methods) + ImmutableArray.Create(SyntaxKind.MethodDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(MethodDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(MethodDeclarationSyntax declaration) + => declaration.Body; protected override ArrowExpressionClauseSyntax GetExpressionBody(MethodDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(MethodDeclarationSyntax declaration) - => declaration.Body; + protected override SyntaxToken GetSemicolonToken(MethodDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override MethodDeclarationSyntax WithSemicolonToken(MethodDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); @@ -41,5 +43,4 @@ protected override MethodDeclarationSyntax WithBody(MethodDeclarationSyntax decl protected override bool CreateReturnStatementForExpression(MethodDeclarationSyntax declaration) => !declaration.ReturnType.IsVoid(); } - } \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForOperatorsHelper.cs similarity index 69% rename from src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsCodeFixProvider.cs rename to src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForOperatorsHelper.cs index 752365bcda82c..0aea6f575551d 100644 --- a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForOperatorsHelper.cs @@ -1,32 +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.CodeFixes; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody { - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForOperatorsCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider + internal class UseExpressionBodyForOperatorsHelper : + UseExpressionBodyHelper { - public UseExpressionBodyForOperatorsCodeFixProvider() + public static readonly UseExpressionBodyForOperatorsHelper Instance = new UseExpressionBodyForOperatorsHelper(); + + private UseExpressionBodyForOperatorsHelper() : base(IDEDiagnosticIds.UseExpressionBodyForOperatorsDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), CSharpCodeStyleOptions.PreferExpressionBodiedOperators, - FeaturesResources.Use_expression_body_for_operators, - FeaturesResources.Use_block_body_for_operators) + ImmutableArray.Create(SyntaxKind.OperatorDeclaration)) { } - protected override SyntaxToken GetSemicolonToken(OperatorDeclarationSyntax declaration) - => declaration.SemicolonToken; + protected override BlockSyntax GetBody(OperatorDeclarationSyntax declaration) + => declaration.Body; protected override ArrowExpressionClauseSyntax GetExpressionBody(OperatorDeclarationSyntax declaration) => declaration.ExpressionBody; - protected override BlockSyntax GetBody(OperatorDeclarationSyntax declaration) - => declaration.Body; + protected override SyntaxToken GetSemicolonToken(OperatorDeclarationSyntax declaration) + => declaration.SemicolonToken; protected override OperatorDeclarationSyntax WithSemicolonToken(OperatorDeclarationSyntax declaration, SyntaxToken token) => declaration.WithSemicolonToken(token); @@ -40,5 +42,4 @@ protected override OperatorDeclarationSyntax WithBody(OperatorDeclarationSyntax protected override bool CreateReturnStatementForExpression(OperatorDeclarationSyntax declaration) => true; } - } \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForPropertiesHelper.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForPropertiesHelper.cs new file mode 100644 index 0000000000000..6b499650a4dda --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyForPropertiesHelper.cs @@ -0,0 +1,99 @@ +// 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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + internal class UseExpressionBodyForPropertiesHelper : + UseExpressionBodyHelper + { + public static readonly UseExpressionBodyForPropertiesHelper Instance = new UseExpressionBodyForPropertiesHelper(); + + private UseExpressionBodyForPropertiesHelper() + : base(IDEDiagnosticIds.UseExpressionBodyForPropertiesDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_properties), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_properties), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + CSharpCodeStyleOptions.PreferExpressionBodiedProperties, + ImmutableArray.Create(SyntaxKind.PropertyDeclaration)) + { + } + + protected override BlockSyntax GetBody(PropertyDeclarationSyntax declaration) + => GetBodyFromSingleGetAccessor(declaration.AccessorList); + + protected override ArrowExpressionClauseSyntax GetExpressionBody(PropertyDeclarationSyntax declaration) + => declaration.ExpressionBody; + + protected override SyntaxToken GetSemicolonToken(PropertyDeclarationSyntax declaration) + => declaration.SemicolonToken; + + protected override PropertyDeclarationSyntax WithSemicolonToken(PropertyDeclarationSyntax declaration, SyntaxToken token) + => declaration.WithSemicolonToken(token); + + protected override PropertyDeclarationSyntax WithExpressionBody(PropertyDeclarationSyntax declaration, ArrowExpressionClauseSyntax expressionBody) + => declaration.WithExpressionBody(expressionBody); + + protected override PropertyDeclarationSyntax WithAccessorList(PropertyDeclarationSyntax declaration, AccessorListSyntax accessorListSyntax) + => declaration.WithAccessorList(accessorListSyntax); + + protected override PropertyDeclarationSyntax WithBody(PropertyDeclarationSyntax declaration, BlockSyntax body) + { + if (body == null) + { + return declaration.WithAccessorList(null); + } + + throw new InvalidOperationException(); + } + + protected override PropertyDeclarationSyntax WithGenerateBody( + PropertyDeclarationSyntax declaration, OptionSet options) + { + return WithAccessorList(declaration, options); + } + + protected override bool CreateReturnStatementForExpression(PropertyDeclarationSyntax declaration) => true; + + protected override bool TryConvertToExpressionBody( + PropertyDeclarationSyntax declaration, ParseOptions options, + ExpressionBodyPreference conversionPreference, + out ArrowExpressionClauseSyntax arrowExpression, + out SyntaxToken semicolonToken) + { + if (base.TryConvertToExpressionBody(declaration, options, conversionPreference, out arrowExpression, out semicolonToken)) + { + return true; + } + + var getAccessor = GetSingleGetAccessor(declaration.AccessorList); + if (getAccessor?.ExpressionBody != null && + BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) + { + arrowExpression = SyntaxFactory.ArrowExpressionClause(getAccessor.ExpressionBody.Expression); + semicolonToken = getAccessor.SemicolonToken; + return true; + } + + return false; + } + + protected override Location GetDiagnosticLocation(PropertyDeclarationSyntax declaration) + { + var body = GetBody(declaration); + if (body != null) + { + return base.GetDiagnosticLocation(declaration); + } + + var getAccessor = GetSingleGetAccessor(declaration.AccessorList); + return getAccessor.ExpressionBody.GetLocation(); + } + } +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper.cs new file mode 100644 index 0000000000000..db0e707d6d937 --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + internal abstract class UseExpressionBodyHelper + { + public abstract Option> Option { get; } + public abstract LocalizableString UseExpressionBodyTitle { get; } + public abstract LocalizableString UseBlockBodyTitle { get; } + public abstract string DiagnosticId { get; } + public abstract ImmutableArray SyntaxKinds { get; } + + public abstract BlockSyntax GetBody(SyntaxNode declaration); + public abstract ArrowExpressionClauseSyntax GetExpressionBody(SyntaxNode declaration); + + public abstract bool CanOfferUseExpressionBody(OptionSet optionSet, SyntaxNode declaration, bool forAnalyzer); + public abstract bool CanOfferUseBlockBody(OptionSet optionSet, SyntaxNode declaration, bool forAnalyzer); + public abstract SyntaxNode Update(SyntaxNode declaration, OptionSet options, bool useExpressionBody); + + public abstract Location GetDiagnosticLocation(SyntaxNode declaration); + + public static readonly ImmutableArray Helpers = + ImmutableArray.Create( + UseExpressionBodyForConstructorsHelper.Instance, + UseExpressionBodyForConversionOperatorsHelper.Instance, + UseExpressionBodyForIndexersHelper.Instance, + UseExpressionBodyForMethodsHelper.Instance, + UseExpressionBodyForOperatorsHelper.Instance, + UseExpressionBodyForPropertiesHelper.Instance, + UseExpressionBodyForAccessorsHelper.Instance); + } +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs new file mode 100644 index 0000000000000..c1e823e78a338 --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs @@ -0,0 +1,225 @@ +// 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 Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + /// + /// Helper class that allows us to share lots of logic between the diagnostic analyzer and the + /// code refactoring provider. Those can't share a common base class due to their own inheritance + /// requirements with and . + /// + internal abstract class UseExpressionBodyHelper : UseExpressionBodyHelper + where TDeclaration : SyntaxNode + { + public override Option> Option { get; } + public override LocalizableString UseExpressionBodyTitle { get; } + public override LocalizableString UseBlockBodyTitle { get; } + public override string DiagnosticId { get; } + public override ImmutableArray SyntaxKinds { get; } + + protected UseExpressionBodyHelper( + string diagnosticId, + LocalizableString useExpressionBodyTitle, + LocalizableString useBlockBodyTitle, + Option> option, + ImmutableArray syntaxKinds) + { + DiagnosticId = diagnosticId; + Option = option; + UseExpressionBodyTitle = useExpressionBodyTitle; + UseBlockBodyTitle = useBlockBodyTitle; + SyntaxKinds = syntaxKinds; + } + + protected static AccessorDeclarationSyntax GetSingleGetAccessor(AccessorListSyntax accessorList) + { + if (accessorList != null && + accessorList.Accessors.Count == 1 && + accessorList.Accessors[0].AttributeLists.Count == 0 && + accessorList.Accessors[0].IsKind(SyntaxKind.GetAccessorDeclaration)) + { + return accessorList.Accessors[0]; + } + + return null; + } + + + protected static BlockSyntax GetBodyFromSingleGetAccessor(AccessorListSyntax accessorList) + => GetSingleGetAccessor(accessorList)?.Body; + + public override BlockSyntax GetBody(SyntaxNode declaration) + => GetBody((TDeclaration)declaration); + + public override ArrowExpressionClauseSyntax GetExpressionBody(SyntaxNode declaration) + => GetExpressionBody((TDeclaration)declaration); + + public override bool CanOfferUseExpressionBody(OptionSet optionSet, SyntaxNode declaration, bool forAnalyzer) + => CanOfferUseExpressionBody(optionSet, (TDeclaration)declaration, forAnalyzer); + + public override bool CanOfferUseBlockBody(OptionSet optionSet, SyntaxNode declaration, bool forAnalyzer) + => CanOfferUseBlockBody(optionSet, (TDeclaration)declaration, forAnalyzer); + + public override SyntaxNode Update(SyntaxNode declaration, OptionSet options, bool useExpressionBody) + => Update((TDeclaration)declaration, options, useExpressionBody); + + public override Location GetDiagnosticLocation(SyntaxNode declaration) + => GetDiagnosticLocation((TDeclaration)declaration); + + protected virtual Location GetDiagnosticLocation(TDeclaration declaration) + => this.GetBody(declaration).Statements[0].GetLocation(); + + public bool CanOfferUseExpressionBody( + OptionSet optionSet, TDeclaration declaration, bool forAnalyzer) + { + var preference = optionSet.GetOption(this.Option).Value; + var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; + + // If the user likes expression bodies, then we offer expression bodies from the diagnostic analyzer. + // If the user does not like expression bodies then we offer expression bodies from the refactoring provider. + if (userPrefersExpressionBodies == forAnalyzer) + { + var expressionBody = this.GetExpressionBody(declaration); + if (expressionBody == null) + { + // They don't have an expression body. See if we could convert the block they + // have into one. + + var options = declaration.SyntaxTree.Options; + var conversionPreference = forAnalyzer ? preference : ExpressionBodyPreference.WhenPossible; + + return TryConvertToExpressionBody(declaration, options, conversionPreference, + out var expressionWhenOnSingleLine, out var semicolonWhenOnSingleLine); + } + } + + return false; + } + + protected virtual bool TryConvertToExpressionBody( + TDeclaration declaration, + ParseOptions options, ExpressionBodyPreference conversionPreference, + out ArrowExpressionClauseSyntax expressionWhenOnSingleLine, + out SyntaxToken semicolonWhenOnSingleLine) + { + var body = this.GetBody(declaration); + + return body.TryConvertToExpressionBody(options, conversionPreference, + out expressionWhenOnSingleLine, out semicolonWhenOnSingleLine); + } + + public virtual bool CanOfferUseBlockBody( + OptionSet optionSet, TDeclaration declaration, bool forAnalyzer) + { + var preference = optionSet.GetOption(this.Option).Value; + var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; + + // If the user likes block bodies, then we offer block bodies from the diagnostic analyzer. + // If the user does not like block bodies then we offer block bodies from the refactoring provider. + if (userPrefersBlockBodies == forAnalyzer) + { + return this.GetExpressionBody(declaration)?.TryConvertToBlock( + SyntaxFactory.Token(SyntaxKind.SemicolonToken), false, out var block) == true; + } + + return false; + } + + public TDeclaration Update(TDeclaration declaration, OptionSet options, bool useExpressionBody) + { + if (useExpressionBody) + { + TryConvertToExpressionBody(declaration, declaration.SyntaxTree.Options, + ExpressionBodyPreference.WhenPossible, out var expressionBody, out var semicolonToken); + + var trailingTrivia = semicolonToken.TrailingTrivia + .Where(t => t.Kind() != SyntaxKind.EndOfLineTrivia) + .Concat(declaration.GetTrailingTrivia()); + semicolonToken = semicolonToken.WithTrailingTrivia(trailingTrivia); + + return WithSemicolonToken( + WithExpressionBody( + WithBody(declaration, null), + expressionBody), + semicolonToken); + } + else + { + return WithSemicolonToken( + WithExpressionBody( + WithGenerateBody(declaration, options), + null), + default(SyntaxToken)); + } + } + + protected abstract BlockSyntax GetBody(TDeclaration declaration); + + protected abstract ArrowExpressionClauseSyntax GetExpressionBody(TDeclaration declaration); + + protected abstract bool CreateReturnStatementForExpression(TDeclaration declaration); + + protected abstract SyntaxToken GetSemicolonToken(TDeclaration declaration); + + protected abstract TDeclaration WithSemicolonToken(TDeclaration declaration, SyntaxToken token); + protected abstract TDeclaration WithExpressionBody(TDeclaration declaration, ArrowExpressionClauseSyntax expressionBody); + protected abstract TDeclaration WithBody(TDeclaration declaration, BlockSyntax body); + + protected virtual TDeclaration WithGenerateBody( + TDeclaration declaration, OptionSet options) + { + var expressionBody = GetExpressionBody(declaration); + var semicolonToken = GetSemicolonToken(declaration); + + if (expressionBody.TryConvertToBlock( + GetSemicolonToken(declaration), + CreateReturnStatementForExpression(declaration), + out var block)) + { + return WithBody(declaration, block); + } + + return declaration; + } + + protected TDeclaration WithAccessorList( + TDeclaration declaration, OptionSet options) + { + var expressionBody = GetExpressionBody(declaration); + var semicolonToken = GetSemicolonToken(declaration); + + var expressionBodyPreference = options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors).Value; + + AccessorDeclarationSyntax accessor; + if (expressionBodyPreference != ExpressionBodyPreference.Never || + !expressionBody.TryConvertToBlock(GetSemicolonToken(declaration), CreateReturnStatementForExpression(declaration), out var block)) + { + accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithExpressionBody(expressionBody) + .WithSemicolonToken(semicolonToken); + } + else + { + accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, block); + } + + return WithAccessorList(declaration, SyntaxFactory.AccessorList( + SyntaxFactory.SingletonList(accessor))); + } + + protected virtual TDeclaration WithAccessorList(TDeclaration declaration, AccessorListSyntax accessorListSyntax) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersDiagnosticAnalyzer.cs deleted file mode 100644 index d364161cd46bd..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Indexers/UseExpressionBodyForIndexersDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForIndexersDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForIndexersDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForIndexersDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_indexers), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_indexers), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.IndexerDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedIndexers) - { - } - - protected override BlockSyntax GetBody(IndexerDeclarationSyntax declaration) - => GetBodyFromSingleGetAccessor(declaration.AccessorList); - - protected override ArrowExpressionClauseSyntax GetExpressionBody(IndexerDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsDiagnosticAnalyzer.cs deleted file mode 100644 index 9acf7f481a05d..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Methods/UseExpressionBodyForMethodsDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForMethodsDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForMethodsDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForMethodsDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_methods), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_methods), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.MethodDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedMethods) - { - } - - protected override BlockSyntax GetBody(MethodDeclarationSyntax declaration) - => declaration.Body; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(MethodDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer.cs deleted file mode 100644 index 5f5201ba5c787..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForConversionOperatorsDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForConversionOperatorsDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.ConversionOperatorDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedOperators) - { - } - - protected override BlockSyntax GetBody(ConversionOperatorDeclarationSyntax declaration) - => declaration.Body; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(ConversionOperatorDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsDiagnosticAnalyzer.cs deleted file mode 100644 index 70ff159322588..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Operators/UseExpressionBodyForOperatorsDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForOperatorsDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForOperatorsDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForOperatorsDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_operators), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.OperatorDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedOperators) - { - } - - protected override BlockSyntax GetBody(OperatorDeclarationSyntax declaration) - => declaration.Body; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(OperatorDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesCodeFixProvider.cs deleted file mode 100644 index 2cc4621a4c22a..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesCodeFixProvider.cs +++ /dev/null @@ -1,62 +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.Composition; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - - [ExportCodeFixProvider(LanguageNames.CSharp), Shared] - internal class UseExpressionBodyForPropertiesCodeFixProvider : AbstractUseExpressionBodyCodeFixProvider - { - public UseExpressionBodyForPropertiesCodeFixProvider() - : base(IDEDiagnosticIds.UseExpressionBodyForPropertiesDiagnosticId, - CSharpCodeStyleOptions.PreferExpressionBodiedProperties, - FeaturesResources.Use_expression_body_for_properties, - FeaturesResources.Use_block_body_for_properties) - { - } - - protected override SyntaxToken GetSemicolonToken(PropertyDeclarationSyntax declaration) - => declaration.SemicolonToken; - - protected override ArrowExpressionClauseSyntax GetExpressionBody(PropertyDeclarationSyntax declaration) - => declaration.ExpressionBody; - - protected override BlockSyntax GetBody(PropertyDeclarationSyntax declaration) - => declaration.AccessorList?.Accessors[0].Body; - - protected override PropertyDeclarationSyntax WithSemicolonToken(PropertyDeclarationSyntax declaration, SyntaxToken token) - => declaration.WithSemicolonToken(token); - - protected override PropertyDeclarationSyntax WithExpressionBody(PropertyDeclarationSyntax declaration, ArrowExpressionClauseSyntax expressionBody) - => declaration.WithExpressionBody(expressionBody); - - protected override PropertyDeclarationSyntax WithAccessorList(PropertyDeclarationSyntax declaration, AccessorListSyntax accessorListSyntax) - => declaration.WithAccessorList(accessorListSyntax); - - protected override PropertyDeclarationSyntax WithBody(PropertyDeclarationSyntax declaration, BlockSyntax body) - { - if (body == null) - { - return declaration.WithAccessorList(null); - } - - throw new InvalidOperationException(); - } - - protected override PropertyDeclarationSyntax WithGenerateBody( - PropertyDeclarationSyntax declaration, OptionSet options) - { - return WithAccessorList(declaration, options); - } - - protected override bool CreateReturnStatementForExpression(PropertyDeclarationSyntax declaration) => true; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesDiagnosticAnalyzer.cs deleted file mode 100644 index e5471c390e8ba..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBody/Properties/UseExpressionBodyForPropertiesDiagnosticAnalyzer.cs +++ /dev/null @@ -1,29 +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.Immutable; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseExpressionBodyForPropertiesDiagnosticAnalyzer : - AbstractUseExpressionBodyDiagnosticAnalyzer - { - public UseExpressionBodyForPropertiesDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForPropertiesDiagnosticId, - new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_properties), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_properties), FeaturesResources.ResourceManager, typeof(FeaturesResources)), - ImmutableArray.Create(SyntaxKind.PropertyDeclaration), - CSharpCodeStyleOptions.PreferExpressionBodiedProperties) - { - } - - protected override BlockSyntax GetBody(PropertyDeclarationSyntax declaration) - => GetBodyFromSingleGetAccessor(declaration.AccessorList); - - protected override ArrowExpressionClauseSyntax GetExpressionBody(PropertyDeclarationSyntax declaration) - => declaration.ExpressionBody; - } -} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeFixProvider.cs new file mode 100644 index 0000000000000..61f5f3a4d6d8b --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeFixProvider.cs @@ -0,0 +1,87 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + internal partial class UseExpressionBodyCodeFixProvider : SyntaxEditorBasedCodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } + + private static readonly ImmutableArray _helpers = UseExpressionBodyHelper.Helpers; + + public UseExpressionBodyCodeFixProvider() + { + FixableDiagnosticIds = _helpers.SelectAsArray(h => h.DiagnosticId); + } + + protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic) + => diagnostic.Severity != DiagnosticSeverity.Hidden; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + var documentOptionSet = await context.Document.GetOptionsAsync(context.CancellationToken).ConfigureAwait(false); + + var priority = diagnostic.Severity == DiagnosticSeverity.Hidden + ? CodeActionPriority.Low + : CodeActionPriority.Medium; + + context.RegisterCodeFix( + new MyCodeAction(diagnostic.GetMessage(), priority, c => FixAsync(context.Document, diagnostic, c)), + diagnostic); + } + + protected override async Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) + { + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + foreach (var diagnostic in diagnostics) + { + cancellationToken.ThrowIfCancellationRequested(); + AddEdits(editor, diagnostic, options, cancellationToken); + } + } + + private void AddEdits( + SyntaxEditor editor, Diagnostic diagnostic, + OptionSet options, CancellationToken cancellationToken) + { + var declarationLocation = diagnostic.AdditionalLocations[0]; + var helper = _helpers.Single(h => h.DiagnosticId == diagnostic.Id); + var declaration = declarationLocation.FindNode(cancellationToken); + var useExpressionBody = diagnostic.Properties.ContainsKey(nameof(UseExpressionBody)); + + var updatedDeclaration = helper.Update(declaration, options, useExpressionBody) + .WithAdditionalAnnotations(Formatter.Annotation); + + editor.ReplaceNode(declaration, updatedDeclaration); + } + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + internal override CodeActionPriority Priority { get; } + + public MyCodeAction(string title, CodeActionPriority priority, Func> createChangedDocument) + : base(title, createChangedDocument) + { + this.Priority = priority; + } + } + } +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..c2f4d94fb758b --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyCodeRefactoringProvider.cs @@ -0,0 +1,137 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp), Shared] + internal class UseExpressionBodyCodeRefactoringProvider : CodeRefactoringProvider + { + private static readonly ImmutableArray _helpers = UseExpressionBodyHelper.Helpers; + + public UseExpressionBodyCodeRefactoringProvider() + { + } + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + if (context.Span.Length > 0) + { + return; + } + + var position = context.Span.Start; + var document = context.Document; + var cancellationToken = context.CancellationToken; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var node = root.FindToken(position).Parent; + if (node == null) + { + return; + } + + var containingLambda = node.FirstAncestorOrSelf(); + if (containingLambda != null && + node.AncestorsAndSelf().Contains(containingLambda.Body)) + { + // don't offer inside a lambda. Lambdas can be quite large, and it will be very noisy + // inside the body of one to be offering to use a block/expression body for the containing + // class member. + return; + } + + var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + foreach (var helper in _helpers) + { + var succeeded = TryComputeRefactoring(context, root, node, optionSet, helper); + if (succeeded) + { + return; + } + } + } + + private bool TryComputeRefactoring( + CodeRefactoringContext context, + SyntaxNode root, SyntaxNode node, OptionSet optionSet, + UseExpressionBodyHelper helper) + { + var declaration = GetDeclaration(node, helper); + if (declaration == null) + { + return false; + } + + var document = context.Document; + + bool succeeded = false; + if (helper.CanOfferUseExpressionBody(optionSet, declaration, forAnalyzer: false)) + { + context.RegisterRefactoring(new MyCodeAction( + helper.UseExpressionBodyTitle.ToString(), + c => UpdateDocumentAsync( + document, root, declaration, optionSet, helper, + useExpressionBody: true, cancellationToken: c))); + succeeded = true; + } + + if (helper.CanOfferUseBlockBody(optionSet, declaration, forAnalyzer: false)) + { + context.RegisterRefactoring(new MyCodeAction( + helper.UseBlockBodyTitle.ToString(), + c => UpdateDocumentAsync( + document, root, declaration, optionSet, helper, + useExpressionBody: false, cancellationToken: c))); + succeeded = true; + } + + return succeeded; + } + + private SyntaxNode GetDeclaration(SyntaxNode node, UseExpressionBodyHelper helper) + { + for (var current = node; current != null; current = current.Parent) + { + if (helper.SyntaxKinds.Contains(current.Kind())) + { + return current; + } + } + + return null; + } + + private Task UpdateDocumentAsync( + Document document, SyntaxNode root, SyntaxNode declaration, + OptionSet options, UseExpressionBodyHelper helper, bool useExpressionBody, + CancellationToken cancellationToken) + { + var updatedDeclaration = helper.Update(declaration, options, useExpressionBody) + .WithAdditionalAnnotations(Formatter.Annotation); + var newRoot = root.ReplaceNode(declaration, updatedDeclaration); + + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + } + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(string title, Func> createChangedDocument) + : base(title, createChangedDocument) + { + } + } + } +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..e5c854ebb52a1 --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs @@ -0,0 +1,123 @@ +// 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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class UseExpressionBodyDiagnosticAnalyzer : AbstractCodeStyleDiagnosticAnalyzer + { + private readonly ImmutableArray _syntaxKinds; + + public override ImmutableArray SupportedDiagnostics { get; } + + public override bool OpenFileOnly(Workspace workspace) => false; + + private static readonly ImmutableArray _helpers = UseExpressionBodyHelper.Helpers; + + public UseExpressionBodyDiagnosticAnalyzer() + : base(_helpers[0].DiagnosticId, _helpers[0].UseExpressionBodyTitle) + { + _syntaxKinds = _helpers.SelectMany(h => h.SyntaxKinds).ToImmutableArray(); + SupportedDiagnostics = _helpers.SelectAsArray( + h => CreateDescriptorWithId(h.DiagnosticId, h.UseExpressionBodyTitle, h.UseExpressionBodyTitle, DiagnosticSeverity.Hidden)); + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + => context.RegisterSyntaxNodeAction(AnalyzeSyntax, _syntaxKinds); + + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + var options = context.Options; + var syntaxTree = context.Node.SyntaxTree; + var cancellationToken = context.CancellationToken; + var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); + if (optionSet == null) + { + return; + } + + var nodeKind = context.Node.Kind(); + + // Don't offer a fix on an accessor, if we would also offer it on the property/indexer. + if (UseExpressionBodyForAccessorsHelper.Instance.SyntaxKinds.Contains(nodeKind)) + { + var grandparent = context.Node.Parent.Parent; + + if (grandparent.Kind() == SyntaxKind.PropertyDeclaration && + AnalyzeSyntax(optionSet, grandparent, UseExpressionBodyForPropertiesHelper.Instance) != null) + { + return; + } + + if (grandparent.Kind() == SyntaxKind.IndexerDeclaration && + AnalyzeSyntax(optionSet, grandparent, UseExpressionBodyForIndexersHelper.Instance) != null) + { + return; + } + } + + foreach (var helper in _helpers) + { + if (helper.SyntaxKinds.Contains(nodeKind)) + { + var diagnostic = AnalyzeSyntax(optionSet, context.Node, helper); + if (diagnostic != null) + { + context.ReportDiagnostic(diagnostic); + return; + } + } + } + } + + private Diagnostic AnalyzeSyntax( + OptionSet optionSet, SyntaxNode declaration, UseExpressionBodyHelper helper) + { + var preferExpressionBodiedOption = optionSet.GetOption(helper.Option); + var severity = preferExpressionBodiedOption.Notification.Value; + + if (helper.CanOfferUseExpressionBody(optionSet, declaration, forAnalyzer: true)) + { + var location = severity == DiagnosticSeverity.Hidden + ? declaration.GetLocation() + : helper.GetDiagnosticLocation(declaration); + + var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); + var properties = ImmutableDictionary.Empty.Add(nameof(UseExpressionBody), ""); + return Diagnostic.Create( + CreateDescriptorWithId(helper.DiagnosticId, helper.UseExpressionBodyTitle, helper.UseExpressionBodyTitle, severity, GetCustomTags(severity)), + location, additionalLocations: additionalLocations, properties: properties); + } + + if (helper.CanOfferUseBlockBody(optionSet, declaration, forAnalyzer: true)) + { + // They have an expression body. Create a diagnostic to convert it to a block + // if they don't want expression bodies for this member. + var location = severity == DiagnosticSeverity.Hidden + ? declaration.GetLocation() + : helper.GetExpressionBody(declaration).GetLocation(); + + var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); + return Diagnostic.Create( + CreateDescriptorWithId(helper.DiagnosticId, helper.UseBlockBodyTitle, helper.UseBlockBodyTitle, severity, GetCustomTags(severity)), + location, additionalLocations: additionalLocations); + } + + return null; + } + + private static string[] GetCustomTags(DiagnosticSeverity severity) + => severity == DiagnosticSeverity.Hidden + ? new[] { WellKnownDiagnosticTags.NotConfigurable } + : Array.Empty(); + } +} \ No newline at end of file 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/CodeStyle/AbstractCodeStyleDiagnosticAnalyzer.cs b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleDiagnosticAnalyzer.cs index 4caa80dfa3283..c0e79ff38211a 100644 --- a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleDiagnosticAnalyzer.cs @@ -66,7 +66,7 @@ protected AbstractCodeStyleDiagnosticAnalyzer( HiddenDescriptor, UnnecessaryWithoutSuggestionDescriptor, UnnecessaryWithSuggestionDescriptor); } - public sealed override ImmutableArray SupportedDiagnostics { get; } + public override ImmutableArray SupportedDiagnostics { get; } protected DiagnosticDescriptor GetDescriptorWithSeverity(DiagnosticSeverity severity) { @@ -86,7 +86,7 @@ protected DiagnosticDescriptor CreateDescriptorWithSeverity(DiagnosticSeverity s protected DiagnosticDescriptor CreateDescriptorWithTitle(LocalizableString title, DiagnosticSeverity severity, params string[] customTags) => CreateDescriptorWithId(DescriptorId, title, title, severity, customTags); - private DiagnosticDescriptor CreateDescriptorWithId(string id, LocalizableString title, LocalizableString message, DiagnosticSeverity severity, params string[] customTags) + protected DiagnosticDescriptor CreateDescriptorWithId(string id, LocalizableString title, LocalizableString message, DiagnosticSeverity severity, params string[] customTags) => new DiagnosticDescriptor( id, title, message, DiagnosticCategory.Style, 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/Analyzers/NamingStyleDiagnosticAnalyzerBase.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/NamingStyleDiagnosticAnalyzerBase.cs index 5998cef0c491d..ad196915dd108 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/NamingStyleDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/NamingStyleDiagnosticAnalyzerBase.cs @@ -56,6 +56,11 @@ private void SymbolAction( SymbolAnalysisContext context, ConcurrentDictionary> idToCachedResult) { + if (string.IsNullOrEmpty(context.Symbol.Name)) + { + return; + } + var namingPreferences = context.GetNamingStylePreferencesAsync().GetAwaiter().GetResult(); if (namingPreferences == null) { 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..0a3e386585189 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -23,8 +23,6 @@ internal abstract class AbstractEditAndContinueAnalyzer : IEditAndContinueAnalyz { internal abstract bool ExperimentalFeaturesEnabled(SyntaxTree tree); - internal abstract void ReportSemanticRudeEdits(SemanticModel oldModel, SyntaxNode oldNode, SemanticModel newModel, SyntaxNode newNode, List diagnostics); - /// /// Finds a member declaration node containing given active statement node. /// @@ -244,6 +242,8 @@ protected abstract bool TryMatchActiveStatement( internal abstract void ReportInsertedMemberSymbolRudeEdits(List diagnostics, ISymbol newSymbol); internal abstract void ReportStateMachineSuspensionPointRudeEdits(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode); + internal abstract void ReportMemberBodySemanticRudeEdits(SemanticModel oldModel, SyntaxNode oldMemberBody, SemanticModel newModel, SyntaxNode newMemberBody, List diagnostics); + internal abstract bool IsMethod(SyntaxNode declaration); internal abstract bool IsLambda(SyntaxNode node); internal abstract bool IsLambdaExpression(SyntaxNode node); @@ -337,7 +337,7 @@ public async Task AnalyzeDocumentAsync( var trackingService = baseSolution.Workspace.Services.GetService(); cancellationToken.ThrowIfCancellationRequested(); - + // TODO: newTree.HasErrors? var syntaxDiagnostics = newRoot.GetDiagnostics(); var syntaxErrorCount = syntaxDiagnostics.Count(d => d.Severity == DiagnosticSeverity.Error); @@ -438,19 +438,6 @@ public async Task AnalyzeDocumentAsync( cancellationToken.ThrowIfCancellationRequested(); - // Bail if there were any semantic errors in the compilation. - var newCompilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var firstError = newCompilation.GetDiagnostics(cancellationToken).FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error); - if (firstError != null) - { - var location = firstError.Location; - DocumentAnalysisResults.Log.Write("Semantic errors, first: {0}", location.IsInSource ? location.SourceTree.FilePath : location.MetadataModule.Name); - - return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), ImmutableArray.Create(), hasSemanticErrors: true); - } - - cancellationToken.ThrowIfCancellationRequested(); - var triviaEdits = new List>(); var lineEdits = new List(); @@ -472,6 +459,8 @@ public async Task AnalyzeDocumentAsync( return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), diagnostics.AsImmutable()); } + cancellationToken.ThrowIfCancellationRequested(); + List semanticEdits = null; if (syntacticEdits.Edits.Length > 0 || triviaEdits.Count > 0) { @@ -490,10 +479,19 @@ public async Task AnalyzeDocumentAsync( newModel, semanticEdits, diagnostics, + out var firstDeclaratingErrorOpt, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); + if (firstDeclaratingErrorOpt != null) + { + var location = firstDeclaratingErrorOpt.Location; + DocumentAnalysisResults.Log.Write("Declaration errors, first: {0}", location.IsInSource ? location.SourceTree.FilePath : location.MetadataModule.Name); + + return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), ImmutableArray.Create(), hasSemanticErrors: true); + } + if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0}@{1}: semantic rude edit ({2} total)", document.FilePath, diagnostics.First().Span.Start, diagnostics.Count); @@ -1696,7 +1694,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 +1702,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 +1834,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) { @@ -2095,6 +2093,7 @@ internal void AnalyzeSemantics( SemanticModel newModel, [Out]List semanticEdits, [Out]List diagnostics, + out Diagnostic firstDeclarationErrorOpt, CancellationToken cancellationToken) { // { new type -> constructor update } @@ -2104,6 +2103,7 @@ internal void AnalyzeSemantics( INamedTypeSymbol layoutAttribute = null; var newSymbolsWithEdit = new HashSet(); int updatedMemberIndex = 0; + firstDeclarationErrorOpt = null; for (int i = 0; i < editScript.Edits.Length; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -2135,7 +2135,9 @@ internal void AnalyzeSemantics( continue; } - // Deleting an parameterless constructor needs special handling: + // The only member that is allowed to be deleted is a parameterless constructor. + // For any other member a rude edit is reported earlier during syntax edit classification. + // Deleting a parameterless constructor needs special handling. // If the new type has a parameterless ctor of the same accessibility then UPDATE. // Error otherwise. @@ -2208,12 +2210,24 @@ internal void AnalyzeSemantics( Debug.Assert(oldType != null); Debug.Assert(newType != null); + // Validate that the type declarations are correct. If not we can't reason about their members. + // Declaration diagnostics are cached on compilation, so we don't need to cache them here. + firstDeclarationErrorOpt = + GetFirstDeclarationError(oldModel, oldType, cancellationToken) ?? + GetFirstDeclarationError(newModel, newType, cancellationToken); + + if (firstDeclarationErrorOpt != null) + { + continue; + } + // Inserting a parameterless constructor needs special handling: // 1) static ctor // a) old type has an implicit static ctor // UPDATE of the implicit static ctor // b) otherwise // INSERT of a static parameterless ctor + // // 2) public instance ctor // a) old type has an implicit instance ctor // UPDATE of the implicit instance ctor @@ -2305,16 +2319,31 @@ internal void AnalyzeSemantics( continue; } - ReportSemanticRudeEdits(oldModel, edit.OldNode, newModel, edit.NewNode, diagnostics); - oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken); Debug.Assert((newSymbol == null) == (oldSymbol == null)); + var oldContainingType = oldSymbol.ContainingType; + var newContainingType = newSymbol.ContainingType; + + // Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc. + // Declaration diagnostics are cached on compilation, so we don't need to cache them here. + firstDeclarationErrorOpt = + GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ?? + GetFirstDeclarationError(newModel, newContainingType, cancellationToken); + + if (firstDeclarationErrorOpt != null) + { + continue; + } + if (updatedMemberIndex < updatedMembers.Count && updatedMembers[updatedMemberIndex].EditOrdinal == i) { var updatedMember = updatedMembers[updatedMemberIndex]; + ReportMemberBodySemanticRudeEdits(oldModel, updatedMember.OldBody, newModel, updatedMember.NewBody, diagnostics); + ReportStateMachineRudeEdits(oldModel.Compilation, updatedMember, oldSymbol, diagnostics); + ReportLambdaAndClosureRudeEdits( oldModel, updatedMember.OldBody, @@ -2360,8 +2389,8 @@ internal void AnalyzeSemantics( IsDeclarationWithInitializer(edit.NewNode)) { if (DeferConstructorEdit( - oldSymbol.ContainingType, - newSymbol.ContainingType, + oldContainingType, + newContainingType, editKind, edit.NewNode, newSymbol, @@ -2394,6 +2423,19 @@ internal void AnalyzeSemantics( { var oldSymbol = GetSymbolForEdit(oldModel, edit.Key, EditKind.Update, editMap, cancellationToken); var newSymbol = GetSymbolForEdit(newModel, edit.Value, EditKind.Update, editMap, cancellationToken); + var oldContainingType = oldSymbol.ContainingType; + var newContainingType = newSymbol.ContainingType; + + // Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc. + // Declaration diagnostics are cached on compilation, so we don't need to cache them here. + firstDeclarationErrorOpt = + GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ?? + GetFirstDeclarationError(newModel, newContainingType, cancellationToken); + + if (firstDeclarationErrorOpt != null) + { + continue; + } // We need to provide syntax map to the compiler if the member is active (see member update above): bool isActiveMember = @@ -2412,8 +2454,8 @@ internal void AnalyzeSemantics( IsDeclarationWithInitializer(edit.Value)) { if (DeferConstructorEdit( - oldSymbol.ContainingType, - newSymbol.ContainingType, + oldContainingType, + newContainingType, SemanticEditKind.Update, edit.Value, newSymbol, @@ -2466,6 +2508,31 @@ internal void AnalyzeSemantics( } } + private Diagnostic GetFirstDeclarationError(SemanticModel primaryModel, ISymbol symbol, CancellationToken cancellationToken) + { + foreach (var syntaxReference in symbol.DeclaringSyntaxReferences) + { + SemanticModel model; + if (primaryModel.SyntaxTree == syntaxReference.SyntaxTree) + { + model = primaryModel; + } + else + { + model = primaryModel.Compilation.GetSemanticModel(syntaxReference.SyntaxTree, ignoreAccessibility: false); + } + + var diagnostics = model.GetDeclarationDiagnostics(syntaxReference.Span, cancellationToken); + var firstError = diagnostics.FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error); + if (firstError != null) + { + return firstError; + } + } + + return null; + } + #region Type Layout Update Validation internal void ReportTypeLayoutUpdateRudeEdits( @@ -3684,8 +3751,14 @@ private void ReportStateMachineRudeEdits( return; } - // only methods and anonymous functions may be async/iterators machines: - var stateMachineAttributeQualifiedName = ((IMethodSymbol)oldMember).IsAsync ? + // Only methods and anonymous functions can be async/iterators machines, + // but don't assume so to be resiliant against errors in code. + if (!(oldMember is IMethodSymbol oldMethod)) + { + return; + } + + var stateMachineAttributeQualifiedName = oldMethod.IsAsync ? "System.Runtime.CompilerServices.AsyncStateMachineAttribute" : "System.Runtime.CompilerServices.IteratorStateMachineAttribute"; 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/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs index 441d6398354bd..b60035b93911d 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs @@ -12,16 +12,20 @@ namespace Microsoft.CodeAnalysis.ReplacePropertyWithMethods { - internal abstract class AbstractReplacePropertyWithMethodsService + internal abstract class AbstractReplacePropertyWithMethodsService : IReplacePropertyWithMethodsService where TIdentifierNameSyntax : TExpressionSyntax where TExpressionSyntax : SyntaxNode + where TCrefSyntax : SyntaxNode where TStatementSyntax : SyntaxNode { public abstract SyntaxNode GetPropertyDeclaration(SyntaxToken token); public abstract SyntaxNode GetPropertyNodeToReplace(SyntaxNode propertyDeclaration); public abstract Task> GetReplacementMembersAsync(Document document, IPropertySymbol property, SyntaxNode propertyDeclaration, IFieldSymbol propertyBackingField, string desiredGetMethodName, string desiredSetMethodName, CancellationToken cancellationToken); + protected abstract TCrefSyntax TryGetCrefSyntax(TIdentifierNameSyntax identifierName); + protected abstract TCrefSyntax CreateCrefSyntax(TCrefSyntax originalCref, SyntaxToken identifierToken, SyntaxNode parameterType); + protected abstract TExpressionSyntax UnwrapCompoundAssignment(SyntaxNode compoundAssignment, TExpressionSyntax readExpression); protected static SyntaxNode GetFieldReference(SyntaxGenerator generator, IFieldSymbol propertyBackingField) @@ -62,7 +66,7 @@ public async Task ReplaceReferenceAsync( private struct ReferenceReplacer { - private readonly AbstractReplacePropertyWithMethodsService _service; + private readonly AbstractReplacePropertyWithMethodsService _service; private readonly SemanticModel _semanticModel; private readonly ISyntaxFactsService _syntaxFacts; private readonly ISemanticFactsService _semanticFacts; @@ -75,10 +79,11 @@ private struct ReferenceReplacer private readonly TIdentifierNameSyntax _identifierName; private readonly TExpressionSyntax _expression; + private readonly TCrefSyntax _cref; private readonly CancellationToken _cancellationToken; public ReferenceReplacer( - AbstractReplacePropertyWithMethodsService service, + AbstractReplacePropertyWithMethodsService service, SemanticModel semanticModel, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, @@ -102,6 +107,7 @@ public ReferenceReplacer( _identifierName = (TIdentifierNameSyntax)nameToken.Parent; _expression = _identifierName; + _cref = _service.TryGetCrefSyntax(_identifierName); if (_syntaxFacts.IsNameOfMemberAccessExpression(_expression)) { _expression = _expression.Parent as TExpressionSyntax; @@ -187,7 +193,13 @@ public ReferenceReplacer( public void Do() { - if (_semanticFacts.IsInOutContext(_semanticModel, _expression, _cancellationToken) || + if (_cref != null) + { + // We're in a documentation comment. Replace with a reference to the getter if one exists, + // otherwise to the setter. + _editor.ReplaceNode(_cref, GetCrefReference(_cref)); + } + else if (_semanticFacts.IsInOutContext(_semanticModel, _expression, _cancellationToken) || _semanticFacts.IsInRefContext(_semanticModel, _expression, _cancellationToken)) { // Code wasn't legal (you can't reference a property in an out/ref position in C#). @@ -267,6 +279,24 @@ private void ReplaceWrite( new ReplaceParentArgs(this, getWriteValue, keepTrivia, conflictMessage)); } + private TCrefSyntax GetCrefReference(TCrefSyntax originalCref) + { + SyntaxToken newIdentifierToken; + SyntaxNode parameterType; + if (_property.GetMethod != null) + { + newIdentifierToken = Generator.Identifier(_desiredGetMethodName); + parameterType = null; + } + else + { + newIdentifierToken = Generator.Identifier(_desiredSetMethodName); + parameterType = Generator.TypeExpression(_property.Type); + } + + return _service.CreateCrefSyntax(originalCref, newIdentifierToken, parameterType); + } + private TExpressionSyntax GetReadExpression( bool keepTrivia, string conflictMessage) { diff --git a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs index 7263883b37e90..e4d73048534a6 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs @@ -245,7 +245,7 @@ private static async Task ReplaceReferencesAsync( var property = tuple.property; var referenceLocation = tuple.location; var location = referenceLocation.Location; - var nameToken = root.FindToken(location.SourceSpan.Start); + var nameToken = root.FindToken(location.SourceSpan.Start, findInsideTrivia: true); if (referenceLocation.IsImplicit) { 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..7369bfd880ca4 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 @@ - + @@ -109,7 +110,6 @@ - @@ -395,6 +395,8 @@ + + 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..f88df8ad5e749 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -3155,25 +3155,25 @@ 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)) End Sub - Friend Overrides Sub ReportSemanticRudeEdits(oldModel As SemanticModel, oldNode As SyntaxNode, newModel As SemanticModel, newNode As SyntaxNode, diagnostics As List(Of RudeEditDiagnostic)) + Friend Overrides Sub ReportMemberBodySemanticRudeEdits(oldModel As SemanticModel, oldNode As SyntaxNode, newModel As SemanticModel, newNode As SyntaxNode, diagnostics As List(Of RudeEditDiagnostic)) End Sub #End Region 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/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToParamRewriter.vb b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToParamRewriter.vb new file mode 100644 index 0000000000000..5ab92da12644f --- /dev/null +++ b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToParamRewriter.vb @@ -0,0 +1,39 @@ +' 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.ReplacePropertyWithMethods +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithProperty + Partial Friend Class VisualBasicReplacePropertyWithMethods + Inherits AbstractReplacePropertyWithMethodsService(Of IdentifierNameSyntax, ExpressionSyntax, CrefReferenceSyntax, StatementSyntax) + + Private Class ConvertValueToParamRewriter + Inherits VisualBasicSyntaxRewriter + + Public Shared ReadOnly instance As New ConvertValueToParamRewriter() + + Private Sub New() + End Sub + + Private Function ConvertToParam(name As XmlNodeSyntax) As SyntaxNode + Return name.ReplaceToken(DirectCast(name, XmlNameSyntax).LocalName, + SyntaxFactory.XmlNameToken("param", SyntaxKind.IdentifierToken)) + End Function + + Public Overrides Function VisitXmlElementStartTag(node As XmlElementStartTagSyntax) As SyntaxNode + If Not IsValueName(node.Name) Then + Return MyBase.VisitXmlElementStartTag(node) + End If + + Return node.ReplaceNode(node.Name, ConvertToParam(node.Name)) _ + .AddAttributes(SyntaxFactory.XmlNameAttribute("Value")) + End Function + + Public Overrides Function VisitXmlElementEndTag(node As XmlElementEndTagSyntax) As SyntaxNode + Return If(IsValueName(node.Name), + node.ReplaceNode(node.Name, ConvertToParam(node.Name)), + MyBase.VisitXmlElementEndTag(node)) + End Function + End Class + End Class +End Namespace diff --git a/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.vb b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToReturnsRewriter.vb similarity index 85% rename from src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.vb rename to src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToReturnsRewriter.vb index ea4f860d722e0..8b6ac614c4908 100644 --- a/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/ConvertValueToReturnsRewriter.vb +++ b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.ConvertValueToReturnsRewriter.vb @@ -5,7 +5,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithProperty Partial Friend Class VisualBasicReplacePropertyWithMethods - Inherits AbstractReplacePropertyWithMethodsService(Of IdentifierNameSyntax, ExpressionSyntax, StatementSyntax) + Inherits AbstractReplacePropertyWithMethodsService(Of IdentifierNameSyntax, ExpressionSyntax, CrefReferenceSyntax, StatementSyntax) Private Class ConvertValueToReturnsRewriter Inherits VisualBasicSyntaxRewriter @@ -15,11 +15,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP Private Sub New() End Sub - Private Function IsValueName(node As XmlNodeSyntax) As Boolean - Dim name = TryCast(node, XmlNameSyntax) - Return name?.Prefix Is Nothing AndAlso name?.LocalName.ValueText = "value" - End Function - Private Function ConvertToReturns(name As XmlNodeSyntax) As SyntaxNode Return name.ReplaceToken(DirectCast(name, XmlNameSyntax).LocalName, SyntaxFactory.XmlNameToken("returns", SyntaxKind.IdentifierToken)) diff --git a/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.vb b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.vb index e11298b5d285b..cf53f99d3bb13 100644 --- a/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.vb +++ b/src/Features/VisualBasic/Portable/ReplacePropertyWithMethods/VisualBasicReplacePropertyWithMethods.vb @@ -11,7 +11,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithProperty Partial Friend Class VisualBasicReplacePropertyWithMethods - Inherits AbstractReplacePropertyWithMethodsService(Of IdentifierNameSyntax, ExpressionSyntax, StatementSyntax) + Inherits AbstractReplacePropertyWithMethodsService(Of IdentifierNameSyntax, ExpressionSyntax, CrefReferenceSyntax, StatementSyntax) Public Overrides Function GetPropertyDeclaration(token As SyntaxToken) As SyntaxNode Dim containingProperty = token.Parent.FirstAncestorOrSelf(Of PropertyStatementSyntax) @@ -84,7 +84,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP result.Add(GetGetMethod( generator, propertyStatement, propertyBackingField, getMethod, desiredGetMethodName, - copyLeadingTrivia:=True, cancellationToken:=cancellationToken)) End If @@ -93,7 +92,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP result.Add(GetSetMethod( generator, propertyStatement, propertyBackingField, setMethod, desiredSetMethodName, - copyLeadingTrivia:=getMethod Is Nothing, cancellationToken:=cancellationToken)) End If @@ -106,7 +104,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP propertyBackingField As IFieldSymbol, getMethod As IMethodSymbol, desiredGetMethodName As String, - copyLeadingTrivia As Boolean, cancellationToken As CancellationToken) As SyntaxNode Dim statements = New List(Of SyntaxNode)() @@ -123,7 +120,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP End If Dim methodDeclaration = generator.MethodDeclaration(getMethod, desiredGetMethodName, statements) - methodDeclaration = CopyLeadingTriviaOver(propertyStatement, methodDeclaration, copyLeadingTrivia) + methodDeclaration = CopyLeadingTriviaOver(propertyStatement, methodDeclaration, ConvertValueToReturnsRewriter.instance) Return methodDeclaration End Function @@ -137,7 +134,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP propertyBackingField As IFieldSymbol, setMethod As IMethodSymbol, desiredSetMethodName As String, - copyLeadingTrivia As Boolean, cancellationToken As CancellationToken) As SyntaxNode Dim statements = New List(Of SyntaxNode)() @@ -155,30 +151,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP End If Dim methodDeclaration = generator.MethodDeclaration(setMethod, desiredSetMethodName, statements) - methodDeclaration = CopyLeadingTriviaOver(propertyStatement, methodDeclaration, copyLeadingTrivia) + methodDeclaration = CopyLeadingTriviaOver(propertyStatement, methodDeclaration, ConvertValueToParamRewriter.instance) Return methodDeclaration End Function Private Function CopyLeadingTriviaOver(propertyStatement As PropertyStatementSyntax, methodDeclaration As SyntaxNode, - copyLeadingTrivia As Boolean) As SyntaxNode - If copyLeadingTrivia Then - Return methodDeclaration.WithLeadingTrivia( - propertyStatement.GetLeadingTrivia().Select(AddressOf ConvertTrivia)) - End If - - Return methodDeclaration + documentationCommentRewriter As VisualBasicSyntaxRewriter) As SyntaxNode + Return methodDeclaration.WithLeadingTrivia( + propertyStatement.GetLeadingTrivia().Select(Function(trivia) ConvertTrivia(trivia, documentationCommentRewriter))) End Function - Private Function ConvertTrivia(trivia As SyntaxTrivia) As SyntaxTrivia + Private Function ConvertTrivia(trivia As SyntaxTrivia, documentationCommentRewriter As VisualBasicSyntaxRewriter) As SyntaxTrivia If trivia.Kind() = SyntaxKind.DocumentationCommentTrivia Then - Dim converted = ConvertValueToReturnsRewriter.instance.Visit(trivia.GetStructure()) + Dim converted = documentationCommentRewriter.Visit(trivia.GetStructure()) Return SyntaxFactory.Trivia(DirectCast(converted, StructuredTriviaSyntax)) End If Return trivia End Function + ''' + ''' Used by the documentation comment rewriters to identify top-level <value> nodes. + ''' + Private Shared Function IsValueName(node As XmlNodeSyntax) As Boolean + Dim name = TryCast(node, XmlNameSyntax) + Return name?.Prefix Is Nothing AndAlso name?.LocalName.ValueText = "value" + End Function + Public Overrides Function GetPropertyNodeToReplace(propertyDeclaration As SyntaxNode) As SyntaxNode ' In VB we'll have the property statement. If that is parented by a ' property block, we'll want to replace that instead. Otherwise we @@ -188,6 +188,38 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithP propertyDeclaration) End Function + Protected Overrides Function TryGetCrefSyntax(identifierName As IdentifierNameSyntax) As CrefReferenceSyntax + Dim simpleNameCref = TryCast(identifierName.Parent, CrefReferenceSyntax) + If simpleNameCref IsNot Nothing Then + Return simpleNameCref + End If + + Dim qualifiedName = TryCast(identifierName.Parent, QualifiedNameSyntax) + If qualifiedName Is Nothing Then + Return Nothing + End If + + Return TryCast(qualifiedName.Parent, CrefReferenceSyntax) + End Function + + Protected Overrides Function CreateCrefSyntax(originalCref As CrefReferenceSyntax, identifierToken As SyntaxToken, parameterType As SyntaxNode) As CrefReferenceSyntax + Dim signature As CrefSignatureSyntax + Dim parameterSyntax = TryCast(parameterType, TypeSyntax) + If parameterSyntax IsNot Nothing Then + signature = SyntaxFactory.CrefSignature(SyntaxFactory.CrefSignaturePart(modifier:=Nothing, type:=parameterSyntax)) + Else + signature = SyntaxFactory.CrefSignature() + End If + + Dim typeReference As TypeSyntax = SyntaxFactory.IdentifierName(identifierToken) + Dim qualifiedType = TryCast(originalCref.Name, QualifiedNameSyntax) + If qualifiedType IsNot Nothing Then + typeReference = qualifiedType.ReplaceNode(qualifiedType.GetLastDottedName(), typeReference) + End If + + Return SyntaxFactory.CrefReference(typeReference, signature, asClause:=Nothing) + End Function + Protected Overrides Function UnwrapCompoundAssignment(compoundAssignment As SyntaxNode, readExpression As ExpressionSyntax) As ExpressionSyntax Throw New InvalidOperationException("Compound assignments don't exist in VB") End Function 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/HostTest/InteractiveHostTest.csproj b/src/Interactive/HostTest/InteractiveHostTest.csproj index b3f7f90eba0e8..9c0a27cb635bd 100644 --- a/src/Interactive/HostTest/InteractiveHostTest.csproj +++ b/src/Interactive/HostTest/InteractiveHostTest.csproj @@ -12,6 +12,7 @@ Roslyn.InteractiveHost.UnitTests v4.6 win7-x86 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/CSharp/ConvertToConditional/Test/ConvertToConditionalCS.UnitTests.csproj b/src/Samples/CSharp/ConvertToConditional/Test/ConvertToConditionalCS.UnitTests.csproj index e10fe785d2efe..7b70313d09b41 100644 --- a/src/Samples/CSharp/ConvertToConditional/Test/ConvertToConditionalCS.UnitTests.csproj +++ b/src/Samples/CSharp/ConvertToConditional/Test/ConvertToConditionalCS.UnitTests.csproj @@ -13,6 +13,7 @@ ConvertToConditionalCS.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest diff --git a/src/Samples/CSharp/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedCS.UnitTests.csproj b/src/Samples/CSharp/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedCS.UnitTests.csproj index f62dcf4a0e1e7..70ad8820d4b93 100644 --- a/src/Samples/CSharp/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedCS.UnitTests.csproj +++ b/src/Samples/CSharp/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedCS.UnitTests.csproj @@ -14,6 +14,7 @@ ImplementNotifyPropertyChangedCS.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/Samples/VisualBasic/ConvertToAutoProperty/Test/ConvertToAutoPropertyVB.UnitTests.vbproj b/src/Samples/VisualBasic/ConvertToAutoProperty/Test/ConvertToAutoPropertyVB.UnitTests.vbproj index f6637440812e7..88fdb29f6db85 100644 --- a/src/Samples/VisualBasic/ConvertToAutoProperty/Test/ConvertToAutoPropertyVB.UnitTests.vbproj +++ b/src/Samples/VisualBasic/ConvertToAutoProperty/Test/ConvertToAutoPropertyVB.UnitTests.vbproj @@ -14,6 +14,7 @@ Off v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest $(NoWarn);41999,42016,42030,42104,42108,42109 41998,42004,42020,42021,42022,42026,42029,42031,42105,42106,42107,42353,42354,42355 diff --git a/src/Samples/VisualBasic/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedVB.UnitTests.vbproj b/src/Samples/VisualBasic/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedVB.UnitTests.vbproj index 0a89d35f6c63c..6c278b644f011 100644 --- a/src/Samples/VisualBasic/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedVB.UnitTests.vbproj +++ b/src/Samples/VisualBasic/ImplementNotifyPropertyChanged/Test/ImplementNotifyPropertyChangedVB.UnitTests.vbproj @@ -13,6 +13,7 @@ Default v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest diff --git a/src/Samples/VisualBasic/RemoveByVal/Test/RemoveByValVB.UnitTests.vbproj b/src/Samples/VisualBasic/RemoveByVal/Test/RemoveByValVB.UnitTests.vbproj index 7ac42b03a918c..7a2db6f1b11af 100644 --- a/src/Samples/VisualBasic/RemoveByVal/Test/RemoveByValVB.UnitTests.vbproj +++ b/src/Samples/VisualBasic/RemoveByVal/Test/RemoveByValVB.UnitTests.vbproj @@ -14,6 +14,7 @@ Off v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest $(NoWarn);41999,42016,42030,42104,42108,42109 41998,42004,42020,42021,42022,42026,42029,42031,42105,42106,42107,42353,42354,42355 diff --git a/src/Scripting/CSharpTest.Desktop/CSharpScriptingTest.Desktop.csproj b/src/Scripting/CSharpTest.Desktop/CSharpScriptingTest.Desktop.csproj index 41fef09765405..60f4d64c28c6b 100644 --- a/src/Scripting/CSharpTest.Desktop/CSharpScriptingTest.Desktop.csproj +++ b/src/Scripting/CSharpTest.Desktop/CSharpScriptingTest.Desktop.csproj @@ -14,6 +14,7 @@ true v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest diff --git a/src/Scripting/CSharpTest/CSharpScriptingTest.csproj b/src/Scripting/CSharpTest/CSharpScriptingTest.csproj index 8ca43a6381ac9..7c8e96aad5551 100644 --- a/src/Scripting/CSharpTest/CSharpScriptingTest.csproj +++ b/src/Scripting/CSharpTest/CSharpScriptingTest.csproj @@ -14,7 +14,6 @@ true netstandard1.3 portable-net452 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTestDesktop diff --git a/src/Scripting/CoreTest.Desktop/ScriptingTest.Desktop.csproj b/src/Scripting/CoreTest.Desktop/ScriptingTest.Desktop.csproj index cec3b5794b580..07f34a825aae8 100644 --- a/src/Scripting/CoreTest.Desktop/ScriptingTest.Desktop.csproj +++ b/src/Scripting/CoreTest.Desktop/ScriptingTest.Desktop.csproj @@ -14,6 +14,7 @@ true v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest diff --git a/src/Scripting/CoreTest/ScriptingTest.csproj b/src/Scripting/CoreTest/ScriptingTest.csproj index 3e77db0b4d16e..88fec7f523ddf 100644 --- a/src/Scripting/CoreTest/ScriptingTest.csproj +++ b/src/Scripting/CoreTest/ScriptingTest.csproj @@ -14,7 +14,6 @@ true netstandard1.3 portable-net452 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTestPortable 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.Desktop/BasicScriptingTest.Desktop.vbproj b/src/Scripting/VisualBasicTest.Desktop/BasicScriptingTest.Desktop.vbproj index 43a6e0f83cdd3..01d549bc9fd58 100644 --- a/src/Scripting/VisualBasicTest.Desktop/BasicScriptingTest.Desktop.vbproj +++ b/src/Scripting/VisualBasicTest.Desktop/BasicScriptingTest.Desktop.vbproj @@ -13,6 +13,7 @@ true v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest 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/CSharp/Test/CSharpVisualStudioTest.csproj b/src/VisualStudio/CSharp/Test/CSharpVisualStudioTest.csproj index f036c9fa5b027..34fed6c3a7c3e 100644 --- a/src/VisualStudio/CSharp/Test/CSharpVisualStudioTest.csproj +++ b/src/VisualStudio/CSharp/Test/CSharpVisualStudioTest.csproj @@ -13,6 +13,7 @@ v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/Next/Remote/JsonRpcSession.cs b/src/VisualStudio/Core/Next/Remote/JsonRpcSession.cs index 95318f119232e..4cb8181546bca 100644 --- a/src/VisualStudio/Core/Next/Remote/JsonRpcSession.cs +++ b/src/VisualStudio/Core/Next/Remote/JsonRpcSession.cs @@ -236,7 +236,7 @@ private async Task WriteOneAssetAsync(ObjectWriter writer, Checksum checksum) writer.WriteInt32(1); checksum.WriteTo(writer); - writer.WriteString(remotableData.Kind); + writer.WriteInt32((int)remotableData.Kind); await remotableData.WriteObjectToAsync(writer, _source.Token).ConfigureAwait(false); } @@ -252,7 +252,7 @@ private async Task WriteMultipleAssetsAsync(ObjectWriter writer, Checksum[] chec var remotableData = kv.Value; checksum.WriteTo(writer); - writer.WriteString(remotableData.Kind); + writer.WriteInt32((int)remotableData.Kind); await remotableData.WriteObjectToAsync(writer, _source.Token).ConfigureAwait(false); } 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.Next/VisualStudioTest.Next.csproj b/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj index 81da87450648e..6cb0c1dfabd0c 100644 --- a/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj +++ b/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj @@ -14,6 +14,7 @@ win7 true + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/Core/Test/ServicesVisualStudioTest.vbproj b/src/VisualStudio/Core/Test/ServicesVisualStudioTest.vbproj index ada8ce72e8eb5..67d1e6fcf7af6 100644 --- a/src/VisualStudio/Core/Test/ServicesVisualStudioTest.vbproj +++ b/src/VisualStudio/Core/Test/ServicesVisualStudioTest.vbproj @@ -13,6 +13,7 @@ win7 true + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest true 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/VisualStudioIntegrationTests.csproj b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualStudioIntegrationTests.csproj index 258b08061d755..94fe76840afc8 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualStudioIntegrationTests.csproj +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualStudioIntegrationTests.csproj @@ -12,6 +12,7 @@ v4.6 win7-x86 true + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/ArrowExpressionClauseSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ArrowExpressionClauseSyntaxExtensions.cs index 874495e4dd46d..161837c0c8024 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ArrowExpressionClauseSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ArrowExpressionClauseSyntaxExtensions.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.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -7,14 +8,29 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions { internal static class ArrowExpressionClauseSyntaxExtensions { - public static BlockSyntax ConvertToBlock( + public static bool TryConvertToBlock( this ArrowExpressionClauseSyntax arrowExpression, SyntaxToken semicolonToken, - bool createReturnStatementForExpression) + bool createReturnStatementForExpression, + out BlockSyntax block) { + // It's tricky to convert an arrow expression with directives over to a block. + // We'd need to find and remove the directives *after* the arrow expression and + // move them accordingly. So, for now, we just disallow this. + if (arrowExpression.Expression.GetLeadingTrivia().Any(t => t.IsDirective)) + { + block = null; + return false; + } + var statement = ConvertToStatement(arrowExpression.Expression, semicolonToken, createReturnStatementForExpression); - statement = statement.WithPrependedLeadingTrivia(arrowExpression.ArrowToken.TrailingTrivia); - return SyntaxFactory.Block(statement); + if (arrowExpression.ArrowToken.TrailingTrivia.Any(t => t.IsSingleOrMultiLineComment())) + { + statement = statement.WithPrependedLeadingTrivia(arrowExpression.ArrowToken.TrailingTrivia); + } + + block = SyntaxFactory.Block(statement); + return true; } private static StatementSyntax ConvertToStatement( @@ -29,8 +45,18 @@ private static StatementSyntax ConvertToStatement( } else if (createReturnStatementForExpression) { - return SyntaxFactory.ReturnStatement(expression) - .WithSemicolonToken(semicolonToken); + if (expression.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment())) + { + return SyntaxFactory.ReturnStatement(expression.WithLeadingTrivia(SyntaxFactory.ElasticSpace)) + .WithSemicolonToken(semicolonToken) + .WithLeadingTrivia(expression.GetLeadingTrivia()) + .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker); + } + else + { + return SyntaxFactory.ReturnStatement(expression) + .WithSemicolonToken(semicolonToken); + } } else { diff --git a/src/Workspaces/CSharp/Portable/Extensions/BlockSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/BlockSyntaxExtensions.cs index 8ae96f694fb12..acb5a77d03d19 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/BlockSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/BlockSyntaxExtensions.cs @@ -45,7 +45,7 @@ public static bool TryConvertToExpressionBody( return false; } - private static bool MatchesPreference( + public static bool MatchesPreference( ExpressionSyntax expression, ExpressionBodyPreference preference) { if (preference == ExpressionBodyPreference.WhenPossible) @@ -70,9 +70,9 @@ private static bool TryGetExpression( { if (returnStatement.Expression != null) { - // If there are any comments on the return keyword, move them to + // If there are any comments or directives on the return keyword, move them to // the expression. - expression = firstStatement.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment()) + expression = firstStatement.GetLeadingTrivia().Any(t => t.IsDirective || t.IsSingleOrMultiLineComment()) ? returnStatement.Expression.WithLeadingTrivia(returnStatement.GetLeadingTrivia()) : returnStatement.Expression; semicolonToken = returnStatement.SemicolonToken; 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/CSharpTest/CSharpServicesTest.csproj b/src/Workspaces/CSharpTest/CSharpServicesTest.csproj index da29ffc1b5728..acdef49c089b4 100644 --- a/src/Workspaces/CSharpTest/CSharpServicesTest.csproj +++ b/src/Workspaces/CSharpTest/CSharpServicesTest.csproj @@ -12,6 +12,7 @@ Roslyn.Services.CSharp.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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 internal abstract class CustomAsset : RemotableData { - public CustomAsset(Checksum checksum, string kind) : base(checksum, kind) + public CustomAsset(Checksum checksum, WellKnownSynchronizationKind kind) : base(checksum, kind) { } } @@ -26,7 +26,7 @@ internal sealed class SimpleCustomAsset : CustomAsset { private readonly Action _writer; - public SimpleCustomAsset(string kind, Action writer) : + public SimpleCustomAsset(WellKnownSynchronizationKind kind, Action writer) : base(CreateChecksumFromStreamWriter(kind, writer), kind) { // unlike SolutionAsset which gets checksum from solution states, this one build one by itself. @@ -39,12 +39,12 @@ public override Task WriteObjectToAsync(ObjectWriter writer, CancellationToken c return SpecializedTasks.EmptyTask; } - private static Checksum CreateChecksumFromStreamWriter(string kind, Action writer) + private static Checksum CreateChecksumFromStreamWriter(WellKnownSynchronizationKind kind, Action writer) { using (var stream = SerializableBytes.CreateWritableStream()) using (var objectWriter = new ObjectWriter(stream)) { - objectWriter.WriteString(kind); + objectWriter.WriteInt32((int)kind); writer(objectWriter, CancellationToken.None); return Checksum.Create(stream); } @@ -68,7 +68,7 @@ internal sealed class WorkspaceAnalyzerReferenceAsset : CustomAsset public WorkspaceAnalyzerReferenceAsset(AnalyzerReference reference, Serializer serializer) : base( serializer.CreateChecksum(reference, CancellationToken.None), - WellKnownSynchronizationKinds.AnalyzerReference) + WellKnownSynchronizationKind.AnalyzerReference) { _reference = reference; _serializer = serializer; diff --git a/src/Workspaces/Core/Portable/Execution/CustomAssetBuilder.cs b/src/Workspaces/Core/Portable/Execution/CustomAssetBuilder.cs index cbf54fc1c0cfc..5322d2e0a8c88 100644 --- a/src/Workspaces/Core/Portable/Execution/CustomAssetBuilder.cs +++ b/src/Workspaces/Core/Portable/Execution/CustomAssetBuilder.cs @@ -32,7 +32,7 @@ public CustomAsset Build(OptionSet options, string language, CancellationToken c { cancellationToken.ThrowIfCancellationRequested(); - return new SimpleCustomAsset(WellKnownSynchronizationKinds.OptionSet, + return new SimpleCustomAsset(WellKnownSynchronizationKind.OptionSet, (writer, cancellationTokenOnStreamWriting) => _serializer.SerializeOptionSet(options, language, writer, cancellationTokenOnStreamWriting)); } diff --git a/src/Workspaces/Core/Portable/Execution/Extensions.cs b/src/Workspaces/Core/Portable/Execution/Extensions.cs index 12aced78a25a0..99a1b3c6767ac 100644 --- a/src/Workspaces/Core/Portable/Execution/Extensions.cs +++ b/src/Workspaces/Core/Portable/Execution/Extensions.cs @@ -15,106 +15,30 @@ public static T[] ReadArray(this ObjectReader reader) return (T[])reader.ReadValue(); } - public static string GetWellKnownSynchronizationKind(this object value) + public static WellKnownSynchronizationKind GetWellKnownSynchronizationKind(this object value) { - if (value is SolutionStateChecksums) - { - return WellKnownSynchronizationKinds.SolutionState; - } - - if (value is ProjectStateChecksums) - { - return WellKnownSynchronizationKinds.ProjectState; - } - - if (value is DocumentStateChecksums) - { - return WellKnownSynchronizationKinds.DocumentState; - } - - if (value is ProjectChecksumCollection) - { - return WellKnownSynchronizationKinds.Projects; - } - - if (value is DocumentChecksumCollection) - { - return WellKnownSynchronizationKinds.Documents; - } - - if (value is TextDocumentChecksumCollection) - { - return WellKnownSynchronizationKinds.TextDocuments; - } - - if (value is ProjectReferenceChecksumCollection) - { - return WellKnownSynchronizationKinds.ProjectReferences; - } - - if (value is MetadataReferenceChecksumCollection) - { - return WellKnownSynchronizationKinds.MetadataReferences; - } - - if (value is AnalyzerReferenceChecksumCollection) - { - return WellKnownSynchronizationKinds.AnalyzerReferences; - } - - if (value is SolutionInfo.SolutionAttributes) - { - return WellKnownSynchronizationKinds.SolutionAttributes; - } - - if (value is ProjectInfo.ProjectAttributes) - { - return WellKnownSynchronizationKinds.ProjectAttributes; - } - - if (value is DocumentInfo.DocumentAttributes) - { - return WellKnownSynchronizationKinds.DocumentAttributes; - } - - if (value is CompilationOptions) - { - return WellKnownSynchronizationKinds.CompilationOptions; - } - - if (value is ParseOptions) - { - return WellKnownSynchronizationKinds.ParseOptions; - } - - if (value is ProjectReference) - { - return WellKnownSynchronizationKinds.ProjectReference; - } - - if (value is MetadataReference) - { - return WellKnownSynchronizationKinds.MetadataReference; - } - - if (value is AnalyzerReference) - { - return WellKnownSynchronizationKinds.AnalyzerReference; - } - - if (value is TextDocumentState) - { - return WellKnownSynchronizationKinds.RecoverableSourceText; - } - - if (value is SourceText) - { - return WellKnownSynchronizationKinds.SourceText; - } - - if (value is OptionSet) - { - return WellKnownSynchronizationKinds.OptionSet; + switch (value) + { + case SolutionStateChecksums _: return WellKnownSynchronizationKind.SolutionState; + case ProjectStateChecksums _: return WellKnownSynchronizationKind.ProjectState; + case DocumentStateChecksums _: return WellKnownSynchronizationKind.DocumentState; + case ProjectChecksumCollection _: return WellKnownSynchronizationKind.Projects; + case DocumentChecksumCollection _: return WellKnownSynchronizationKind.Documents; + case TextDocumentChecksumCollection _: return WellKnownSynchronizationKind.TextDocuments; + case ProjectReferenceChecksumCollection _: return WellKnownSynchronizationKind.ProjectReferences; + case MetadataReferenceChecksumCollection _: return WellKnownSynchronizationKind.MetadataReferences; + case AnalyzerReferenceChecksumCollection _: return WellKnownSynchronizationKind.AnalyzerReferences; + case SolutionInfo.SolutionAttributes _: return WellKnownSynchronizationKind.SolutionAttributes; + case ProjectInfo.ProjectAttributes _: return WellKnownSynchronizationKind.ProjectAttributes; + case DocumentInfo.DocumentAttributes _: return WellKnownSynchronizationKind.DocumentAttributes; + case CompilationOptions _: return WellKnownSynchronizationKind.CompilationOptions; + case ParseOptions _: return WellKnownSynchronizationKind.ParseOptions; + case ProjectReference _: return WellKnownSynchronizationKind.ProjectReference; + case MetadataReference _: return WellKnownSynchronizationKind.MetadataReference; + case AnalyzerReference _: return WellKnownSynchronizationKind.AnalyzerReference; + case TextDocumentState _: return WellKnownSynchronizationKind.RecoverableSourceText; + case SourceText _: return WellKnownSynchronizationKind.SourceText; + case OptionSet _: return WellKnownSynchronizationKind.OptionSet; } throw ExceptionUtilities.UnexpectedValue(value); diff --git a/src/Workspaces/Core/Portable/Execution/RemotableData.cs b/src/Workspaces/Core/Portable/Execution/RemotableData.cs index 1dd7218ea3dfc..8b35d69adfad7 100644 --- a/src/Workspaces/Core/Portable/Execution/RemotableData.cs +++ b/src/Workspaces/Core/Portable/Execution/RemotableData.cs @@ -16,19 +16,19 @@ internal abstract partial class RemotableData /// /// Indicates what kind of object it is - /// for examples. + /// for examples. /// /// this will be used in tranportation framework and deserialization service /// to hand shake how to send over data and deserialize serialized data /// - public readonly string Kind; + public readonly WellKnownSynchronizationKind Kind; /// /// Checksum of this object /// public readonly Checksum Checksum; - public RemotableData(Checksum checksum, string kind) + public RemotableData(Checksum checksum, WellKnownSynchronizationKind kind) { Checksum = checksum; Kind = kind; @@ -47,7 +47,7 @@ public RemotableData(Checksum checksum, string kind) private sealed class NullRemotableData : RemotableData { public NullRemotableData() : - base(Checksum.Null, WellKnownSynchronizationKinds.Null) + base(Checksum.Null, WellKnownSynchronizationKind.Null) { // null object has null kind and null checksum. // this null object is known to checksum framework and transportation framework to handle null case diff --git a/src/Workspaces/Core/Portable/Execution/Serializer.cs b/src/Workspaces/Core/Portable/Execution/Serializer.cs index d7d69d89c5bc7..7a4f5cb2d13df 100644 --- a/src/Workspaces/Core/Portable/Execution/Serializer.cs +++ b/src/Workspaces/Core/Portable/Execution/Serializer.cs @@ -52,7 +52,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken { var kind = value.GetWellKnownSynchronizationKind(); - using (Logger.LogBlock(FunctionId.Serializer_CreateChecksum, kind, cancellationToken)) + using (Logger.LogBlock(FunctionId.Serializer_CreateChecksum, kind.ToStringFast(), cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -63,21 +63,21 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken switch (kind) { - case WellKnownSynchronizationKinds.Null: + case WellKnownSynchronizationKind.Null: return Checksum.Null; - case WellKnownSynchronizationKinds.CompilationOptions: - case WellKnownSynchronizationKinds.ParseOptions: - case WellKnownSynchronizationKinds.ProjectReference: + case WellKnownSynchronizationKind.CompilationOptions: + case WellKnownSynchronizationKind.ParseOptions: + case WellKnownSynchronizationKind.ProjectReference: return Checksum.Create(kind, value, this); - case WellKnownSynchronizationKinds.MetadataReference: + case WellKnownSynchronizationKind.MetadataReference: return Checksum.Create(kind, _hostSerializationService.CreateChecksum((MetadataReference)value, cancellationToken)); - case WellKnownSynchronizationKinds.AnalyzerReference: + case WellKnownSynchronizationKind.AnalyzerReference: return Checksum.Create(kind, _hostSerializationService.CreateChecksum((AnalyzerReference)value, cancellationToken)); - case WellKnownSynchronizationKinds.SourceText: + case WellKnownSynchronizationKind.SourceText: return Checksum.Create(kind, ((SourceText)value).GetChecksum()); default: @@ -92,7 +92,7 @@ public void Serialize(object value, ObjectWriter writer, CancellationToken cance { var kind = value.GetWellKnownSynchronizationKind(); - using (Logger.LogBlock(FunctionId.Serializer_Serialize, kind, cancellationToken)) + using (Logger.LogBlock(FunctionId.Serializer_Serialize, kind.ToString(), cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -104,37 +104,37 @@ public void Serialize(object value, ObjectWriter writer, CancellationToken cance switch (kind) { - case WellKnownSynchronizationKinds.Null: + case WellKnownSynchronizationKind.Null: // do nothing return; - case WellKnownSynchronizationKinds.SolutionAttributes: - case WellKnownSynchronizationKinds.ProjectAttributes: - case WellKnownSynchronizationKinds.DocumentAttributes: + case WellKnownSynchronizationKind.SolutionAttributes: + case WellKnownSynchronizationKind.ProjectAttributes: + case WellKnownSynchronizationKind.DocumentAttributes: ((IObjectWritable)value).WriteTo(writer); return; - case WellKnownSynchronizationKinds.CompilationOptions: + case WellKnownSynchronizationKind.CompilationOptions: SerializeCompilationOptions((CompilationOptions)value, writer, cancellationToken); return; - case WellKnownSynchronizationKinds.ParseOptions: + case WellKnownSynchronizationKind.ParseOptions: SerializeParseOptions((ParseOptions)value, writer, cancellationToken); return; - case WellKnownSynchronizationKinds.ProjectReference: + case WellKnownSynchronizationKind.ProjectReference: SerializeProjectReference((ProjectReference)value, writer, cancellationToken); return; - case WellKnownSynchronizationKinds.MetadataReference: + case WellKnownSynchronizationKind.MetadataReference: SerializeMetadataReference((MetadataReference)value, writer, cancellationToken); return; - case WellKnownSynchronizationKinds.AnalyzerReference: + case WellKnownSynchronizationKind.AnalyzerReference: SerializeAnalyzerReference((AnalyzerReference)value, writer, usePathFromAssembly: true, cancellationToken: cancellationToken); return; - case WellKnownSynchronizationKinds.SourceText: + case WellKnownSynchronizationKind.SourceText: SerializeSourceText(storage: null, text: (SourceText)value, writer: writer, cancellationToken: cancellationToken); return; @@ -146,47 +146,47 @@ public void Serialize(object value, ObjectWriter writer, CancellationToken cance } } - public T Deserialize(string kind, ObjectReader reader, CancellationToken cancellationToken) + public T Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.Serializer_Deserialize, kind, cancellationToken)) + using (Logger.LogBlock(FunctionId.Serializer_Deserialize, kind.ToString(), cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); switch (kind) { - case WellKnownSynchronizationKinds.Null: + case WellKnownSynchronizationKind.Null: return default(T); - case WellKnownSynchronizationKinds.SolutionState: - case WellKnownSynchronizationKinds.ProjectState: - case WellKnownSynchronizationKinds.DocumentState: - case WellKnownSynchronizationKinds.Projects: - case WellKnownSynchronizationKinds.Documents: - case WellKnownSynchronizationKinds.TextDocuments: - case WellKnownSynchronizationKinds.ProjectReferences: - case WellKnownSynchronizationKinds.MetadataReferences: - case WellKnownSynchronizationKinds.AnalyzerReferences: + case WellKnownSynchronizationKind.SolutionState: + case WellKnownSynchronizationKind.ProjectState: + case WellKnownSynchronizationKind.DocumentState: + case WellKnownSynchronizationKind.Projects: + case WellKnownSynchronizationKind.Documents: + case WellKnownSynchronizationKind.TextDocuments: + case WellKnownSynchronizationKind.ProjectReferences: + case WellKnownSynchronizationKind.MetadataReferences: + case WellKnownSynchronizationKind.AnalyzerReferences: return (T)(object)DeserializeChecksumWithChildren(reader, cancellationToken); - case WellKnownSynchronizationKinds.SolutionAttributes: + case WellKnownSynchronizationKind.SolutionAttributes: return (T)(object)SolutionInfo.SolutionAttributes.ReadFrom(reader); - case WellKnownSynchronizationKinds.ProjectAttributes: + case WellKnownSynchronizationKind.ProjectAttributes: return (T)(object)ProjectInfo.ProjectAttributes.ReadFrom(reader); - case WellKnownSynchronizationKinds.DocumentAttributes: + case WellKnownSynchronizationKind.DocumentAttributes: return (T)(object)DocumentInfo.DocumentAttributes.ReadFrom(reader); - case WellKnownSynchronizationKinds.CompilationOptions: + case WellKnownSynchronizationKind.CompilationOptions: return (T)(object)DeserializeCompilationOptions(reader, cancellationToken); - case WellKnownSynchronizationKinds.ParseOptions: + case WellKnownSynchronizationKind.ParseOptions: return (T)(object)DeserializeParseOptions(reader, cancellationToken); - case WellKnownSynchronizationKinds.ProjectReference: + case WellKnownSynchronizationKind.ProjectReference: return (T)(object)DeserializeProjectReference(reader, cancellationToken); - case WellKnownSynchronizationKinds.MetadataReference: + case WellKnownSynchronizationKind.MetadataReference: return (T)(object)DeserializeMetadataReference(reader, cancellationToken); - case WellKnownSynchronizationKinds.AnalyzerReference: + case WellKnownSynchronizationKind.AnalyzerReference: return (T)(object)DeserializeAnalyzerReference(reader, cancellationToken); - case WellKnownSynchronizationKinds.SourceText: + case WellKnownSynchronizationKind.SourceText: return (T)(object)DeserializeSourceText(reader, cancellationToken); - case WellKnownSynchronizationKinds.OptionSet: + case WellKnownSynchronizationKind.OptionSet: return (T)(object)DeserializeOptionSet(reader, cancellationToken); default: diff --git a/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumWithChildren.cs b/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumWithChildren.cs index aa977b72fc1f9..1d8cb16bbf208 100644 --- a/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumWithChildren.cs +++ b/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumWithChildren.cs @@ -17,14 +17,14 @@ internal partial class Serializer private const byte ChecksumKind = 0; private const byte ChecksumWithChildrenKind = 1; - private static readonly ImmutableDictionary> s_creatorMap = CreateCreatorMap(); + private static readonly ImmutableDictionary> s_creatorMap = CreateCreatorMap(); public void SerializeChecksumWithChildren(ChecksumWithChildren checksums, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var kind = checksums.GetWellKnownSynchronizationKind(); - writer.WriteString(kind); + writer.WriteInt32((int)kind); checksums.Checksum.WriteTo(writer); writer.WriteInt32(checksums.Children.Count); @@ -54,7 +54,7 @@ private ChecksumWithChildren DeserializeChecksumWithChildren(ObjectReader reader { cancellationToken.ThrowIfCancellationRequested(); - var kind = reader.ReadString(); + var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); var checksum = Checksum.ReadFrom(reader); var childrenCount = reader.ReadInt32(); @@ -84,18 +84,18 @@ private ChecksumWithChildren DeserializeChecksumWithChildren(ObjectReader reader return checksums; } - private static ImmutableDictionary> CreateCreatorMap() + private static ImmutableDictionary> CreateCreatorMap() { - return ImmutableDictionary>.Empty - .Add(WellKnownSynchronizationKinds.SolutionState, children => new SolutionStateChecksums(children)) - .Add(WellKnownSynchronizationKinds.ProjectState, children => new ProjectStateChecksums(children)) - .Add(WellKnownSynchronizationKinds.DocumentState, children => new DocumentStateChecksums(children)) - .Add(WellKnownSynchronizationKinds.Projects, children => new ProjectChecksumCollection(children)) - .Add(WellKnownSynchronizationKinds.Documents, children => new DocumentChecksumCollection(children)) - .Add(WellKnownSynchronizationKinds.TextDocuments, children => new TextDocumentChecksumCollection(children)) - .Add(WellKnownSynchronizationKinds.ProjectReferences, children => new ProjectReferenceChecksumCollection(children)) - .Add(WellKnownSynchronizationKinds.MetadataReferences, children => new MetadataReferenceChecksumCollection(children)) - .Add(WellKnownSynchronizationKinds.AnalyzerReferences, children => new AnalyzerReferenceChecksumCollection(children)); + return ImmutableDictionary>.Empty + .Add(WellKnownSynchronizationKind.SolutionState, children => new SolutionStateChecksums(children)) + .Add(WellKnownSynchronizationKind.ProjectState, children => new ProjectStateChecksums(children)) + .Add(WellKnownSynchronizationKind.DocumentState, children => new DocumentStateChecksums(children)) + .Add(WellKnownSynchronizationKind.Projects, children => new ProjectChecksumCollection(children)) + .Add(WellKnownSynchronizationKind.Documents, children => new DocumentChecksumCollection(children)) + .Add(WellKnownSynchronizationKind.TextDocuments, children => new TextDocumentChecksumCollection(children)) + .Add(WellKnownSynchronizationKind.ProjectReferences, children => new ProjectReferenceChecksumCollection(children)) + .Add(WellKnownSynchronizationKind.MetadataReferences, children => new MetadataReferenceChecksumCollection(children)) + .Add(WellKnownSynchronizationKind.AnalyzerReferences, children => new AnalyzerReferenceChecksumCollection(children)); } } } diff --git a/src/Workspaces/Core/Portable/Execution/SolutionAsset.cs b/src/Workspaces/Core/Portable/Execution/SolutionAsset.cs index 067df7d7a18b9..86fbf604e2260 100644 --- a/src/Workspaces/Core/Portable/Execution/SolutionAsset.cs +++ b/src/Workspaces/Core/Portable/Execution/SolutionAsset.cs @@ -13,7 +13,8 @@ namespace Microsoft.CodeAnalysis.Execution /// internal abstract class SolutionAsset : RemotableData { - protected SolutionAsset(Checksum checksum, string kind) : base(checksum, kind) + protected SolutionAsset(Checksum checksum, WellKnownSynchronizationKind kind) + : base(checksum, kind) { } @@ -63,7 +64,7 @@ internal sealed class SourceTextAsset : SolutionAsset private readonly Serializer _serializer; public SourceTextAsset(Checksum checksum, TextDocumentState state, Serializer serializer) : - base(checksum, WellKnownSynchronizationKinds.SourceText) + base(checksum, WellKnownSynchronizationKind.SourceText) { _state = state; _serializer = serializer; diff --git a/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKind.cs new file mode 100644 index 0000000000000..78bf14f17ed2b --- /dev/null +++ b/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKind.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Reflection; + +namespace Microsoft.CodeAnalysis.Serialization +{ + // TODO: Kind might not actually needed. see whether we can get rid of this + internal enum WellKnownSynchronizationKind + { + Null, + + SolutionState, + ProjectState, + DocumentState, + + Projects, + Documents, + TextDocuments, + ProjectReferences, + MetadataReferences, + AnalyzerReferences, + + SolutionAttributes, + ProjectAttributes, + DocumentAttributes, + + CompilationOptions, + ParseOptions, + ProjectReference, + MetadataReference, + AnalyzerReference, + SourceText, + OptionSet, + + RecoverableSourceText, + + // + + SyntaxTreeIndex, + SymbolTreeInfo, + + ProjectReferenceChecksumCollection, + MetadataReferenceChecksumCollection, + AnalyzerReferenceChecksumCollection, + TextDocumentChecksumCollection, + DocumentChecksumCollection, + ProjectChecksumCollection, + SolutionStateChecksums, + ProjectStateChecksums, + DocumentStateChecksums, + } + + internal static class WellKnownSynchronizationKindExtensions + { + private static readonly string[] s_strings; + + static WellKnownSynchronizationKindExtensions() + { + var fields = typeof(WellKnownSynchronizationKind).GetTypeInfo().DeclaredFields.Where(f => f.IsStatic); + + var maxValue = fields.Max(f => (int)f.GetValue(null)); + s_strings = new string[maxValue + 1]; + + foreach (var field in fields) + { + var value = (int)field.GetValue(null); + s_strings[value] = field.Name; + } + } + + public static string ToStringFast(this WellKnownSynchronizationKind kind) + => s_strings[(int)kind]; + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKinds.cs b/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKinds.cs deleted file mode 100644 index c81a8793f089f..0000000000000 --- a/src/Workspaces/Core/Portable/Execution/WellKnownSynchronizationKinds.cs +++ /dev/null @@ -1,35 +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. - -namespace Microsoft.CodeAnalysis.Serialization -{ - // TODO: Kind might not actually needed. see whether we can get rid of this - internal static class WellKnownSynchronizationKinds - { - public const string Null = nameof(Null); - - public const string SolutionState = nameof(SolutionStateChecksums); - public const string ProjectState = nameof(ProjectStateChecksums); - public const string DocumentState = nameof(DocumentStateChecksums); - - public const string Projects = nameof(ProjectChecksumCollection); - public const string Documents = nameof(DocumentChecksumCollection); - public const string TextDocuments = nameof(TextDocumentChecksumCollection); - public const string ProjectReferences = nameof(ProjectReferenceChecksumCollection); - public const string MetadataReferences = nameof(MetadataReferenceChecksumCollection); - public const string AnalyzerReferences = nameof(AnalyzerReferenceChecksumCollection); - - public const string SolutionAttributes = nameof(SolutionInfo.SolutionAttributes); - public const string ProjectAttributes = nameof(ProjectInfo.ProjectAttributes); - public const string DocumentAttributes = nameof(DocumentInfo.DocumentAttributes); - - public const string CompilationOptions = nameof(CompilationOptions); - public const string ParseOptions = nameof(ParseOptions); - public const string ProjectReference = nameof(ProjectReference); - public const string MetadataReference = nameof(MetadataReference); - public const string AnalyzerReference = nameof(AnalyzerReference); - public const string SourceText = nameof(SourceText); - public const string OptionSet = nameof(OptionSet); - - public const string RecoverableSourceText = nameof(RecoverableSourceText); - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index 0e8daa8c9ad1b..b7bd6d9184ed3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { internal partial class SymbolTreeInfo { - private readonly VersionStamp _version; + public readonly Checksum Checksum; /// /// 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..f4a1b4c540f13 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(WellKnownSynchronizationKind.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..42f0c9a6bf9ba 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(WellKnownSynchronizationKind.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/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 7e1d0970ebaf7..008ecaeebdf3a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -12,11 +12,11 @@ namespace Microsoft.CodeAnalysis.Serialization /// internal abstract class ChecksumCollection : ChecksumWithChildren, IEnumerable { - protected ChecksumCollection(string kind, Checksum[] checksums) : this(kind, (object[])checksums) + protected ChecksumCollection(WellKnownSynchronizationKind kind, Checksum[] checksums) : this(kind, (object[])checksums) { } - protected ChecksumCollection(string kind, object[] checksums) : base(kind, checksums) + protected ChecksumCollection(WellKnownSynchronizationKind kind, object[] checksums) : base(kind, checksums) { } @@ -38,36 +38,36 @@ IEnumerator IEnumerable.GetEnumerator() internal class ProjectChecksumCollection : ChecksumCollection { public ProjectChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public ProjectChecksumCollection(object[] checksums) : base(nameof(ProjectChecksumCollection), checksums) { } + public ProjectChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.ProjectChecksumCollection, checksums) { } } internal class DocumentChecksumCollection : ChecksumCollection { public DocumentChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public DocumentChecksumCollection(object[] checksums) : base(nameof(DocumentChecksumCollection), checksums) { } + public DocumentChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.DocumentChecksumCollection, checksums) { } } internal class TextDocumentChecksumCollection : ChecksumCollection { public TextDocumentChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public TextDocumentChecksumCollection(object[] checksums) : base(nameof(TextDocumentChecksumCollection), checksums) { } + public TextDocumentChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.TextDocumentChecksumCollection, checksums) { } } internal class ProjectReferenceChecksumCollection : ChecksumCollection { public ProjectReferenceChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public ProjectReferenceChecksumCollection(object[] checksums) : base(nameof(ProjectReferenceChecksumCollection), checksums) { } + public ProjectReferenceChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.ProjectReferenceChecksumCollection, checksums) { } } internal class MetadataReferenceChecksumCollection : ChecksumCollection { public MetadataReferenceChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public MetadataReferenceChecksumCollection(object[] checksums) : base(nameof(MetadataReferenceChecksumCollection), checksums) { } + public MetadataReferenceChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.MetadataReferenceChecksumCollection, checksums) { } } internal class AnalyzerReferenceChecksumCollection : ChecksumCollection { public AnalyzerReferenceChecksumCollection(Checksum[] checksums) : this((object[])checksums) { } - public AnalyzerReferenceChecksumCollection(object[] checksums) : base(nameof(AnalyzerReferenceChecksumCollection), checksums) { } + public AnalyzerReferenceChecksumCollection(object[] checksums) : base(WellKnownSynchronizationKind.AnalyzerReferenceChecksumCollection, checksums) { } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs index d40ba8c5073dd..8a96e582ed5ac 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Serialization /// internal abstract class ChecksumWithChildren : IChecksummedObject { - public ChecksumWithChildren(string kind, params object[] children) + public ChecksumWithChildren(WellKnownSynchronizationKind kind, params object[] children) { Checksum = CreateChecksum(kind, children); Children = children; @@ -20,7 +20,7 @@ public ChecksumWithChildren(string kind, params object[] children) public IReadOnlyList Children { get; } - private static Checksum CreateChecksum(string kind, object[] children) + private static Checksum CreateChecksum(WellKnownSynchronizationKind kind, object[] children) { // given children must be either Checksum or Checksums (collection of a checksum) return Checksum.Create(kind, children.Select(c => c as Checksum ?? ((ChecksumCollection)c).Checksum)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs index 7a9e11b78ef01..e2b80e2d67239 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs @@ -16,32 +16,54 @@ internal partial class Checksum { public static Checksum Create(Stream stream) { - // REVIEW: should we cache SHA1CryptoServiceProvider - using (var algorithm = SHA1.Create()) + using (var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1)) + { + return ComputeChecksum(stream, hash); + } + } + + private static Checksum ComputeChecksum(Stream stream, IncrementalHash hash) + { + using (var pooledBuffer = SharedPools.ByteArray.GetPooledObject()) { stream.Seek(0, SeekOrigin.Begin); - return new Checksum(algorithm.ComputeHash(stream)); + + var buffer = pooledBuffer.Object; + var bufferLength = buffer.Length; + int bytesRead; + do + { + bytesRead = stream.Read(buffer, 0, bufferLength); + if (bytesRead > 0) + { + hash.AppendData(buffer, 0, bytesRead); + } + } + while (bytesRead > 0); + + var bytes = hash.GetHashAndReset(); + return new Checksum(bytes); } } - public static Checksum Create(string kind, IObjectWritable @object) + public static Checksum Create(WellKnownSynchronizationKind kind, IObjectWritable @object) { using (var stream = SerializableBytes.CreateWritableStream()) using (var objectWriter = new ObjectWriter(stream)) { - objectWriter.WriteString(kind); + objectWriter.WriteInt32((int)kind); @object.WriteTo(objectWriter); return Create(stream); } } - public static Checksum Create(string kind, IEnumerable checksums) + public static Checksum Create(WellKnownSynchronizationKind kind, IEnumerable checksums) { using (var stream = SerializableBytes.CreateWritableStream()) using (var writer = new ObjectWriter(stream)) { - writer.WriteString(kind); + writer.WriteInt32((int)kind); foreach (var checksum in checksums) { @@ -52,12 +74,12 @@ public static Checksum Create(string kind, IEnumerable checksums) } } - public static Checksum Create(string kind, ImmutableArray bytes) + public static Checksum Create(WellKnownSynchronizationKind kind, ImmutableArray bytes) { using (var stream = SerializableBytes.CreateWritableStream()) using (var writer = new ObjectWriter(stream)) { - writer.WriteString(kind); + writer.WriteInt32((int)kind); for (var i = 0; i < bytes.Length; i++) { @@ -68,12 +90,12 @@ public static Checksum Create(string kind, ImmutableArray bytes) } } - public static Checksum Create(string kind, T value, Serializer serializer) + public static Checksum Create(WellKnownSynchronizationKind kind, T value, Serializer serializer) { using (var stream = SerializableBytes.CreateWritableStream()) using (var objectWriter = new ObjectWriter(stream)) { - objectWriter.WriteString(kind); + objectWriter.WriteInt32((int)kind); serializer.Serialize(value, objectWriter, CancellationToken.None); return Create(stream); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs index e675cad81bec5..a6ac6503361c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -234,7 +235,7 @@ Checksum IChecksummedObject.Checksum { if (_lazyChecksum == null) { - _lazyChecksum = Checksum.Create(nameof(DocumentAttributes), this); + _lazyChecksum = Checksum.Create(WellKnownSynchronizationKind.DocumentAttributes, this); } return _lazyChecksum; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs index 04d936c2a10e8..f40bcdd9de367 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -474,7 +475,7 @@ Checksum IChecksummedObject.Checksum { if (_lazyChecksum == null) { - _lazyChecksum = Checksum.Create(nameof(ProjectAttributes), this); + _lazyChecksum = Checksum.Create(WellKnownSynchronizationKind.ProjectAttributes, this); } return _lazyChecksum; 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/SolutionInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs index f120edfdc0e83..a76b498de81c8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -138,7 +139,7 @@ Checksum IChecksummedObject.Checksum { if (_lazyChecksum == null) { - _lazyChecksum = Checksum.Create(nameof(SolutionAttributes), this); + _lazyChecksum = Checksum.Create(WellKnownSynchronizationKind.SolutionAttributes, this); } return _lazyChecksum; 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/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index e42e9288a1c3d..bef86d7f5264d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -16,7 +16,7 @@ public SolutionStateChecksums(Checksum infoChecksum, ProjectChecksumCollection p { } - public SolutionStateChecksums(params object[] children) : base(nameof(SolutionStateChecksums), children) + public SolutionStateChecksums(params object[] children) : base(WellKnownSynchronizationKind.SolutionStateChecksums, children) { } @@ -94,7 +94,7 @@ public ProjectStateChecksums( { } - public ProjectStateChecksums(params object[] children) : base(nameof(ProjectStateChecksums), children) + public ProjectStateChecksums(params object[] children) : base(WellKnownSynchronizationKind.ProjectStateChecksums, children) { } @@ -228,7 +228,7 @@ public DocumentStateChecksums(Checksum infoChecksum, Checksum textChecksum) : { } - public DocumentStateChecksums(params object[] children) : base(nameof(DocumentStateChecksums), children) + public DocumentStateChecksums(params object[] children) : base(WellKnownSynchronizationKind.DocumentStateChecksums, children) { } 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..2b343008c486d 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -368,7 +368,7 @@ - + @@ -607,8 +607,6 @@ - - diff --git a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTestBase.cs b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTestBase.cs index c9f89b7fb9bac..832430e20f6ef 100644 --- a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTestBase.cs +++ b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTestBase.cs @@ -39,7 +39,7 @@ internal static Solution CreateFullSolution(HostServices hostServices = null) internal static async Task VerifyAssetAsync(ISolutionSynchronizationService service, SolutionStateChecksums solutionObject) { await VerifyAssetSerializationAsync( - service, solutionObject.Info, WellKnownSynchronizationKinds.SolutionAttributes, + service, solutionObject.Info, WellKnownSynchronizationKind.SolutionAttributes, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)).ConfigureAwait(false); foreach (var projectChecksum in solutionObject.Projects) @@ -52,15 +52,15 @@ internal static async Task VerifyAssetAsync(ISolutionSynchronizationService serv internal static async Task VerifyAssetAsync(ISolutionSynchronizationService service, ProjectStateChecksums projectObject) { var info = await VerifyAssetSerializationAsync( - service, projectObject.Info, WellKnownSynchronizationKinds.ProjectAttributes, + service, projectObject.Info, WellKnownSynchronizationKind.ProjectAttributes, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)).ConfigureAwait(false); await VerifyAssetSerializationAsync( - service, projectObject.CompilationOptions, WellKnownSynchronizationKinds.CompilationOptions, + service, projectObject.CompilationOptions, WellKnownSynchronizationKind.CompilationOptions, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); await VerifyAssetSerializationAsync( - service, projectObject.ParseOptions, WellKnownSynchronizationKinds.ParseOptions, + service, projectObject.ParseOptions, WellKnownSynchronizationKind.ParseOptions, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); foreach (var checksum in projectObject.Documents) @@ -72,21 +72,21 @@ await VerifyAssetSerializationAsync( foreach (var checksum in projectObject.ProjectReferences) { await VerifyAssetSerializationAsync( - service, checksum, WellKnownSynchronizationKinds.ProjectReference, + service, checksum, WellKnownSynchronizationKind.ProjectReference, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); } foreach (var checksum in projectObject.MetadataReferences) { await VerifyAssetSerializationAsync( - service, checksum, WellKnownSynchronizationKinds.MetadataReference, + service, checksum, WellKnownSynchronizationKind.MetadataReference, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); } foreach (var checksum in projectObject.AnalyzerReferences) { await VerifyAssetSerializationAsync( - service, checksum, WellKnownSynchronizationKinds.AnalyzerReference, + service, checksum, WellKnownSynchronizationKind.AnalyzerReference, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); } @@ -100,19 +100,19 @@ await VerifyAssetSerializationAsync( internal static async Task VerifyAssetAsync(ISolutionSynchronizationService service, DocumentStateChecksums documentObject) { var info = await VerifyAssetSerializationAsync( - service, documentObject.Info, WellKnownSynchronizationKinds.DocumentAttributes, + service, documentObject.Info, WellKnownSynchronizationKind.DocumentAttributes, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)).ConfigureAwait(false); await VerifyAssetSerializationAsync( - service, documentObject.Text, WellKnownSynchronizationKinds.SourceText, + service, documentObject.Text, WellKnownSynchronizationKind.SourceText, (v, k, s) => SolutionAsset.Create(s.CreateChecksum(v, CancellationToken.None), v, s)); } internal static async Task VerifyAssetSerializationAsync( ISolutionSynchronizationService service, Checksum checksum, - string kind, - Func assetGetter) + WellKnownSynchronizationKind kind, + Func assetGetter) { // re-create asset from object var syncService = (SolutionSynchronizationServiceFactory.Service)service; @@ -206,20 +206,20 @@ internal static void VerifySnapshotInService( int expectedAdditionalDocumentCount) { VerifyChecksumInService(snapshotService, projectObject.Checksum, projectObject.GetWellKnownSynchronizationKind()); - VerifyChecksumInService(snapshotService, projectObject.Info, WellKnownSynchronizationKinds.ProjectAttributes); - VerifyChecksumInService(snapshotService, projectObject.CompilationOptions, WellKnownSynchronizationKinds.CompilationOptions); - VerifyChecksumInService(snapshotService, projectObject.ParseOptions, WellKnownSynchronizationKinds.ParseOptions); + VerifyChecksumInService(snapshotService, projectObject.Info, WellKnownSynchronizationKind.ProjectAttributes); + VerifyChecksumInService(snapshotService, projectObject.CompilationOptions, WellKnownSynchronizationKind.CompilationOptions); + VerifyChecksumInService(snapshotService, projectObject.ParseOptions, WellKnownSynchronizationKind.ParseOptions); VerifyCollectionInService(snapshotService, projectObject.Documents.ToDocumentObjects(snapshotService), expectedDocumentCount); - VerifyCollectionInService(snapshotService, projectObject.ProjectReferences, expectedProjectReferenceCount, WellKnownSynchronizationKinds.ProjectReference); - VerifyCollectionInService(snapshotService, projectObject.MetadataReferences, expectedMetadataReferenceCount, WellKnownSynchronizationKinds.MetadataReference); - VerifyCollectionInService(snapshotService, projectObject.AnalyzerReferences, expectedAnalyzerReferenceCount, WellKnownSynchronizationKinds.AnalyzerReference); + VerifyCollectionInService(snapshotService, projectObject.ProjectReferences, expectedProjectReferenceCount, WellKnownSynchronizationKind.ProjectReference); + VerifyCollectionInService(snapshotService, projectObject.MetadataReferences, expectedMetadataReferenceCount, WellKnownSynchronizationKind.MetadataReference); + VerifyCollectionInService(snapshotService, projectObject.AnalyzerReferences, expectedAnalyzerReferenceCount, WellKnownSynchronizationKind.AnalyzerReference); VerifyCollectionInService(snapshotService, projectObject.AdditionalDocuments.ToDocumentObjects(snapshotService), expectedAdditionalDocumentCount); } - internal static void VerifyCollectionInService(ISolutionSynchronizationService snapshotService, ChecksumCollection checksums, int expectedCount, string expectedItemKind) + internal static void VerifyCollectionInService(ISolutionSynchronizationService snapshotService, ChecksumCollection checksums, int expectedCount, WellKnownSynchronizationKind expectedItemKind) { VerifyChecksumInService(snapshotService, checksums.Checksum, checksums.GetWellKnownSynchronizationKind()); Assert.Equal(checksums.Count, expectedCount); @@ -244,8 +244,8 @@ internal static void VerifyCollectionInService(ISolutionSynchronizationService s internal static void VerifySnapshotInService(ISolutionSynchronizationService snapshotService, DocumentStateChecksums documentObject) { VerifyChecksumInService(snapshotService, documentObject.Checksum, documentObject.GetWellKnownSynchronizationKind()); - VerifyChecksumInService(snapshotService, documentObject.Info, WellKnownSynchronizationKinds.DocumentAttributes); - VerifyChecksumInService(snapshotService, documentObject.Text, WellKnownSynchronizationKinds.SourceText); + VerifyChecksumInService(snapshotService, documentObject.Info, WellKnownSynchronizationKind.DocumentAttributes); + VerifyChecksumInService(snapshotService, documentObject.Text, WellKnownSynchronizationKind.SourceText); } internal static void VerifySynchronizationObjectInService(ISolutionSynchronizationService snapshotService, T syncObject) where T : RemotableData @@ -253,7 +253,7 @@ internal static void VerifySynchronizationObjectInService(ISolutionSynchroniz VerifyChecksumInService(snapshotService, syncObject.Checksum, syncObject.Kind); } - internal static void VerifyChecksumInService(ISolutionSynchronizationService snapshotService, Checksum checksum, string kind) + internal static void VerifyChecksumInService(ISolutionSynchronizationService snapshotService, Checksum checksum, WellKnownSynchronizationKind kind) { Assert.NotNull(checksum); var otherObject = snapshotService.GetRemotableData(checksum, CancellationToken.None); @@ -266,7 +266,7 @@ internal static void SynchronizationObjectEqual(T checksumObject1, T checksum ChecksumEqual(checksumObject1.Checksum, checksumObject1.Kind, checksumObject2.Checksum, checksumObject2.Kind); } - internal static void ChecksumEqual(Checksum checksum1, string kind1, Checksum checksum2, string kind2) + internal static void ChecksumEqual(Checksum checksum1, WellKnownSynchronizationKind kind1, Checksum checksum2, WellKnownSynchronizationKind kind2) { Assert.Equal(checksum1, checksum2); Assert.Equal(kind1, kind2); diff --git a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs index adacfe3682c93..152c06e12caae 100644 --- a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs +++ b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs @@ -42,7 +42,7 @@ public async Task CreateSolutionSnapshotId_Empty() VerifySynchronizationObjectInService(snapshotService, solutionSyncObject); var solutionObject = await snapshotService.GetValueAsync(checksum).ConfigureAwait(false); - VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKinds.SolutionAttributes); + VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKind.SolutionAttributes); var projectsSyncObject = snapshotService.GetRemotableData(solutionObject.Projects.Checksum, CancellationToken.None); VerifySynchronizationObjectInService(snapshotService, projectsSyncObject); @@ -78,7 +78,7 @@ public async Task CreateSolutionSnapshotId_Project() var solutionObject = await snapshotService.GetValueAsync(checksum).ConfigureAwait(false); - VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKinds.SolutionAttributes); + VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKind.SolutionAttributes); var projectSyncObject = snapshotService.GetRemotableData(solutionObject.Projects.Checksum, CancellationToken.None); VerifySynchronizationObjectInService(snapshotService, projectSyncObject); @@ -114,8 +114,8 @@ public async Task CreateSolutionSnapshotId() var solutionObject = await snapshotService.GetValueAsync(syncObject.Checksum).ConfigureAwait(false); VerifySynchronizationObjectInService(snapshotService, syncObject); - VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKinds.SolutionAttributes); - VerifyChecksumInService(snapshotService, solutionObject.Projects.Checksum, WellKnownSynchronizationKinds.Projects); + VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKind.SolutionAttributes); + VerifyChecksumInService(snapshotService, solutionObject.Projects.Checksum, WellKnownSynchronizationKind.Projects); Assert.Equal(solutionObject.Projects.Count, 1); VerifySnapshotInService(snapshotService, solutionObject.Projects.ToProjectObjects(snapshotService)[0], 1, 0, 0, 0, 0); @@ -151,8 +151,8 @@ public async Task CreateSolutionSnapshotId_Full() var solutionObject = await snapshotService.GetValueAsync(syncObject.Checksum).ConfigureAwait(false); VerifySynchronizationObjectInService(snapshotService, syncObject); - VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKinds.SolutionAttributes); - VerifyChecksumInService(snapshotService, solutionObject.Projects.Checksum, WellKnownSynchronizationKinds.Projects); + VerifyChecksumInService(snapshotService, solutionObject.Info, WellKnownSynchronizationKind.SolutionAttributes); + VerifyChecksumInService(snapshotService, solutionObject.Projects.Checksum, WellKnownSynchronizationKind.Projects); Assert.Equal(solutionObject.Projects.Count, 2); 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/ServicesTest.csproj b/src/Workspaces/CoreTest/ServicesTest.csproj index db824df8ea2a6..c89c53073539a 100644 --- a/src/Workspaces/CoreTest/ServicesTest.csproj +++ b/src/Workspaces/CoreTest/ServicesTest.csproj @@ -12,6 +12,7 @@ Roslyn.Services.UnitTests v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} UnitTest 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/AssetService.cs b/src/Workspaces/Remote/Core/Services/AssetService.cs index 26d7b536b9023..05caaad9879b9 100644 --- a/src/Workspaces/Remote/Core/Services/AssetService.cs +++ b/src/Workspaces/Remote/Core/Services/AssetService.cs @@ -29,7 +29,7 @@ public AssetService(int sessionId, AssetStorage assetStorage) _assetStorage = assetStorage; } - public T Deserialize(string kind, ObjectReader reader, CancellationToken cancellationToken) + public T Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader, CancellationToken cancellationToken) { return s_serializer.Deserialize(kind, reader, cancellationToken); } 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/Remote/ServiceHub/Services/SnapshotService.JsonRpcAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Services/SnapshotService.JsonRpcAssetSource.cs index 882b56a148972..0ae446a02b51b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SnapshotService.JsonRpcAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SnapshotService.JsonRpcAssetSource.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; @@ -72,7 +73,7 @@ private IList> ReadAssets( var responseChecksum = Checksum.ReadFrom(reader); Contract.ThrowIfFalse(checksums.Contains(responseChecksum)); - var kind = reader.ReadString(); + var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); // in service hub, cancellation means simply closed stream var @object = _owner.RoslynServices.AssetService.Deserialize(kind, reader, cancellationToken); 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 diff --git a/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj b/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj index ed69a34b4b746..1b33d5a7b8e87 100644 --- a/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj +++ b/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj @@ -13,6 +13,7 @@ Default v4.6 win7 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} UnitTest