diff options
author | Mike Krüger <mkrueger@xamarin.com> | 2015-06-12 10:22:06 +0300 |
---|---|---|
committer | Mike Krüger <mkrueger@xamarin.com> | 2015-06-12 10:22:06 +0300 |
commit | 8130658e4515d53b4ba5cdc1e7542cff818f7408 (patch) | |
tree | 809204195a7ccc9939048f07054da84cc71be597 | |
parent | 8b21b87ab59174eea584b4c5778bcc870291cdc9 (diff) |
[CSharpBinding] Restructured NR6.
NR6 will be discontinued. The refactoring part will be hosted as RefactoringEssentials.
The features will move to the IDE.
Due to a mcs bug that's not compileable. Porting needs to be continued on Windows.
332 files changed, 38817 insertions, 121 deletions
diff --git a/.gitmodules b/.gitmodules index cb047cfe6f..395dab8a8b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -56,3 +56,6 @@ path = main/external/libgit2 url = git://github.com/mono/libgit2.git branch = xs-5.10-v1 +[submodule "main/external/RefactoringEssentials"] + path = main/external/RefactoringEssentials + url = git://github.com/icsharpcode/RefactoringEssentials.git diff --git a/main/Main.sln b/main/Main.sln index 6798841da1..41323b2b80 100644 --- a/main/Main.sln +++ b/main/Main.sln @@ -265,8 +265,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory6.CSh EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ICSharpCode.NRefactory6.Shared", "external\NRefactory6\ICSharpCode.NRefactory6.Shared\ICSharpCode.NRefactory6.Shared.shproj", "{5AD3093F-F79D-4014-B197-27D09492C3E3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NR6Pack", "external\NRefactory6\NR6Pack\NR6Pack.csproj", "{C465A5DC-AD28-49A2-89C0-F81838814A7E}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuiUnit_NET_4_5", "external\guiunit\src\framework\GuiUnit_NET_4_5.csproj", "{D12F0F7B-8DE3-43EC-BA49-41052D065A9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibGit2Sharp", "external\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj", "{EE6ED99F-CB12-4683-B055-D28FC7357A34}"
@@ -302,6 +300,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ide.Tests", "tests\Ide.Test EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsPlatform.Tests", "tests\WindowsPlatform.Tests\WindowsPlatform.Tests.csproj", "{865100E2-A29C-4FCD-B803-1A0B9A0A6EF7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RefactoringEssentials", "external\RefactoringEssentials\RefactoringEssentials\RefactoringEssentials.csproj", "{C465A5DC-AD28-49A2-89C0-F81838814A7E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1506,26 +1506,6 @@ Global {C3887A93-B2BD-4097-8E2F-3A063EFF32FD}.ReleaseMac|Any CPU.Build.0 = Release|Any CPU
{C3887A93-B2BD-4097-8E2F-3A063EFF32FD}.ReleaseWin32|Any CPU.ActiveCfg = Release|Any CPU
{C3887A93-B2BD-4097-8E2F-3A063EFF32FD}.ReleaseWin32|Any CPU.Build.0 = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|x86.Build.0 = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugGnome|Any CPU.ActiveCfg = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugGnome|Any CPU.Build.0 = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugMac|Any CPU.ActiveCfg = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugMac|Any CPU.Build.0 = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugWin32|Any CPU.ActiveCfg = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugWin32|Any CPU.Build.0 = Debug|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|Any CPU.Build.0 = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|x86.ActiveCfg = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|x86.Build.0 = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseGnome|Any CPU.ActiveCfg = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseGnome|Any CPU.Build.0 = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseMac|Any CPU.ActiveCfg = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseMac|Any CPU.Build.0 = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseWin32|Any CPU.ActiveCfg = Release|Any CPU
- {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseWin32|Any CPU.Build.0 = Release|Any CPU
{C4B0275C-37D3-43F2-927D-ABF556600804}.Debug|Any CPU.ActiveCfg = Debug|x86
{C4B0275C-37D3-43F2-927D-ABF556600804}.DebugGnome|Any CPU.ActiveCfg = Debug|x86
{C4B0275C-37D3-43F2-927D-ABF556600804}.DebugMac|Any CPU.ActiveCfg = Debug|x86
@@ -1919,6 +1899,26 @@ Global {FEC19BDA-4904-4005-8C09-68E82E8BEF6A}.ReleaseMac|Any CPU.Build.0 = Release|Any CPU
{FEC19BDA-4904-4005-8C09-68E82E8BEF6A}.ReleaseWin32|Any CPU.ActiveCfg = Release|Any CPU
{FEC19BDA-4904-4005-8C09-68E82E8BEF6A}.ReleaseWin32|Any CPU.Build.0 = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugMac|Any CPU.ActiveCfg = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugMac|Any CPU.Build.0 = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugWin32|Any CPU.ActiveCfg = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugWin32|Any CPU.Build.0 = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugGnome|Any CPU.ActiveCfg = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.DebugGnome|Any CPU.Build.0 = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseMac|Any CPU.ActiveCfg = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseMac|Any CPU.Build.0 = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseWin32|Any CPU.ActiveCfg = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseWin32|Any CPU.Build.0 = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseGnome|Any CPU.ActiveCfg = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.ReleaseGnome|Any CPU.Build.0 = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Debug|x86.Build.0 = Debug|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|x86.ActiveCfg = Release|Any CPU
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7525BB88-6142-4A26-93B9-A30C6983390A} = {9D360D43-0C05-49D6-84DB-4E7AB2F38F82}
@@ -2042,7 +2042,6 @@ Global {2A705FC6-1A9E-4941-9E47-254D79F2D9D5} = {2D711139-8765-4929-BC7A-AA2DEE6F615D}
{7E891659-45F3-42B5-B940-A728780CCAE9} = {840E8B8D-929C-4CB2-9D23-2702FF01269A}
{5AD3093F-F79D-4014-B197-27D09492C3E3} = {840E8B8D-929C-4CB2-9D23-2702FF01269A}
- {C465A5DC-AD28-49A2-89C0-F81838814A7E} = {840E8B8D-929C-4CB2-9D23-2702FF01269A}
{BFE8691A-D323-4622-9021-7B8B27F81599} = {5D3F7E65-E55B-45CA-A83B-D1E10040281E}
{8A04FF99-5DFE-4E3D-A24F-72A621C8DDC6} = {5D3F7E65-E55B-45CA-A83B-D1E10040281E}
{D0B5AF2B-4BC1-4EB4-81D3-E5B85DDCE925} = {5D3F7E65-E55B-45CA-A83B-D1E10040281E}
@@ -2053,6 +2052,7 @@ Global {9E4BA410-8338-42EC-AF9C-422C35ECED81} = {78C10DAE-D3D7-44FC-93DF-831D8D54ECF9}
{73D4CC8B-BAB9-4A29-841B-F25C6311F067} = {78C10DAE-D3D7-44FC-93DF-831D8D54ECF9}
{865100E2-A29C-4FCD-B803-1A0B9A0A6EF7} = {78C10DAE-D3D7-44FC-93DF-831D8D54ECF9}
+ {C465A5DC-AD28-49A2-89C0-F81838814A7E} = {F12939F1-D55A-4CE9-9F33-8D959BFC7D6C}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
@@ -2064,7 +2064,7 @@ Global $2.TabsToSpaces = False
$2.inheritsSet = null
$2.inheritsScope = text/plain
- $2.scope = application/glade+xml
+ $2.scope = application/json
$0.DotNetNamingPolicy = $3
$3.DirectoryNamespaceAssociation = Flat
$3.ResourceNamePolicy = FileName
@@ -2101,7 +2101,7 @@ Global $12.scope = image/svg+xml
$0.XmlFormattingPolicy = $13
$13.inheritsSet = null
- $13.scope = application/glade+xml
+ $13.scope = application/config+xml
$13.inheritsScope = application/xml
$0.TextStylePolicy = $14
$14.TabsToSpaces = False
diff --git a/main/external/NRefactory6 b/main/external/NRefactory6 -Subproject 1434281c7ab7bf20234d55ed9d369cf5615476e +Subproject eca10d754c673c743858edfb857549b756efee9 diff --git a/main/external/RefactoringEssentials b/main/external/RefactoringEssentials new file mode 160000 +Subproject b33f5e68749daa53cd70779d2b0c26dd6a504ac diff --git a/main/src/addins/CSharpBinding/CSharpBinding.csproj b/main/src/addins/CSharpBinding/CSharpBinding.csproj index 111300b4aa..80bef28958 100644 --- a/main/src/addins/CSharpBinding/CSharpBinding.csproj +++ b/main/src/addins/CSharpBinding/CSharpBinding.csproj @@ -126,10 +126,9 @@ <Name>ICSharpCode.NRefactory6.CSharp</Name> <Private>False</Private> </ProjectReference> - <ProjectReference Include="..\..\..\external\NRefactory6\NR6Pack\NR6Pack.csproj"> + <ProjectReference Include="..\..\..\external\RefactoringEssentials\RefactoringEssentials\RefactoringEssentials.csproj"> <Project>{C465A5DC-AD28-49A2-89C0-F81838814A7E}</Project> - <Name>NR6Pack</Name> - <Private>False</Private> + <Name>RefactoringEssentials</Name> </ProjectReference> </ItemGroup> <ItemGroup> @@ -396,6 +395,218 @@ <Compile Include="MonoDevelop.CSharp.Refactoring\CodeGenerationService.cs" /> <Compile Include="MonoDevelop.CSharp\CSharpBraceMatcher.cs" /> <Compile Include="MonoDevelop.CSharp.Refactoring\FindProjectReferenceUsagesHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpExtractMethodService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.Analyzer.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.CSharpCodeGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.FormattingProvider.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.PostProcessor.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpMethodExtractor.TriviaResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSelectionResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSelectionResult.ExpressionResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSelectionResult.StatementResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSelectionValidator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSelectionValidator.Validator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSyntaxTriviaService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\CSharpSyntaxTriviaServiceFactory.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\CSharp\Extensions.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\AbstractExtractMethodService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\AbstractSyntaxTriviaService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\AbstractSyntaxTriviaService.Result.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\Enums.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\Extensions.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ExtractMethodMatrix.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ExtractMethodOptions.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ExtractMethodResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ExtractMethodService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\FailedExtractMethodResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\IExtractMethodService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\InsertionPoint.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ISyntaxTriviaService.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.Analyzer.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.Analyzer.SymbolMapBuilder.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.AnalyzerResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.CodeGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.GeneratedCode.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.TriviaResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.TypeParameterCollector.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.VariableInfo.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\MethodExtractor.VariableSymbol.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\OperationStatus.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\OperationStatus_Statics.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\OperationStatus`1.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ParameterStyle.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\ReturnStyle.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\SelectionResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\SelectionValidator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\SelectionValidator.NullSelectionResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\SimpleExtractMethodResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\UniqueNameGenerator.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\ExtractMethod\VariableStyle.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\AttributeNamedParameterContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\CastCompletionContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\CompletionContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\DelegateCreationContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\EnumMemberContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\ExplicitInterfaceContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\ExternAliasContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\FormatItemContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\KeywordContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\NamedParameterContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\ObjectCreationContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\ObjectInitializerContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\OverrideContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\PartialContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\PreProcessorExpressionContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\RoslynRecommendationsCompletionContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\SenderCompletionContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\SnippetContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\SpeculativeNameContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\SpeculativeTContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ContextHandler\XmlDocCommentContextHandler.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AbstractKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AbstractSyntacticSingleKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AddKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AliasKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AscendingKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AsKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AssemblyKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AsyncKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\AwaitKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\BaseKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\BoolKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\BreakKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ByKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ByteKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\CaseKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\CatchKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\CharKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\CheckedKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ChecksumKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ClassKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ConstKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ContinueKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DecimalKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DefaultKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DefineKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DelegateKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DescendingKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DisableKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DoKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DoubleKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\DynamicKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ElifKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ElseKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\EndIfKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\EndRegionKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\EnumKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\EqualsKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ErrorKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\EventKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ExplicitKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ExternKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FalseKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FieldKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FinallyKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FixedKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FloatKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ForEachKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ForKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\FromKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\GetKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\GlobalKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\GotoKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\GroupKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\HiddenKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\IfKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ImplicitKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\InKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\InterfaceKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\InternalKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\IntKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\IntoKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\IsKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\JoinKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\LetKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\LineKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\LockKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\LongKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\MethodKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ModuleKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\NameOfKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\NamespaceKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\NewKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\NullKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ObjectKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\OnKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\OperatorKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\OrderByKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\OutKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\OverrideKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ParamKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ParamsKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\PartialKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\PragmaKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\PrivateKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\PropertyKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ProtectedKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\PublicKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ReadOnlyKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ReferenceKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\RefKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\RegionKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\RemoveKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\RestoreKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ReturnKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SByteKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SealedKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SelectKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SetKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ShortKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SizeOfKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\StackAllocKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\StaticKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\StringKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\StructKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\SwitchKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ThisKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ThrowKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\TrueKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\TryKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\TypeKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\TypeOfKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\TypeVarKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UIntKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\ULongKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UncheckedKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UndefKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UnsafeKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UShortKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\UsingKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\VarKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\VirtualKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\VoidKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\VolatileKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\WarningKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\WhenKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\WhereKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\WhileKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\KeywordRecommender\YieldKeywordRecommender.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\CompletionContext.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\CompletionEngine.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\CompletionResult.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\CompletionTriggerInfo.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\CompletionTriggerReason.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\DisplayFlags.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\EditorBrowsableBehavior.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ICompletionCategory.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ICompletionDataFactory.cs" /> + <Compile Include="MonoDevelop.CSharp.Features\Completion\ICompletionKeyHandler.cs" /> </ItemGroup> <ItemGroup> <None Include="Makefile.am" /> @@ -449,5 +660,7 @@ <Folder Include="MonoDevelop.CSharp.CodeFixes\RemoveUnnecessaryCast\" /> <Folder Include="MonoDevelop.CSharp.Diagnostics\MonoTODO\" /> <Folder Include="MonoDevelop.CSharp.CodeRefactorings\ConvertToEnum\" /> + <Folder Include="Neuer Ordner\" /> + <Folder Include="MonoDevelop.CSharp.Features\EncapsulateField\" /> </ItemGroup> </Project> diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs index ca6de1af18..3ad7b6e1b7 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs @@ -13,7 +13,7 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; using ICSharpCode.NRefactory6.CSharp; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs index 11e5cf2d72..a5fcc5b4b2 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs @@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.CodeActions; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using MonoDevelop.CSharp.CodeFixes; using ICSharpCode.NRefactory6.CSharp; using Microsoft.CodeAnalysis.CSharp; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs index 5bd232f594..b1bd4224f6 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CodeActions; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using MonoDevelop.CSharp.CodeFixes; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs index 01fb9b6052..7a40df4f42 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.FindSymbols; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CodeActions; using System; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using MonoDevelop.Ide.TypeSystem; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs index f821d0d4a6..50f93e61f4 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis; using ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass; using MonoDevelop.Core; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using ICSharpCode.NRefactory6.CSharp; namespace MonoDevelop.CSharp.CodeFixes.ImplementAbstractClass diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs index a8c74428e8..7fc62d4b6c 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs @@ -29,7 +29,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using ICSharpCode.NRefactory.CSharp; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs index 63770abbdf..26283ce45d 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs @@ -19,7 +19,7 @@ using Microsoft.CodeAnalysis; using MonoDevelop.CSharp.Diagnostics; using ICSharpCode.NRefactory6.CSharp; using MonoDevelop.Core; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using Microsoft.CodeAnalysis.CSharp; namespace MonoDevelop.CSharp.CodeFixes.RemoveUnnecessaryCast diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs index d7cb42e0d2..e3ed5cea15 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs @@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis; using ICSharpCode.NRefactory6.CSharp.Features.RemoveUnnecessaryImports; using MonoDevelop.Core; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using Microsoft.CodeAnalysis.Text; using MonoDevelop.CSharp.Diagnostics; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs index be3ccc4cd3..0b2b11df09 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs @@ -19,7 +19,7 @@ using ICSharpCode.NRefactory6.CSharp; using MonoDevelop.Core; using System; using Microsoft.CodeAnalysis.CSharp; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using MonoDevelop.CSharp.Diagnostics.SimplifyTypeNames; namespace MonoDevelop.CSharp.CodeFixes.SimplifyTypeNames diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/ExtractMethod/ExtractMethodCodeRefactoringProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/ExtractMethod/ExtractMethodCodeRefactoringProvider.cs index 992b28b012..b1f395cf6d 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/ExtractMethod/ExtractMethodCodeRefactoringProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/ExtractMethod/ExtractMethodCodeRefactoringProvider.cs @@ -9,10 +9,10 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Text; using System.Threading; using ICSharpCode.NRefactory6.CSharp.ExtractMethod; -using ICSharpCode.NRefactory6.CSharp.Refactoring; using MonoDevelop.Core; using MonoDevelop.Ide; using ICSharpCode.NRefactory6.CSharp; +using RefactoringEssentials; namespace MonoDevelop.CSharp.CodeRefactorings.ExtractMethod { diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.cs index b5951ab7bf..5e4447f063 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.cs @@ -21,7 +21,7 @@ using Roslyn.Utilities; using Microsoft.CodeAnalysis; using ICSharpCode.NRefactory6.CSharp; using Microsoft.CodeAnalysis.CSharp; -using ICSharpCode.NRefactory6.CSharp.Refactoring; +using RefactoringEssentials; using MonoDevelop.Core; namespace MonoDevelop.CSharp.CodeRefactorings.InlineTemporary diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CSharpCompletionTextEditorExtension.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CSharpCompletionTextEditorExtension.cs index 476adc3e88..ea030ccc9a 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CSharpCompletionTextEditorExtension.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CSharpCompletionTextEditorExtension.cs @@ -123,7 +123,7 @@ namespace MonoDevelop.CSharp.Completion } static Func<Microsoft.CodeAnalysis.Document, CancellationToken, Task<Microsoft.CodeAnalysis.Document>> WithFrozenPartialSemanticsAsync; - static List<ICompletionData> snippets; + static List<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData> snippets; static CSharpCompletionTextEditorExtension () { @@ -142,8 +142,8 @@ namespace MonoDevelop.CSharp.Completion CompletionEngine.SnippetCallback = delegate(CancellationToken arg) { if (snippets != null) - return Task.FromResult ((IEnumerable<ICompletionData>)snippets); - var newSnippets = new List<ICompletionData> (); + return Task.FromResult((IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData>)snippets); + var newSnippets = new List<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData>(); foreach (var ct in MonoDevelop.Ide.CodeTemplates.CodeTemplateService.GetCodeTemplates ("text/x-csharp")) { if (string.IsNullOrEmpty (ct.Shortcut) || ct.CodeTemplateContext != MonoDevelop.Ide.CodeTemplates.CodeTemplateContext.Standard) continue; @@ -155,7 +155,7 @@ namespace MonoDevelop.CSharp.Completion }); } snippets = newSnippets; - return Task.FromResult ((IEnumerable<ICompletionData>)newSnippets); + return Task.FromResult((IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData>)newSnippets); }; } @@ -408,7 +408,7 @@ namespace MonoDevelop.CSharp.Completion return null; foreach (var symbol in completionResult) { - list.Add ((CompletionData)symbol); + list.Add ((Ide.CodeCompletion.CompletionData)symbol); } if (IdeApp.Preferences.AddImportedItemsToCompletionList.Value && list.OfType<RoslynSymbolCompletionData> ().Any (cd => cd.Symbol is ITypeSymbol)) { diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/ProtocolMemberContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/ProtocolMemberContextHandler.cs index a67e29b303..3ab31e92a0 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/ProtocolMemberContextHandler.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/ProtocolMemberContextHandler.cs @@ -48,9 +48,9 @@ namespace MonoDevelop.CSharp.Completion this.factory = factory; } - protected override IEnumerable<ICompletionData> CreateCompletionData (CompletionEngine engine, SemanticModel semanticModel, int position, ITypeSymbol returnType, Accessibility seenAccessibility, SyntaxToken startToken, SyntaxToken tokenBeforeReturnType, bool afterKeyword, CancellationToken cancellationToken) + protected override IEnumerable<CompletionData> CreateCompletionData (CompletionEngine engine, SemanticModel semanticModel, int position, ITypeSymbol returnType, Accessibility seenAccessibility, SyntaxToken startToken, SyntaxToken tokenBeforeReturnType, bool afterKeyword, CancellationToken cancellationToken) { - var result = new List<ICompletionData> (); + var result = new List<CompletionData> (); ISet<ISymbol> overridableMembers; if (!TryDetermineOverridableMembers (semanticModel, tokenBeforeReturnType, seenAccessibility, out overridableMembers, cancellationToken)) { return result; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCodeCompletionFactory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCodeCompletionFactory.cs index bc8779ff37..0dbc5d906f 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCodeCompletionFactory.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCodeCompletionFactory.cs @@ -63,7 +63,7 @@ namespace MonoDevelop.CSharp.Completion #region ICompletionDataFactory implementation - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateGenericData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string data, ICSharpCode.NRefactory6.CSharp.Completion.GenericDataType genericDataType) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateGenericData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string data, ICSharpCode.NRefactory6.CSharp.Completion.GenericDataType genericDataType) { return new RoslynCompletionData (keyHandler) { CompletionText = data, @@ -128,7 +128,7 @@ namespace MonoDevelop.CSharp.Completion } } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateFormatItemCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string format, string description, object example) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateFormatItemCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string format, string description, object example) { return new FormatItemCompletionData (keyHandler, format, description, example); } @@ -174,7 +174,7 @@ namespace MonoDevelop.CSharp.Completion } } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateXmlDocCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string title, string description, string insertText) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateXmlDocCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string title, string description, string insertText) { return new XmlDocCompletionData (keyHandler, this, title, description, insertText); } @@ -189,17 +189,17 @@ namespace MonoDevelop.CSharp.Completion return new RoslynSymbolCompletionData (keyHandler, this, symbol, text); } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateNewOverrideCompletionData(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, ISymbol m, bool afterKeyword) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateNewOverrideCompletionData(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, ISymbol m, bool afterKeyword) { return new CreateOverrideCompletionData (keyHandler, this, declarationBegin, currentType, m, afterKeyword); } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreatePartialCompletionData(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, IMethodSymbol method, bool afterKeyword) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreatePartialCompletionData(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, IMethodSymbol method, bool afterKeyword) { return new CreatePartialCompletionData (keyHandler, this, declarationBegin, currentType, method, afterKeyword); } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateAnonymousMethod(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string displayText, string description, string textBeforeCaret, string textAfterCaret) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateAnonymousMethod(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, string displayText, string description, string textBeforeCaret, string textAfterCaret) { return new AnonymousMethodCompletionData (keyHandler) { CompletionText = textBeforeCaret + "|" + textAfterCaret, @@ -208,17 +208,17 @@ namespace MonoDevelop.CSharp.Completion }; } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateNewMethodDelegate(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ITypeSymbol delegateType, string varName, INamedTypeSymbol curType) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateNewMethodDelegate(ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ITypeSymbol delegateType, string varName, INamedTypeSymbol curType) { return new EventCreationCompletionData (keyHandler, this, delegateType, varName, curType); } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateObjectCreation (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ITypeSymbol type, ISymbol symbol, int declarationBegin, bool afterKeyword) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateObjectCreation (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ITypeSymbol type, ISymbol symbol, int declarationBegin, bool afterKeyword) { return new ObjectCreationCompletionData (keyHandler, this, semanticModel, type, symbol, declarationBegin, afterKeyword); } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateCastCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ISymbol member, SyntaxNode nodeToCast, ITypeSymbol targetType) + ICSharpCode.NRefactory6.CSharp.Completion.CompletionData ICSharpCode.NRefactory6.CSharp.Completion.ICompletionDataFactory.CreateCastCompletionData (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler, ISymbol member, SyntaxNode nodeToCast, ITypeSymbol targetType) { return new CastCompletionData (keyHandler, this, semanticModel, member, nodeToCast, targetType); } diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCompletionData.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCompletionData.cs index 372e2e0ad7..c1596a382e 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCompletionData.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/RoslynCompletionData.cs @@ -38,7 +38,7 @@ using MonoDevelop.Ide; namespace MonoDevelop.CSharp.Completion { - class RoslynCompletionData : CompletionData, ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData + class RoslynCompletionData : CompletionData, ICSharpCode.NRefactory6.CSharp.Completion.CompletionData { List<CompletionData> overloads; @@ -48,7 +48,7 @@ namespace MonoDevelop.CSharp.Completion } } - void ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.AddOverload (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData data) + void ICSharpCode.NRefactory6.CSharp.Completion.CompletionData.AddOverload (ICSharpCode.NRefactory6.CSharp.Completion.CompletionData data) { if (overloads == null) overloads = new List<CompletionData> (); @@ -56,7 +56,7 @@ namespace MonoDevelop.CSharp.Completion sorted = null; } - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionCategory ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.CompletionCategory { + ICSharpCode.NRefactory6.CSharp.Completion.ICompletionCategory ICSharpCode.NRefactory6.CSharp.Completion.CompletionData.CompletionCategory { get { return (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionCategory)base.CompletionCategory; } @@ -65,7 +65,7 @@ namespace MonoDevelop.CSharp.Completion } } - ICSharpCode.NRefactory6.CSharp.Completion.DisplayFlags ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.DisplayFlags { + ICSharpCode.NRefactory6.CSharp.Completion.DisplayFlags ICSharpCode.NRefactory6.CSharp.Completion.CompletionData.DisplayFlags { get { return (ICSharpCode.NRefactory6.CSharp.Completion.DisplayFlags)base.DisplayFlags; } @@ -76,9 +76,9 @@ namespace MonoDevelop.CSharp.Completion List<CompletionData> sorted; - IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData> ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.OverloadedData { + IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData> ICSharpCode.NRefactory6.CSharp.Completion.CompletionData.OverloadedData { get { - return (IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData>)OverloadedData; + return (IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.CompletionData>)OverloadedData; } } @@ -98,7 +98,7 @@ namespace MonoDevelop.CSharp.Completion protected readonly ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler; - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.KeyHandler { + ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler ICSharpCode.NRefactory6.CSharp.Completion.CompletionData.KeyHandler { get { return keyHandler; } diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Diagnostics/InconsistentNaming/NameConventionRule.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Diagnostics/InconsistentNaming/NameConventionRule.cs index 2453efff88..7816fa310f 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Diagnostics/InconsistentNaming/NameConventionRule.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Diagnostics/InconsistentNaming/NameConventionRule.cs @@ -27,7 +27,6 @@ using System; using System.Text; using MonoDevelop.Projects.Policies; using MonoDevelop.Core.Serialization; -using ICSharpCode.NRefactory6.CSharp.Refactoring; namespace MonoDevelop.CSharp.Diagnostics.InconsistentNaming { diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionContext.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionContext.cs new file mode 100644 index 0000000000..88acf43833 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionContext.cs @@ -0,0 +1,103 @@ +// +// CompletionContext.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using Microsoft.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using ICSharpCode.NRefactory6.CSharp.Completion; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public class CompletionContext + { + readonly Document document; + + public Document Document { + get { + return document; + } + } + + SemanticModel semanticModel; + + internal async Task<SemanticModel> GetSemanticModelAsync (CancellationToken cancellationToken = default (CancellationToken)) + { + if (semanticModel == null) + semanticModel = await document.GetSemanticModelAsync (cancellationToken); + return semanticModel; + } + + readonly int position; + public int Position { + get { + return position; + } + } + + Task<SyntaxContext> syntaxContext; + object syntaxCreationLock = new object (); + + internal async Task<SyntaxContext> GetSyntaxContextAsync (Workspace workspace, CancellationToken cancellationToken = default (CancellationToken)) + { + if (syntaxContext == null) { + lock (syntaxCreationLock) { + syntaxContext = syntaxContext ?? Task.Run (() => { + return SyntaxContext.Create (workspace, document, semanticModel, position, cancellationToken); + }); + } + } + return await syntaxContext; + } + + IEnumerable<CompletionContextHandler> additionalContextHandlers; + + /// <summary> + /// Adds completion context handlers to the given context. + /// </summary> + public IEnumerable<CompletionContextHandler> AdditionalContextHandlers { + get { + return additionalContextHandlers ?? Enumerable.Empty<CompletionContextHandler> (); + } + set { + additionalContextHandlers = value; + } + } + + /// <summary> + /// If false no default handlers will be used and only the AdditionalContextHandlers will run. + /// </summary> + public bool UseDefaultContextHandlers { get; set; } = true; + + public CompletionContext (Document document, int position, SemanticModel semanticModel = null) + { + this.document = document; + this.semanticModel = semanticModel; + this.position = position; + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionEngine.cs new file mode 100644 index 0000000000..157e8c0155 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionEngine.cs @@ -0,0 +1,204 @@ +// +// CSharpCompletionEngine.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Text; +using Microsoft.CodeAnalysis.Recommendations; +using System.Threading.Tasks; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public partial class CompletionEngine + { + static CompletionContextHandler[] handlers = { + new RoslynRecommendationsCompletionContextHandler (), + new KeywordContextHandler(), + new OverrideContextHandler(), + new PartialContextHandler(), + new EnumMemberContextHandler(), + new XmlDocCommentContextHandler(), + new ExplicitInterfaceContextHandler(), + new AttributeNamedParameterContextHandler(), + new NamedParameterContextHandler(), + new SpeculativeTContextHandler(), + new SnippetContextHandler(), + new ObjectInitializerContextHandler(), + new FormatItemContextHandler(), + new SpeculativeNameContextHandler(), + new DelegateCreationContextHandler(), + new ObjectCreationContextHandler(), + new SenderCompletionContextHandler(), + new CastCompletionContextHandler(), + new PreProcessorExpressionContextHandler() + }; + + static readonly ICompletionKeyHandler DefaultKeyHandler = new RoslynRecommendationsCompletionContextHandler (); + + readonly ICompletionDataFactory factory; + readonly Workspace workspace; + + public ICompletionDataFactory Factory { + get { + return factory; + } + } + + public Workspace Workspace { + get { + return workspace; + } + } + + public CompletionEngine(Workspace workspace, ICompletionDataFactory factory) + { + if (workspace == null) + throw new ArgumentNullException("workspace"); + if (factory == null) + throw new ArgumentNullException("factory"); + this.workspace = workspace; + this.factory = factory; + } + + public void AddImportCompletionData (CompletionResult result, Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken = default(CancellationToken)) + { + var ns = new Stack<INamespaceOrTypeSymbol>(); + ns.Push(semanticModel.Compilation.GlobalNamespace); + + semanticModel.LookupNamespacesAndTypes(position); + } + + public async Task<CompletionResult> GetCompletionDataAsync(CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken = default(CancellationToken)) + { + if (completionContext == null) + throw new ArgumentNullException ("completionContext"); + + var document = completionContext.Document; + var semanticModel = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait(false); + var position = completionContext.Position; + + var text = await document.GetTextAsync (cancellationToken).ConfigureAwait (false); + var ctx = await completionContext.GetSyntaxContextAsync (workspace, cancellationToken).ConfigureAwait (false); + + // case lambda parameter (n1, $ + if (ctx.TargetToken.IsKind (SyntaxKind.CommaToken) && + ctx.TargetToken.Parent != null && ctx.TargetToken.Parent.Parent != null && + ctx.TargetToken.Parent.Parent.IsKind(SyntaxKind.ParenthesizedLambdaExpression)) + return CompletionResult.Empty; + + var result = new CompletionResult(); + + if (position > 0) { + var nonExclusiveHandlers = new List<CompletionContextHandler> (); + var exclusiveHandlers = new List<CompletionContextHandler> (); + IEnumerable<CompletionContextHandler> handlerList; + if (completionContext.UseDefaultContextHandlers) { + handlerList = handlers.Concat (completionContext.AdditionalContextHandlers); + } else { + handlerList = completionContext.AdditionalContextHandlers; + } + foreach (var handler in handlerList) { + if (info.CompletionTriggerReason == CompletionTriggerReason.CompletionCommand || handler.IsTriggerCharacter (text, position - 1)) { + if (await handler.IsExclusiveAsync (document, position, info, cancellationToken)) { + exclusiveHandlers.Add (handler); + } else { + nonExclusiveHandlers.Add (handler); + } + } + } + + foreach (var handler in exclusiveHandlers) { + var handlerResult = handler.GetCompletionDataAsync (result, this, completionContext, info, cancellationToken).Result; + //Console.WriteLine ("-----" + handler); + //foreach (var item in handlerResult) { + // Console.WriteLine (item.DisplayText); + //} + if (handlerResult != null) + result.AddRange (handlerResult); + } + + if (result.Count == 0) { + foreach (var handler in nonExclusiveHandlers) { + var handlerResult = handler.GetCompletionDataAsync (result, this, completionContext, info, cancellationToken).Result; + //foreach (var item in handlerResult) { + // Console.WriteLine (item.DisplayText); + //} + if (handlerResult != null) + result.AddRange (handlerResult); + } + } + } + + // prevent auto selection for "<number>." case + if (ctx.TargetToken.IsKind(SyntaxKind.DotToken)) { + var accessExpr = ctx.TargetToken.Parent as MemberAccessExpressionSyntax; + if (accessExpr != null && + accessExpr.Expression != null && + accessExpr.Expression.IsKind(SyntaxKind.NumericLiteralExpression)) { + result.AutoSelect = false; + } + } + + if (ctx.LeftToken.Parent != null && + ctx.LeftToken.Parent.Parent != null && + ctx.TargetToken.Parent != null && !ctx.TargetToken.Parent.IsKind(SyntaxKind.NameEquals) && + ctx.LeftToken.Parent.Parent.IsKind(SyntaxKind.AnonymousObjectMemberDeclarator)) + result.AutoSelect = false; + + if (ctx.TargetToken.IsKind (SyntaxKind.OpenParenToken) && ctx.TargetToken.GetPreviousToken ().IsKind (SyntaxKind.OpenParenToken)) { + var validTypes = TypeGuessing.GetValidTypes (semanticModel, ctx.TargetToken.Parent, cancellationToken); + result.AutoSelect = !validTypes.Any (t => t.IsDelegateType ()); + } + + foreach (var type in ctx.InferredTypes) { + if (type.TypeKind == TypeKind.Delegate) { + result.AutoSelect = false; + break; + } + } + + return result; + } + + IEnumerable<ISymbol> GetAllMembers (ITypeSymbol type) + { + if (type == null) + yield break; + foreach (var member in type.GetMembers()) { + yield return member; + } + foreach (var baseMember in GetAllMembers(type.BaseType)) + yield return baseMember; + } + + public static Func<CancellationToken, Task<IEnumerable<CompletionData>>> SnippetCallback; + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionResult.cs new file mode 100644 index 0000000000..1d6164d6b7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionResult.cs @@ -0,0 +1,121 @@ +// +// CompletionResult.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class CompletionResult : IReadOnlyList<CompletionData> + { + public static readonly CompletionResult Empty = new CompletionResult (); + + readonly List<CompletionData> data = new List<CompletionData> (); + + public string DefaultCompletionString { + get; + internal set; + } + + bool autoCompleteEmptyMatch = true; + + public bool AutoCompleteEmptyMatch { + get { return autoCompleteEmptyMatch; } + set { autoCompleteEmptyMatch = value; } + } + + public bool AutoCompleteEmptyMatchOnCurlyBracket { + get; + set; + } + + public bool AutoSelect { + get; + set; + } + + public bool CloseOnSquareBrackets { + get; + set; + } + + public readonly List<IMethodSymbol> PossibleDelegates = new List<IMethodSymbol>(); + + #region IReadOnlyList<ICompletionData> implemenation + public IEnumerator<CompletionData> GetEnumerator() + { + return data.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return ((System.Collections.IEnumerable)data).GetEnumerator(); + } + + public CompletionData this[int index] { + get { + return data [index]; + } + } + + public int Count { + get { + return data.Count; + } + } + #endregion + + internal CompletionResult() + { + AutoSelect = true; + AutoCompleteEmptyMatchOnCurlyBracket = true; + } + + internal void AddData (CompletionData completionData) + { + if (completionData.DisplayText == "foobar") + Console.WriteLine (Environment.StackTrace); + data.Add(completionData); + } + + internal void AddRange (IEnumerable<CompletionData> completionData) + { + foreach (var data in completionData) + if (data.DisplayText == "foobar") + Console.WriteLine (Environment.StackTrace); + + data.AddRange(completionData); + } + + public static CompletionResult Create(IEnumerable<CompletionData> data) + { + var result = new CompletionResult(); + result.data.AddRange(data); + return result; + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerInfo.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerInfo.cs new file mode 100644 index 0000000000..9ada1cd180 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerInfo.cs @@ -0,0 +1,95 @@ +// +// CompletionTriggerInfo.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + /// <summary> + /// Provides information about what triggered completion. + /// </summary> + public struct CompletionTriggerInfo + { + /// <summary> + /// Provides the reason that completion was triggered. + /// </summary> + public CompletionTriggerReason CompletionTriggerReason { get; private set; } + + /// <summary> + /// If the <see cref="CompletionTriggerReason"/> was <see + /// cref="CompletionTriggerReason.CharTyped"/> then this was the character that was + /// typed or deleted by backspace. Otherwise it is null. + /// </summary> + public char? TriggerCharacter { get; private set; } + + /// <summary> + /// Returns true if the reason completion was triggered was to augment an existing list of + /// completion items. + /// </summary> + public bool IsAugment { get; private set; } + + /// <summary> + /// Returns true if completion was triggered by the debugger. + /// </summary> + public bool IsDebugger { get; private set; } + + /// <summary> + /// Return true if completion is running in the Immediate Window. + /// </summary> + public bool IsImmediateWindow { get; private set; } + + public CompletionTriggerInfo (CompletionTriggerReason completionTriggerReason, char? triggerCharacter = null, bool isAugment = false, bool isDebugger = false, bool isImmediateWindow = false) : this() + { + this.CompletionTriggerReason = completionTriggerReason; + this.TriggerCharacter = triggerCharacter; + this.IsAugment = isAugment; + this.IsDebugger = isDebugger; + this.IsImmediateWindow = isImmediateWindow; + } + + public CompletionTriggerInfo WithIsAugment(bool isAugment) + { + return this.IsAugment == isAugment + ? this + : new CompletionTriggerInfo(this.CompletionTriggerReason, this.TriggerCharacter, isAugment, this.IsDebugger, this.IsImmediateWindow); + } + + public CompletionTriggerInfo WithIsDebugger(bool isDebugger) + { + return this.IsDebugger == isDebugger + ? this + : new CompletionTriggerInfo(this.CompletionTriggerReason, this.TriggerCharacter, this.IsAugment, isDebugger, this.IsImmediateWindow); + } + + public CompletionTriggerInfo WithIsImmediateWindow(bool isImmediateWIndow) + { + return this.IsImmediateWindow == isImmediateWIndow + ? this + : new CompletionTriggerInfo(this.CompletionTriggerReason, this.TriggerCharacter, this.IsAugment, this.IsDebugger, isImmediateWIndow); + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerReason.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerReason.cs new file mode 100644 index 0000000000..4c27c4727b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/CompletionTriggerReason.cs @@ -0,0 +1,37 @@ +// +// CompletionTriggerReason.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public enum CompletionTriggerReason + { + CharTyped, + CompletionCommand + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/AttributeNamedParameterContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/AttributeNamedParameterContextHandler.cs new file mode 100644 index 0000000000..ef74da4307 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/AttributeNamedParameterContextHandler.cs @@ -0,0 +1,232 @@ +// +// AttributeNamedParameterContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; +using System.Collections.Immutable; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class AttributeNamedParameterContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + + var syntaxTree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxTree.IsInNonUserCode(position, cancellationToken)) + { + return null; + } + + var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken); + token = token.GetPreviousTokenIfTouchingWord(position); + + if (token.Kind() != SyntaxKind.OpenParenToken && token.Kind() != SyntaxKind.CommaToken) + { + return null; + } + + var attributeArgumentList = token.Parent as AttributeArgumentListSyntax; + var attributeSyntax = token.Parent.Parent as AttributeSyntax; + if (attributeSyntax == null || attributeArgumentList == null) + { + return null; + } + + // We actually want to collect two sets of named parameters to present the user. The + // normal named parameters that come from the attribute constructors. These will be + // presented like "foo:". And also the named parameters that come from the writable + // fields/properties in the attribute. These will be presented like "bar =". + + var existingNamedParameters = GetExistingNamedParameters(attributeArgumentList, position); + + var workspace = document.Project.Solution.Workspace; + var semanticModel = await document.GetCSharpSemanticModelForNodeAsync(attributeSyntax, cancellationToken).ConfigureAwait(false); + var nameColonItems = await GetNameColonItemsAsync(engine, workspace, semanticModel, position, token, attributeSyntax, existingNamedParameters, cancellationToken).ConfigureAwait(false); + var nameEqualsItems = await GetNameEqualsItemsAsync(engine, workspace, semanticModel, position, token, attributeSyntax, existingNamedParameters, cancellationToken).ConfigureAwait(false); + + // If we're after a name= parameter, then we only want to show name= parameters. + if (IsAfterNameEqualsArgument(token)) + { + return nameEqualsItems; + } + + return nameColonItems.Concat(nameEqualsItems); + } + + protected async Task<bool> IsExclusiveAsync(Document document, int caretPosition, CompletionTriggerInfo triggerInfo, CancellationToken cancellationToken) + { + var syntaxTree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var token = syntaxTree.FindTokenOnLeftOfPosition(caretPosition, cancellationToken) + .GetPreviousTokenIfTouchingWord(caretPosition); + + return IsAfterNameColonArgument(token) || IsAfterNameEqualsArgument(token); + } + + private bool IsAfterNameColonArgument(SyntaxToken token) + { + var argumentList = token.Parent as AttributeArgumentListSyntax; + if (token.Kind() == SyntaxKind.CommaToken && argumentList != null) + { + foreach (var item in argumentList.Arguments.GetWithSeparators()) + { + if (item.IsToken && item.AsToken() == token) + { + return false; + } + + if (item.IsNode) + { + var node = item.AsNode() as AttributeArgumentSyntax; + if (node.NameColon != null) + { + return true; + } + } + } + } + + return false; + } + + private bool IsAfterNameEqualsArgument(SyntaxToken token) + { + var argumentList = token.Parent as AttributeArgumentListSyntax; + if (token.Kind() == SyntaxKind.CommaToken && argumentList != null) + { + foreach (var item in argumentList.Arguments.GetWithSeparators()) + { + if (item.IsToken && item.AsToken() == token) + { + return false; + } + + if (item.IsNode) + { + var node = item.AsNode() as AttributeArgumentSyntax; + if (node.NameEquals != null) + { + return true; + } + } + } + } + + return false; + } + + private Task<IEnumerable<CompletionData>> GetNameEqualsItemsAsync(CompletionEngine engine, Workspace workspace, SemanticModel semanticModel, + int position, SyntaxToken token, AttributeSyntax attributeSyntax, ISet<string> existingNamedParameters, + CancellationToken cancellationToken) + { + var attributeNamedParameters = GetAttributeNamedParameters(semanticModel, position, attributeSyntax, cancellationToken); + // var unspecifiedNamedParameters = attributeNamedParameters.Where(p => !existingNamedParameters.Contains(p.Name)); + + // var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + return Task.FromResult ( + attributeNamedParameters + .Where (p => !existingNamedParameters.Contains (p.Name)) + .Select (p => { + var result = engine.Factory.CreateSymbolCompletionData (this, p); + result.DisplayFlags |= DisplayFlags.NamedArgument; + return (CompletionData)result; + })); + + + } + + private Task<IEnumerable<CompletionData>> GetNameColonItemsAsync( + CompletionEngine engine, Workspace workspace, SemanticModel semanticModel, int position, SyntaxToken token, AttributeSyntax attributeSyntax, ISet<string> existingNamedParameters, + CancellationToken cancellationToken) + { + var parameterLists = GetParameterLists(semanticModel, position, attributeSyntax, cancellationToken); + parameterLists = parameterLists.Where(pl => IsValid(pl, existingNamedParameters)); + + // var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + return Task.FromResult ( + from pl in parameterLists + from p in pl + where !existingNamedParameters.Contains (p.Name) + select engine.Factory.CreateGenericData(this, p.Name + ":", GenericDataType.NamedParameter)); + } + + private bool IsValid(ImmutableArray<IParameterSymbol> parameterList, ISet<string> existingNamedParameters) + { + return existingNamedParameters.Except(parameterList.Select(p => p.Name)).IsEmpty(); + } + + private ISet<string> GetExistingNamedParameters(AttributeArgumentListSyntax argumentList, int position) + { + var existingArguments1 = + argumentList.Arguments.Where(a => a.Span.End <= position) + .Where(a => a.NameColon != null) + .Select(a => a.NameColon.Name.Identifier.ValueText); + var existingArguments2 = + argumentList.Arguments.Where(a => a.Span.End <= position) + .Where(a => a.NameEquals != null) + .Select(a => a.NameEquals.Name.Identifier.ValueText); + + return existingArguments1.Concat(existingArguments2).ToSet(); + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetParameterLists( + SemanticModel semanticModel, + int position, + AttributeSyntax attribute, + CancellationToken cancellationToken) + { + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + var attributeType = semanticModel.GetTypeInfo(attribute, cancellationToken).Type as INamedTypeSymbol; + if (within != null && attributeType != null) + { + return attributeType.InstanceConstructors.Where(c => c.IsAccessibleWithin(within)) + .Select(c => c.Parameters); + } + + return SpecializedCollections.EmptyEnumerable<ImmutableArray<IParameterSymbol>>(); + } + + private IEnumerable<ISymbol> GetAttributeNamedParameters( + SemanticModel semanticModel, + int position, + AttributeSyntax attribute, + CancellationToken cancellationToken) + { + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + var attributeType = semanticModel.GetTypeInfo(attribute, cancellationToken).Type as INamedTypeSymbol; + return attributeType.GetAttributeNamedParameters(semanticModel.Compilation, within); + } + + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CastCompletionContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CastCompletionContextHandler.cs new file mode 100644 index 0000000000..233b58e555 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CastCompletionContextHandler.cs @@ -0,0 +1,108 @@ +// +// CastCompletionContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class CastCompletionContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var position = completionContext.Position; + var document = completionContext.Document; + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var syntaxTree = ctx.SyntaxTree; + if (syntaxTree.IsInNonUserCode(position, cancellationToken) || + syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + if (!syntaxTree.IsRightOfDotOrArrowOrColonColon(position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + var ma = ctx.LeftToken.Parent as MemberAccessExpressionSyntax; + if (ma == null) + return Enumerable.Empty<CompletionData> (); + + var model = ctx.CSharpSyntaxContext.SemanticModel; + + var symbolInfo = model.GetSymbolInfo (ma.Expression); + if (symbolInfo.Symbol == null) + return Enumerable.Empty<CompletionData> (); + + var list = new List<CompletionData> (); + var within = model.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + var addedSymbols = new HashSet<string> (); + foreach (var ifStmSyntax in ma.Expression.AncestorsAndSelf ().OfType<IfStatementSyntax> ()) { + var condition = ifStmSyntax.Condition.SkipParens (); + if (condition == null || !condition.IsKind (SyntaxKind.IsExpression)) + continue; + var isExpr = ((BinaryExpressionSyntax)condition); + var leftSymbol = model.GetSymbolInfo (isExpr.Left); + + if (leftSymbol.Symbol == symbolInfo.Symbol) { + var type = model.GetTypeInfo (isExpr.Right).Type; + if (type != null) { + Analyze (engine, ma.Expression, type, within, list, addedSymbols, cancellationToken); + } + } + } + + return list; + } + + void Analyze (CompletionEngine engine, SyntaxNode node, ITypeSymbol type, ISymbol within, List<CompletionData> list, HashSet<string> addedSymbols, CancellationToken cancellationToken) + { + var startType = type; + + while (type.SpecialType != SpecialType.System_Object) { + foreach (var member in type.GetMembers ()) { + cancellationToken.ThrowIfCancellationRequested (); + if (member.IsImplicitlyDeclared || member.IsStatic) + continue; + if (member.IsOrdinaryMethod () || member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property) { + if (member.IsAccessibleWithin (within)) { + var completionData = engine.Factory.CreateCastCompletionData (this, member, node, startType); + if (addedSymbols.Contains (completionData.DisplayText)) + continue; + addedSymbols.Add (completionData.DisplayText); + list.Add (completionData); + } + } + } + + type = type.BaseType; + } + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CompletionContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CompletionContextHandler.cs new file mode 100644 index 0000000000..0b8a673d73 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/CompletionContextHandler.cs @@ -0,0 +1,187 @@ +// +// CompletionContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.Text; +using System.Threading.Tasks; +using System.Security.Policy; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public abstract class CompletionContextHandler : ICompletionKeyHandler + { + public async Task<IEnumerable<CompletionData>> GetCompletionDataAsync (CompletionResult result, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken = default(CancellationToken)) + { + // If we were triggered by typign a character, then do a semantic check to make sure + // we're still applicable. If not, then return immediately. + if (info.CompletionTriggerReason == CompletionTriggerReason.CharTyped) + { + var isSemanticTriggerCharacter = await IsSemanticTriggerCharacterAsync(completionContext.Document, completionContext.Position - 1, cancellationToken).ConfigureAwait(false); + if (!isSemanticTriggerCharacter) + return null; + } + + return await GetItemsWorkerAsync(result, engine, completionContext, info, cancellationToken).ConfigureAwait(false); + + } + + protected virtual Task<bool> IsSemanticTriggerCharacterAsync(Document document, int characterPosition, CancellationToken cancellationToken) + { + return Task.FromResult (true); + } + + protected abstract Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult result, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken); + + static readonly char[] csharpCommitChars = { + ' ', '{', '}', '[', ']', '(', ')', '.', ',', ':', + ';', '+', '-', '*', '/', '%', '&', '|', '^', '!', + '~', '=', '<', '>', '?', '@', '#', '\'', '\"', '\\' + }; + + public virtual bool IsCommitCharacter (CompletionData completionItem, char ch, string textTypedSoFar) + { + return csharpCommitChars.Contains (ch); + } + + public virtual bool SendEnterThroughToEditor(CompletionData completionItem, string textTypedSoFar) + { + return string.Compare (completionItem.DisplayText, textTypedSoFar, StringComparison.OrdinalIgnoreCase) == 0; + } + + public virtual bool IsFilterCharacter(CompletionData completionItem, char ch, string textTypedSoFar) + { + return false; + } + + public virtual Task<bool> IsExclusiveAsync(Document document, int position, CompletionTriggerInfo triggerInfo, CancellationToken cancellationToken) + { + return Task.FromResult (false); + } + + public virtual bool IsTriggerCharacter (SourceText text, int position) + { + var ch = text [position]; + return ch == '.' || // simple member access + ch == '#' || // pre processor directives + ch == '>' && position >= 1 && text [position - 1] == '-' || // pointer member access + ch == ':' && position >= 1 && text [position - 1] == ':' || // alias name + IsStartingNewWord (text, position); + } + + internal protected static bool IsTriggerAfterSpaceOrStartOfWordCharacter(SourceText text, int characterPosition) + { + var ch = text[characterPosition]; + if (ch == ' ') { + ch = text[characterPosition - 1]; + return !char.IsWhiteSpace (ch); + } + return IsStartingNewWord(text, characterPosition); + } + + internal protected static bool IsStartingNewWord (SourceText text, int position) + { + var ch = text [position]; + if (!SyntaxFacts.IsIdentifierStartCharacter (ch)) + return false; + + if (position > 0 && IsWordCharacter (text [position - 1])) + return false; + + if (position < text.Length - 1 && IsWordCharacter (text [position + 1])) + return false; + + return true; + } + + protected static bool IsWordCharacter (char ch) + { + return SyntaxFacts.IsIdentifierStartCharacter (ch) || SyntaxFacts.IsIdentifierPartCharacter (ch); + } + + protected static bool IsOnStartLine(int position, SourceText text, int startLine) + { + return text.Lines.IndexOf(position) == startLine; + } + + protected static TextSpan GetTextChangeSpan(SourceText text, int position) + { + return GetTextChangeSpan(text, position, IsTextChangeSpanStartCharacter, IsWordCharacter); + } + + public static bool IsTextChangeSpanStartCharacter(char ch) + { + return ch == '@' || IsWordCharacter(ch); + } + + public static TextSpan GetTextChangeSpan(SourceText text, int position, + Func<char, bool> isWordStartCharacter, Func<char, bool> isWordCharacter) + { + int start = position; + while (start > 0 && isWordStartCharacter(text[start - 1])) + { + start--; + } + + // If we're brought up in the middle of a word, extend to the end of the word as well. + // This means that if a user brings up the completion list at the start of the word they + // will "insert" the text before what's already there (useful for qualifying existing + // text). However, if they bring up completion in the "middle" of a word, then they will + // "overwrite" the text. Useful for correcting misspellings or just replacing unwanted + // code with new code. + int end = position; + if (start != position) + { + while (end < text.Length && isWordCharacter(text[end])) + { + end++; + } + } + + return TextSpan.FromBounds(start, end); + } + + protected class UnionCompletionItemComparer : IEqualityComparer<CompletionData> + { + public static UnionCompletionItemComparer Instance = new UnionCompletionItemComparer(); + + public bool Equals(CompletionData x, CompletionData y) + { + return x.DisplayText == y.DisplayText; + } + + public int GetHashCode(CompletionData obj) + { + return obj.DisplayText.GetHashCode(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/DelegateCreationContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/DelegateCreationContextHandler.cs new file mode 100644 index 0000000000..987ebca4b8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/DelegateCreationContextHandler.cs @@ -0,0 +1,267 @@ +// +// DelegateCreationContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis.Text; +using ICSharpCode.NRefactory6.CSharp.ExtractMethod; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class DelegateCreationContextHandler : CompletionContextHandler + { + internal static readonly SymbolDisplayFormat NameFormat = + new SymbolDisplayFormat ( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + propertyStyle: SymbolDisplayPropertyStyle.NameOnly, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeExplicitInterface, + parameterOptions: + SymbolDisplayParameterOptions.IncludeParamsRefOut | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType | + SymbolDisplayParameterOptions.IncludeName, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | + SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + internal static readonly SymbolDisplayFormat overrideNameFormat = NameFormat.WithParameterOptions ( + SymbolDisplayParameterOptions.IncludeDefaultValue | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType | + SymbolDisplayParameterOptions.IncludeName | + SymbolDisplayParameterOptions.IncludeParamsRefOut); + + public override bool IsTriggerCharacter (SourceText text, int position) + { + var ch = text [position]; + return ch == '(' || ch == '[' || ch == ',' || IsTriggerAfterSpaceOrStartOfWordCharacter (text, position); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult result, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + + var tree = await document.GetSyntaxTreeAsync (cancellationToken).ConfigureAwait (false); + var model = await document.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + if (tree.IsInNonUserCode (position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + + if (!ctx.CSharpSyntaxContext.IsAnyExpressionContext) + return Enumerable.Empty<CompletionData> (); + var list = new List<CompletionData> (); + foreach (var type in ctx.InferredTypes) { + if (type.TypeKind != TypeKind.Delegate) + continue; + string delegateName = null; + + if (ctx.TargetToken.IsKind (SyntaxKind.PlusEqualsToken)) { + delegateName = GuessEventHandlerBaseName (ctx.LeftToken.Parent, ctx.ContainingTypeDeclaration); + } + + AddDelegateHandlers (list, ctx.TargetToken.Parent, model, engine, result, type, position, delegateName, cancellationToken); + } + if (list.Count > 0) { + result.AutoSelect = false; + } + return list; + } + + + static string GuessEventHandlerBaseName (SyntaxNode node, TypeDeclarationSyntax containingTypeDeclaration) + { + var addAssign = node as AssignmentExpressionSyntax; + if (addAssign == null) + return null; + + var ident = addAssign.Left as IdentifierNameSyntax; + if (ident != null) + return ToPascalCase (containingTypeDeclaration.Identifier + "_" + ident); + + var memberAccess = addAssign.Left as MemberAccessExpressionSyntax; + if (memberAccess != null) + return ToPascalCase (GetMemberAccessBaseName(memberAccess) + "_" + memberAccess.Name); + + return null; + } + + static string GetMemberAccessBaseName (MemberAccessExpressionSyntax memberAccess) + { + var ident = memberAccess.Expression as IdentifierNameSyntax; + if (ident != null) + return ident.ToString (); + + var ma = memberAccess.Expression as MemberAccessExpressionSyntax; + if (ma != null) + return ma.Name.ToString (); + + return "Handle"; + } + + static string ToPascalCase (string str) + { + var result = new StringBuilder (); + result.Append (char.ToUpper (str[0])); + bool nextUpper = false; + for (int i = 1; i < str.Length; i++) { + var ch = str [i]; + if (nextUpper && char.IsLetter (ch)) { + ch = char.ToUpper (ch); + nextUpper = false; + } + result.Append (ch); + if (ch == '_') + nextUpper = true; + } + + return result.ToString (); + } + + void AddDelegateHandlers (List<CompletionData> completionList, SyntaxNode parent, SemanticModel semanticModel, CompletionEngine engine, CompletionResult result, ITypeSymbol delegateType, int position, string optDelegateName, CancellationToken cancellationToken) + { + var delegateMethod = delegateType.GetDelegateInvokeMethod (); + result.PossibleDelegates.Add (delegateMethod); + + var thisLineIndent = ""; + string EolMarker = "\n"; + bool addSemicolon = true; + bool addDefault = true; + + string delegateEndString = EolMarker + thisLineIndent + "}" + (addSemicolon ? ";" : ""); + //bool containsDelegateData = completionList.Result.Any(d => d.DisplayText.StartsWith("delegate(")); + CompletionData item; + if (addDefault) { + item = engine.Factory.CreateAnonymousMethod ( + this, + "delegate", + "Creates anonymous delegate.", + "delegate {" + EolMarker + thisLineIndent, + delegateEndString + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + + //if (LanguageVersion.Major >= 5) + + item = engine.Factory.CreateAnonymousMethod ( + this, + "async delegate", + "Creates anonymous async delegate.", + "async delegate {" + EolMarker + thisLineIndent, + delegateEndString + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + } + + var sb = new StringBuilder ("("); + var sbWithoutTypes = new StringBuilder ("("); + for (int k = 0; k < delegateMethod.Parameters.Length; k++) { + if (k > 0) { + sb.Append (", "); + sbWithoutTypes.Append (", "); + } + sb.Append (delegateMethod.Parameters [k].ToMinimalDisplayString (semanticModel, position, overrideNameFormat)); + sbWithoutTypes.Append (delegateMethod.Parameters [k].Name); + } + + sb.Append (")"); + sbWithoutTypes.Append (")"); + var signature = sb.ToString () + .Replace (", params ", ", ") + .Replace ("(params ", "("); + + if (completionList.All (data => data.DisplayText != signature)) { + item = engine.Factory.CreateAnonymousMethod ( + this, + signature + " =>", + "Creates typed lambda expression.", + signature + " => ", + (addSemicolon ? ";" : "") + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + + // if (LanguageVersion.Major >= 5) { + + item = engine.Factory.CreateAnonymousMethod ( + this, + "async " + signature + " =>", + "Creates typed async lambda expression.", + "async " + signature + " => ", + (addSemicolon ? ";" : "") + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + + var signatureWithoutTypes = sbWithoutTypes.ToString (); + if (!delegateMethod.Parameters.Any (p => p.RefKind != RefKind.None) && completionList.All (data => data.DisplayText != signatureWithoutTypes)) { + item = engine.Factory.CreateAnonymousMethod ( + this, + signatureWithoutTypes + " =>", + "Creates typed lambda expression.", + signatureWithoutTypes + " => ", + (addSemicolon ? ";" : "") + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + + //if (LanguageVersion.Major >= 5) { + item = engine.Factory.CreateAnonymousMethod ( + this, + "async " + signatureWithoutTypes + " =>", + "Creates typed async lambda expression.", + "async " + signatureWithoutTypes + " => ", + (addSemicolon ? ";" : "") + ); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + + //} + } + } + string varName = optDelegateName ?? "Handle" + delegateType.Name; + + + var curType = semanticModel.GetEnclosingSymbol<INamedTypeSymbol> (position, cancellationToken); + var uniqueName = new UniqueNameGenerator (semanticModel).CreateUniqueMethodName (parent, varName); + item = engine.Factory.CreateNewMethodDelegate (this, delegateType, uniqueName, curType); + if (!completionList.Any (i => i.DisplayText == item.DisplayText)) + completionList.Add (item); + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/EnumMemberContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/EnumMemberContextHandler.cs new file mode 100644 index 0000000000..00d2693070 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/EnumMemberContextHandler.cs @@ -0,0 +1,117 @@ +// +// EnumMemberContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.Recommendations; +using Microsoft.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Options; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + +// public class CompletionEngineCache +// { +// public List<INamespace> namespaces; +// public ICompletionData[] importCompletion; +// } + + class EnumMemberContextHandler : CompletionContextHandler + { + public override bool IsCommitCharacter (CompletionData completionItem, char ch, string textTypedSoFar) + { + // Only commit on dot. + return ch == '.'; + } + + public override bool IsTriggerCharacter(SourceText text, int position) + { + // Bring up on space or at the start of a word, or after a ( or [. + // + // Note: we don't want to bring this up after traditional enum operators like & or |. + // That's because we don't like the experience where the enum appears directly after the + // operator. Instead, the user normally types <space> and we will bring up the list + // then. + var ch = text[position]; + return + ch == ' ' || + ch == '[' || + ch == '(' || + (/*options.GetOption(CompletionOptions.TriggerOnTypingLetters, LanguageNames.CSharp) && CompletionUtilities.*/IsStartingNewWord(text, position)); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait(false); + var model = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + var tree = await completionContext.Document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree.IsInNonUserCode(completionContext.Position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + + var token = tree.FindTokenOnLeftOfPosition(completionContext.Position, cancellationToken); + if (token.IsMandatoryNamedParameterPosition()) + return Enumerable.Empty<CompletionData> (); + var result = new List<CompletionData> (); + + foreach (var _type in ctx.InferredTypes) { + var type = _type; + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { + type = type.GetTypeArguments().FirstOrDefault(); + if (type == null) + continue; + } + + if (type.TypeKind != TypeKind.Enum) + continue; + if (!type.IsEditorBrowsable ()) + continue; + + // Does type have any aliases? + ISymbol alias = await type.FindApplicableAlias(completionContext.Position, model, cancellationToken).ConfigureAwait(false); + + if (string.IsNullOrEmpty(completionResult.DefaultCompletionString)) + completionResult.DefaultCompletionString = type.Name; + + result.Add (engine.Factory.CreateSymbolCompletionData(this, type, type.ToMinimalDisplayString(model, completionContext.Position, SymbolDisplayFormat.CSharpErrorMessageFormat))); + foreach (IFieldSymbol field in type.GetMembers().OfType<IFieldSymbol>()) { + if (field.DeclaredAccessibility == Accessibility.Public && (field.IsConst || field.IsStatic)) { + result.Add (engine.Factory.CreateEnumMemberCompletionData(this, alias, field)); + } + } + } + return result; + } + } + +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExplicitInterfaceContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExplicitInterfaceContextHandler.cs new file mode 100644 index 0000000000..389f5f7ab7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExplicitInterfaceContextHandler.cs @@ -0,0 +1,153 @@ +// +// ExplicitInterfaceContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.Text; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class ExplicitInterfaceContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter(SourceText text, int position) + { + return text[position] == '.'; + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var position = completionContext.Position; + var document = completionContext.Document; + var span = new TextSpan(position, 0); + var semanticModel = await document.GetCSharpSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false); + var syntaxTree = semanticModel.SyntaxTree; + // var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + + if (syntaxTree.IsInNonUserCode(position, cancellationToken) || + syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken)) + { + return Enumerable.Empty<CompletionData> (); + } + + if (!syntaxTree.IsRightOfDotOrArrowOrColonColon(position, cancellationToken)) + { + return Enumerable.Empty<CompletionData> (); + } + + var node = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken) + .GetPreviousTokenIfTouchingWord(position) + .Parent; + + if (node.Kind() == SyntaxKind.ExplicitInterfaceSpecifier) + { + return await GetCompletionsOffOfExplicitInterfaceAsync( + engine, document, semanticModel, position, ((ExplicitInterfaceSpecifierSyntax)node).Name, cancellationToken).ConfigureAwait(false); + } + + return Enumerable.Empty<CompletionData> (); + } + + private Task<IEnumerable<CompletionData>> GetCompletionsOffOfExplicitInterfaceAsync( + CompletionEngine engine, Document document, SemanticModel semanticModel, int position, NameSyntax name, CancellationToken cancellationToken) + { + // Bind the interface name which is to the left of the dot + var syntaxTree = semanticModel.SyntaxTree; + var nameBinding = semanticModel.GetSymbolInfo(name, cancellationToken); + // var context = CSharpSyntaxContext.CreateContext(document.Project.Solution.Workspace, semanticModel, position, cancellationToken); + + var symbol = nameBinding.Symbol as ITypeSymbol; + if (symbol == null || symbol.TypeKind != TypeKind.Interface) + { + return Task.FromResult (Enumerable.Empty<CompletionData> ()); + } + + var members = semanticModel.LookupSymbols ( + position: name.SpanStart, + container: symbol) + .Where (s => !s.IsStatic); + // .FilterToVisibleAndBrowsableSymbols(document.ShouldHideAdvancedMembers(), semanticModel.Compilation); + + // We're going to create a entry for each one, including the signature + var completions = new List<CompletionData>(); + +// var signatureDisplayFormat = +// new SymbolDisplayFormat( +// genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, +// memberOptions: +// SymbolDisplayMemberOptions.IncludeParameters, +// parameterOptions: +// SymbolDisplayParameterOptions.IncludeName | +// SymbolDisplayParameterOptions.IncludeType | +// SymbolDisplayParameterOptions.IncludeParamsRefOut, +// miscellaneousOptions: +// SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | +// SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + var namePosition = name.SpanStart; + + // var text = await context.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + // var textChangeSpan = GetTextChangeSpan(text, context.Position); + + foreach (var member in members) + { + // var displayString = member.ToMinimalDisplayString(semanticModel, namePosition, signatureDisplayFormat); + // var memberCopied = member; + // var insertionText = displayString; + + completions.Add(engine.Factory.CreateSymbolCompletionData (this, member) + + /*new SymbolCompletionItem( + this, + displayString, + insertionText: insertionText, + filterSpan: textChangeSpan, + position: position, + symbols: new List<ISymbol> { member }, + context: context) */); + } + + return Task.FromResult ((IEnumerable<CompletionData>)completions); + } + +// public override TextChange GetTextChange(CompletionItem selectedItem, char? ch = default(char), string textTypedSoFar = null) +// { +// if (ch.HasValue && ch.Value == '(') +// { +// return new TextChange(selectedItem.FilterSpan, ((SymbolCompletionItem)selectedItem).Symbols[0].Name); +// } +// +// return new TextChange(selectedItem.FilterSpan, selectedItem.DisplayText); +// } + + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExternAliasContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExternAliasContextHandler.cs new file mode 100644 index 0000000000..35620aad79 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ExternAliasContextHandler.cs @@ -0,0 +1,72 @@ +// +// ExternAliasContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class ExternAliasContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + if (tree.IsInNonUserCode(position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + + var targetToken = tree.FindTokenOnLeftOfPosition(position, cancellationToken).GetPreviousTokenIfTouchingWord(position); + if (targetToken.IsKind(SyntaxKind.AliasKeyword) && targetToken.Parent.IsKind(SyntaxKind.ExternAliasDirective)) + { + var compilation = await document.GetCSharpCompilationAsync(cancellationToken).ConfigureAwait(false); + var aliases = compilation.ExternalReferences.Where(r => r.Properties.Aliases != null).SelectMany(r => r.Properties.Aliases).ToSet(); + + if (aliases.Any()) + { + var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var usedAliases = root.ChildNodes().OfType<ExternAliasDirectiveSyntax>().Where(e => !e.Identifier.IsMissing).Select(e => e.Identifier.ValueText); + foreach (var used in usedAliases) { + aliases.Remove (used); + } + aliases.Remove(MetadataReferenceProperties.GlobalAlias); + return aliases.Select (e => engine.Factory.CreateGenericData (this, e, GenericDataType.Undefined)); + } + } + + return Enumerable.Empty<CompletionData> (); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/FormatItemContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/FormatItemContextHandler.cs new file mode 100644 index 0000000000..bf740b53bd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/FormatItemContextHandler.cs @@ -0,0 +1,256 @@ +// +// FormatItemContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class FormatItemContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter (Microsoft.CodeAnalysis.Text.SourceText text, int position) + { + var ch = text [position]; + return ch == ':' || ch == '"'; + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait(false); + var document = completionContext.Document; + var position = completionContext.Position; + var semanticModel = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + + if (ctx.TargetToken.Parent != null && ctx.TargetToken.Parent.Parent != null && + ctx.TargetToken.Parent.Parent.IsKind(SyntaxKind.Argument)) { + + if (ctx.TargetToken.Parent == null || !ctx.TargetToken.Parent.IsKind(SyntaxKind.StringLiteralExpression) || + ctx.TargetToken.Parent.Parent == null || !ctx.TargetToken.Parent.Parent.IsKind(SyntaxKind.Argument) || + ctx.TargetToken.Parent.Parent.Parent == null || !ctx.TargetToken.Parent.Parent.Parent.IsKind(SyntaxKind.ArgumentList) || + ctx.TargetToken.Parent.Parent.Parent.Parent == null || !ctx.TargetToken.Parent.Parent.Parent.Parent.IsKind(SyntaxKind.InvocationExpression)) { + return Enumerable.Empty<CompletionData> (); + } + var formatArgument = GetFormatItemNumber(document, position); + var invocationExpression = ctx.TargetToken.Parent.Parent.Parent.Parent as InvocationExpressionSyntax; + var symbolInfo = semanticModel.GetSymbolInfo(invocationExpression); + return GetFormatCompletionData(engine, semanticModel, invocationExpression, formatArgument, symbolInfo.Symbol); + } + + return Enumerable.Empty<CompletionData> (); + } + + + static readonly DateTime curDate = DateTime.Now; + + IEnumerable<CompletionData> GenerateNumberFormatitems(CompletionEngine engine, bool isFloatingPoint) + { + yield return engine.Factory.CreateFormatItemCompletionData(this, "D", "decimal", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "D5", "decimal", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "C", "currency", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "C0", "currency", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "E", "exponential", 1.23E4); + yield return engine.Factory.CreateFormatItemCompletionData(this, "E2", "exponential", 1.234); + yield return engine.Factory.CreateFormatItemCompletionData(this, "e2", "exponential", 1.234); + yield return engine.Factory.CreateFormatItemCompletionData(this, "F", "fixed-point", 123.45); + yield return engine.Factory.CreateFormatItemCompletionData(this, "F1", "fixed-point", 123.45); + yield return engine.Factory.CreateFormatItemCompletionData(this, "G", "general", 1.23E+56); + yield return engine.Factory.CreateFormatItemCompletionData(this, "g2", "general", 1.23E+56); + yield return engine.Factory.CreateFormatItemCompletionData(this, "N", "number", 12345.68); + yield return engine.Factory.CreateFormatItemCompletionData(this, "N1", "number", 12345.68); + yield return engine.Factory.CreateFormatItemCompletionData(this, "P", "percent", 12.34); + yield return engine.Factory.CreateFormatItemCompletionData(this, "P1", "percent", 12.34); + yield return engine.Factory.CreateFormatItemCompletionData(this, "R", "round-trip", 0.1230000001); + yield return engine.Factory.CreateFormatItemCompletionData(this, "X", "hexadecimal", 1234); + yield return engine.Factory.CreateFormatItemCompletionData(this, "x8", "hexadecimal", 1234); + yield return engine.Factory.CreateFormatItemCompletionData(this, "0000", "custom", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "####", "custom", 123); + yield return engine.Factory.CreateFormatItemCompletionData(this, "##.###", "custom", 1.23); + yield return engine.Factory.CreateFormatItemCompletionData(this, "##.000", "custom", 1.23); + yield return engine.Factory.CreateFormatItemCompletionData(this, "## 'items'", "custom", 12); + } + + IEnumerable<CompletionData> GenerateDateTimeFormatitems(CompletionEngine engine) + { + yield return engine.Factory.CreateFormatItemCompletionData(this, "D", "long date", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "d", "short date", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "F", "full date long", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "f", "full date short", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "G", "general long", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "g", "general short", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "M", "month", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "O", "ISO 8601", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "R", "RFC 1123", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "s", "sortable", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "T", "long time", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "t", "short time", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "U", "universal full", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "u", "universal sortable", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "Y", "year month", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "yy-MM-dd", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "yyyy MMMMM dd", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "yy-MMM-dd ddd", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "yyyy-M-d dddd", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "hh:mm:ss t z", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "hh:mm:ss tt zz", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "HH:mm:ss tt zz", "custom", curDate); + yield return engine.Factory.CreateFormatItemCompletionData(this, "HH:m:s tt zz", "custom", curDate); + + } + + [Flags] + enum TestEnum + { + EnumCaseName = 0, + Flag1 = 1, + Flag2 = 2, + Flags + } + + IEnumerable<CompletionData> GenerateEnumFormatitems(CompletionEngine engine) + { + yield return engine.Factory.CreateFormatItemCompletionData(this, "G", "string value", TestEnum.EnumCaseName); + yield return engine.Factory.CreateFormatItemCompletionData(this, "F", "flags value", TestEnum.Flags); + yield return engine.Factory.CreateFormatItemCompletionData(this, "D", "integer value", TestEnum.Flags); + yield return engine.Factory.CreateFormatItemCompletionData(this, "X", "hexadecimal", TestEnum.Flags); + } + + IEnumerable<CompletionData> GenerateTimeSpanFormatitems(CompletionEngine engine) + { + yield return engine.Factory.CreateFormatItemCompletionData(this, "c", "invariant", new TimeSpan(0, 1, 23, 456)); + yield return engine.Factory.CreateFormatItemCompletionData(this, "G", "general long", new TimeSpan(0, 1, 23, 456)); + yield return engine.Factory.CreateFormatItemCompletionData(this, "g", "general short", new TimeSpan(0, 1, 23, 456)); + } + + static Guid defaultGuid = Guid.NewGuid(); + + IEnumerable<CompletionData> GenerateGuidFormatitems(CompletionEngine engine) + { + yield return engine.Factory.CreateFormatItemCompletionData(this, "N", "digits", defaultGuid); + yield return engine.Factory.CreateFormatItemCompletionData(this, "D", "hypens", defaultGuid); + yield return engine.Factory.CreateFormatItemCompletionData(this, "B", "braces", defaultGuid); + yield return engine.Factory.CreateFormatItemCompletionData(this, "P", "parentheses", defaultGuid); + } + + + static int GetFormatItemNumber(Document document, int offset) + { + int number = 0; + var o = offset - 2; + var text = document.GetTextAsync().Result; + while (o > 0) { + char ch = text[o]; + if (ch == '{') + return number; + if (!char.IsDigit(ch)) + break; + number = number * 10 + ch - '0'; + o--; + } + return -1; + } + + IEnumerable<CompletionData> GetFormatCompletionForType(CompletionEngine engine, ITypeSymbol type) + { + if (type == null) { + return GenerateNumberFormatitems (engine, false) + .Concat (GenerateDateTimeFormatitems (engine)) + .Concat (GenerateTimeSpanFormatitems (engine)) + .Concat (GenerateEnumFormatitems (engine)) + .Concat (GenerateGuidFormatitems (engine)); + } + + switch (type.ToString()) { + case "long": + case "System.Int64": + case "ulong": + case "System.UInt64": + case "int": + case "System.Int32": + case "uint": + case "System.UInt32": + case "short": + case "System.Int16": + case "ushort": + case "System.UInt16": + case "byte": + case "System.Byte": + case "sbyte": + case "System.SByte": + return GenerateNumberFormatitems(engine, false); + case "float": + case "System.Single": + case "double": + case "System.Double": + case "decimal": + case "System.Decimal": + return GenerateNumberFormatitems(engine, true); + case "System.Enum": + return GenerateEnumFormatitems(engine); + case "System.DateTime": + return GenerateDateTimeFormatitems(engine); + case "System.TimeSpan": + return GenerateTimeSpanFormatitems(engine); + case "System.Guid": + return GenerateGuidFormatitems(engine); + } + return CompletionResult.Empty; + } + + IEnumerable<CompletionData> GetFormatCompletionData(CompletionEngine engine, SemanticModel semanticModel, InvocationExpressionSyntax invocationExpression, int formatArgument, ISymbol symbol) + { + var ma = invocationExpression.Expression as MemberAccessExpressionSyntax; + + if (ma != null && ma.Name.ToString () == "ToString") { + return GetFormatCompletionForType(engine, symbol != null ? symbol.ContainingType : null); + } else { + var method = symbol as IMethodSymbol; + if (method == null) + return Enumerable.Empty<CompletionData> (); + + ExpressionSyntax fmtArgumets; + IList<ExpressionSyntax> args; + if (FormatStringHelper.TryGetFormattingParameters(semanticModel, invocationExpression, out fmtArgumets, out args, null)) { + ITypeSymbol type = null; + if (formatArgument + 1< args.Count) { + var invokeArgument = semanticModel.GetSymbolInfo(args[formatArgument + 1]); + if (invokeArgument.Symbol != null) + type = invokeArgument.Symbol.GetReturnType(); + } + + return GetFormatCompletionForType(engine, type); + } + } + return Enumerable.Empty<CompletionData> (); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/KeywordContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/KeywordContextHandler.cs new file mode 100644 index 0000000000..074171be1b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/KeywordContextHandler.cs @@ -0,0 +1,244 @@ +// +// KeywordContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.Recommendations; +using Microsoft.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + internal sealed class RecommendedKeyword + { + public string Keyword { get; private set; } + public bool IsIntrinsic { get; private set; } + public bool ShouldFormatOnCommit { get; private set; } + + public RecommendedKeyword (string keyword, bool isIntrinsic = false, bool shouldFormatOnCommit = false) + { + this.Keyword = keyword; + this.IsIntrinsic = isIntrinsic; + this.ShouldFormatOnCommit = shouldFormatOnCommit; + } + + } + + internal interface IKeywordRecommender<TContext> + { + IEnumerable<RecommendedKeyword> RecommendKeywords(int position, TContext context, CancellationToken cancellationToken); + } + + class KeywordContextHandler : CompletionContextHandler + { + static readonly IKeywordRecommender<CSharpSyntaxContext>[] recommender = { + new AbstractKeywordRecommender(), + new AddKeywordRecommender(), + new AliasKeywordRecommender(), + new AscendingKeywordRecommender(), + new AsKeywordRecommender(), + new AssemblyKeywordRecommender(), + new AsyncKeywordRecommender(), + new AwaitKeywordRecommender(), + new BaseKeywordRecommender(), + new BoolKeywordRecommender(), + new BreakKeywordRecommender(), + new ByKeywordRecommender(), + new ByteKeywordRecommender(), + new CaseKeywordRecommender(), + new CatchKeywordRecommender(), + new CharKeywordRecommender(), + new CheckedKeywordRecommender(), + new ChecksumKeywordRecommender(), + new ClassKeywordRecommender(), + new ConstKeywordRecommender(), + new ContinueKeywordRecommender(), + new DecimalKeywordRecommender(), + new DefaultKeywordRecommender(), + new DefineKeywordRecommender(), + new DelegateKeywordRecommender(), + new DescendingKeywordRecommender(), + new DisableKeywordRecommender(), + new DoKeywordRecommender(), + new DoubleKeywordRecommender(), + new DynamicKeywordRecommender(), + new ElifKeywordRecommender(), + new ElseKeywordRecommender(), + new EndIfKeywordRecommender(), + new EndRegionKeywordRecommender(), + new EnumKeywordRecommender(), + new EqualsKeywordRecommender(), + new ErrorKeywordRecommender(), + new EventKeywordRecommender(), + new ExplicitKeywordRecommender(), + new ExternKeywordRecommender(), + new FalseKeywordRecommender(), + new FieldKeywordRecommender(), + new FinallyKeywordRecommender(), + new FixedKeywordRecommender(), + new FloatKeywordRecommender(), + new ForEachKeywordRecommender(), + new ForKeywordRecommender(), + new FromKeywordRecommender(), + new GetKeywordRecommender(), + new GlobalKeywordRecommender(), + new GotoKeywordRecommender(), + new GroupKeywordRecommender(), + new HiddenKeywordRecommender(), + new IfKeywordRecommender(), + new ImplicitKeywordRecommender(), + new InKeywordRecommender(), + new InterfaceKeywordRecommender(), + new InternalKeywordRecommender(), + new IntKeywordRecommender(), + new IntoKeywordRecommender(), + new IsKeywordRecommender(), + new JoinKeywordRecommender(), + new LetKeywordRecommender(), + new LineKeywordRecommender(), + new LockKeywordRecommender(), + new LongKeywordRecommender(), + new MethodKeywordRecommender(), + new ModuleKeywordRecommender(), + new NameOfKeywordRecommender(), + new NamespaceKeywordRecommender(), + new NewKeywordRecommender(), + new NullKeywordRecommender(), + new ObjectKeywordRecommender(), + new OnKeywordRecommender(), + new OperatorKeywordRecommender(), + new OrderByKeywordRecommender(), + new OutKeywordRecommender(), + new OverrideKeywordRecommender(), + new ParamKeywordRecommender(), + new ParamsKeywordRecommender(), + new PartialKeywordRecommender(), + new PragmaKeywordRecommender(), + new PrivateKeywordRecommender(), + new PropertyKeywordRecommender(), + new ProtectedKeywordRecommender(), + new PublicKeywordRecommender(), + new ReadOnlyKeywordRecommender(), + new ReferenceKeywordRecommender(), + new RefKeywordRecommender(), + new RegionKeywordRecommender(), + new RemoveKeywordRecommender(), + new RestoreKeywordRecommender(), + new ReturnKeywordRecommender(), + new SByteKeywordRecommender(), + new SealedKeywordRecommender(), + new SelectKeywordRecommender(), + new SetKeywordRecommender(), + new ShortKeywordRecommender(), + new SizeOfKeywordRecommender(), + new StackAllocKeywordRecommender(), + new StaticKeywordRecommender(), + new StringKeywordRecommender(), + new StructKeywordRecommender(), + new SwitchKeywordRecommender(), + new ThisKeywordRecommender(), + new ThrowKeywordRecommender(), + new TrueKeywordRecommender(), + new TryKeywordRecommender(), + new TypeKeywordRecommender(), + new TypeOfKeywordRecommender(), + new TypeVarKeywordRecommender(), + new UIntKeywordRecommender(), + new ULongKeywordRecommender(), + new UncheckedKeywordRecommender(), + new UndefKeywordRecommender(), + new UnsafeKeywordRecommender(), + new UShortKeywordRecommender(), + new UsingKeywordRecommender(), + new VarKeywordRecommender(), + new VirtualKeywordRecommender(), + new VoidKeywordRecommender(), + new VolatileKeywordRecommender(), + new WarningKeywordRecommender(), +// new WhenKeywordRecommender(), + new WhereKeywordRecommender(), + new WhileKeywordRecommender(), + new YieldKeywordRecommender() + }; + + public override bool IsTriggerCharacter (Microsoft.CodeAnalysis.Text.SourceText text, int position) + { + var ch = text [position]; + return ch == '#' || + ch == ' ' && position >= 1 && !char.IsWhiteSpace (text [position - 1]) || + IsStartingNewWord (text, position); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var model = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + if (ctx.CSharpSyntaxContext.IsInNonUserCode) { + return Enumerable.Empty<CompletionData> (); + } + + if (ctx.TargetToken.IsKind (SyntaxKind.OverrideKeyword)) + return Enumerable.Empty<CompletionData> (); + + if (info.CompletionTriggerReason == CompletionTriggerReason.CharTyped && info.TriggerCharacter == ' ') { + if (!ctx.CSharpSyntaxContext.IsEnumBaseListContext && !ctx.LeftToken.IsKind (SyntaxKind.EqualsToken) && !ctx.LeftToken.IsKind (SyntaxKind.EqualsEqualsToken)) + return Enumerable.Empty<CompletionData> (); +// completionResult.AutoCompleteEmptyMatch = false; + } + + var result = new List<CompletionData> (); + + foreach (var r in recommender) { + var recommended = r.RecommendKeywords (completionContext.Position, ctx.CSharpSyntaxContext, cancellationToken); + if (recommended == null) + continue; + foreach (var kw in recommended) { + result.Add (engine.Factory.CreateGenericData (this, kw.Keyword, GenericDataType.Keyword)); + } + } + +// if (ctx.IsPreProcessorKeywordContext) { +// foreach (var kw in preprocessorKeywords) +// result.Add(factory.CreateGenericData (this, kw, GenericDataType.PreprocessorKeyword)); +// } +// + +// if (parent.IsKind(SyntaxKind.TypeParameterConstraintClause)) { +// result.Add(factory.CreateGenericData (this, "new()", GenericDataType.PreprocessorKeyword)); +// } + return result; + } + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/NamedParameterContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/NamedParameterContextHandler.cs new file mode 100644 index 0000000000..4eb8cff5ce --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/NamedParameterContextHandler.cs @@ -0,0 +1,218 @@ +// +// NamedParameterContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class NamedParameterContextHandler : CompletionContextHandler, IEqualityComparer<IParameterSymbol> + { + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + var syntaxTree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxTree.IsInNonUserCode(position, cancellationToken)) + { + return null; + } + + var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken); + token = token.GetPreviousTokenIfTouchingWord(position); + + if (token.Kind() != SyntaxKind.OpenParenToken && + token.Kind() != SyntaxKind.OpenBracketToken && + token.Kind() != SyntaxKind.CommaToken) + { + return null; + } + + var argumentList = token.Parent as BaseArgumentListSyntax; + if (argumentList == null) + { + return null; + } + + var semanticModel = await document.GetCSharpSemanticModelForNodeAsync(argumentList, cancellationToken).ConfigureAwait(false); + var parameterLists = GetParameterLists(semanticModel, position, argumentList.Parent, cancellationToken); + if (parameterLists == null) + { + return null; + } + + var existingNamedParameters = GetExistingNamedParameters(argumentList, position); + parameterLists = parameterLists.Where(pl => IsValid(pl, existingNamedParameters)); + + var unspecifiedParameters = parameterLists.SelectMany(pl => pl) + .Where(p => !existingNamedParameters.Contains(p.Name)) + .Distinct(this); + + // var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + return unspecifiedParameters + .Select(p => engine.Factory.CreateGenericData(this, p.Name + ":", GenericDataType.NamedParameter)); + } + + + private bool IsValid(ImmutableArray<IParameterSymbol> parameterList, ISet<string> existingNamedParameters) + { + // A parameter list is valid if it has parameters that match in name all the existing + // named parameters that have been provided. + return existingNamedParameters.Except(parameterList.Select(p => p.Name)).IsEmpty(); + } + + private ISet<string> GetExistingNamedParameters(BaseArgumentListSyntax argumentList, int position) + { + var existingArguments = argumentList.Arguments.Where(a => a.Span.End <= position && a.NameColon != null) + .Select(a => a.NameColon.Name.Identifier.ValueText); + + return existingArguments.ToSet(); + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetParameterLists( + SemanticModel semanticModel, + int position, + SyntaxNode invocableNode, + CancellationToken cancellationToken) + { + return invocableNode.TypeSwitch( + (InvocationExpressionSyntax invocationExpression) => GetInvocationExpressionParameterLists(semanticModel, position, invocationExpression, cancellationToken), + (ConstructorInitializerSyntax constructorInitializer) => GetConstructorInitializerParameterLists(semanticModel, position, constructorInitializer, cancellationToken), + (ElementAccessExpressionSyntax elementAccessExpression) => GetElementAccessExpressionParameterLists(semanticModel, position, elementAccessExpression, cancellationToken), + (ObjectCreationExpressionSyntax objectCreationExpression) => GetObjectCreationExpressionParameterLists(semanticModel, position, objectCreationExpression, cancellationToken)); + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetObjectCreationExpressionParameterLists( + SemanticModel semanticModel, + int position, + ObjectCreationExpressionSyntax objectCreationExpression, + CancellationToken cancellationToken) + { + var type = semanticModel.GetTypeInfo(objectCreationExpression, cancellationToken).Type as INamedTypeSymbol; + var within = semanticModel.GetEnclosingNamedType(position, cancellationToken); + if (type != null && within != null && type.TypeKind != TypeKind.Delegate) + { + return type.InstanceConstructors.Where(c => c.IsAccessibleWithin(within)) + .Select(c => c.Parameters); + } + + return null; + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetElementAccessExpressionParameterLists( + SemanticModel semanticModel, + int position, + ElementAccessExpressionSyntax elementAccessExpression, + CancellationToken cancellationToken) + { + var expressionSymbol = semanticModel.GetSymbolInfo(elementAccessExpression.Expression, cancellationToken).GetAnySymbol(); + var expressionType = semanticModel.GetTypeInfo(elementAccessExpression.Expression, cancellationToken).Type; + + if (expressionSymbol != null && expressionType != null) + { + var indexers = semanticModel.LookupSymbols(position, expressionType, WellKnownMemberNames.Indexer).OfType<IPropertySymbol>(); + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + if (within != null) + { + return indexers.Where(i => i.IsAccessibleWithin(within, throughTypeOpt: expressionType)) + .Select(i => i.Parameters); + } + } + + return null; + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetConstructorInitializerParameterLists( + SemanticModel semanticModel, + int position, + ConstructorInitializerSyntax constructorInitializer, + CancellationToken cancellationToken) + { + var within = semanticModel.GetEnclosingNamedType(position, cancellationToken); + if (within != null && + (within.TypeKind == TypeKind.Struct || within.TypeKind == TypeKind.Class)) + { + var type = constructorInitializer.Kind() == SyntaxKind.BaseConstructorInitializer + ? within.BaseType + : within; + + if (type != null) + { + return type.InstanceConstructors.Where(c => c.IsAccessibleWithin(within)) + .Select(c => c.Parameters); + } + } + + return null; + } + + private IEnumerable<ImmutableArray<IParameterSymbol>> GetInvocationExpressionParameterLists( + SemanticModel semanticModel, + int position, + InvocationExpressionSyntax invocationExpression, + CancellationToken cancellationToken) + { + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + if (within != null) + { + var methodGroup = semanticModel.GetMemberGroup(invocationExpression.Expression, cancellationToken).OfType<IMethodSymbol>(); + var expressionType = semanticModel.GetTypeInfo(invocationExpression.Expression, cancellationToken).Type as INamedTypeSymbol; + + if (methodGroup.Any()) + { + return methodGroup.Where(m => m.IsAccessibleWithin(within)) + .Select(m => m.Parameters); + } + else if (expressionType.IsDelegateType()) + { + var delegateType = (INamedTypeSymbol)expressionType; + return SpecializedCollections.SingletonEnumerable(delegateType.DelegateInvokeMethod.Parameters); + } + } + + return null; + } + + bool IEqualityComparer<IParameterSymbol>.Equals(IParameterSymbol x, IParameterSymbol y) + { + return x.Name.Equals(y.Name); + } + + int IEqualityComparer<IParameterSymbol>.GetHashCode(IParameterSymbol obj) + { + return obj.Name.GetHashCode(); + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectCreationContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectCreationContextHandler.cs new file mode 100644 index 0000000000..2a15d15524 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectCreationContextHandler.cs @@ -0,0 +1,187 @@ +// +// ObjectCreationContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class ObjectCreationContextHandler : CompletionContextHandler + { + // static readonly ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders.NewKeywordRecommender nkr = new ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders.NewKeywordRecommender (); + + public override bool IsTriggerCharacter (Microsoft.CodeAnalysis.Text.SourceText text, int position) + { + return IsTriggerAfterSpaceOrStartOfWordCharacter (text, position); + } + + public override bool IsCommitCharacter (CompletionData completionItem, char ch, string textTypedSoFar) + { + return ch == ' ' || ch == '(' || ch == '{' || ch == '['; + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult result, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var list = new List<CompletionData> (); + + var newExpression = GetObjectCreationNewExpression (ctx.SyntaxTree, completionContext.Position, cancellationToken); + if (newExpression == null) { + if (ctx.SyntaxTree.IsInNonUserCode(completionContext.Position, cancellationToken) || + ctx.SyntaxTree.IsPreProcessorDirectiveContext(completionContext.Position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + +// if (!nkr.IsValid (completionContext.Position, ctx.CSharpSyntaxContext, cancellationToken)) +// return Enumerable.Empty<ICompletionData> (); + + var tokenOnLeftOfPosition = ctx.SyntaxTree.FindTokenOnLeftOfPosition (completionContext.Position, cancellationToken); + if (!tokenOnLeftOfPosition.IsKind (SyntaxKind.EqualsToken) && !tokenOnLeftOfPosition.Parent.IsKind (SyntaxKind.EqualsValueClause)) + return Enumerable.Empty<CompletionData> (); + + foreach (var inferredType in SyntaxContext.InferenceService.InferTypes (ctx.CSharpSyntaxContext.SemanticModel, completionContext.Position, cancellationToken)) { + if (inferredType.IsEnumType () || inferredType.IsInterfaceType () || inferredType.IsAbstract) + continue; + foreach (var symbol in await GetPreselectedSymbolsWorker(ctx.CSharpSyntaxContext, inferredType, completionContext.Position - 1, cancellationToken)) { + var symbolCompletionData = engine.Factory.CreateObjectCreation (this, inferredType, symbol, completionContext.Position, false); + list.Add (symbolCompletionData); + } + } + return list; + } + + var type = SyntaxContext.InferenceService.InferType (ctx.CSharpSyntaxContext.SemanticModel, newExpression, objectAsDefault: false, cancellationToken: cancellationToken); + + foreach (var symbol in await GetPreselectedSymbolsWorker(ctx.CSharpSyntaxContext, type, completionContext.Position, cancellationToken)) { + var symbolCompletionData = engine.Factory.CreateObjectCreation (this, type, symbol, newExpression.SpanStart, true); + list.Add (symbolCompletionData); + if (string.IsNullOrEmpty (result.DefaultCompletionString)) + result.DefaultCompletionString = symbolCompletionData.DisplayText; + } + return list; + } + + + static Task<IEnumerable<ISymbol>> GetPreselectedSymbolsWorker2 (CSharpSyntaxContext context, ITypeSymbol type, CancellationToken cancellationToken) + { + // Unwrap an array type fully. We only want to offer the underlying element type in the + // list of completion items. + bool isArray = false; + while (type is IArrayTypeSymbol) { + isArray = true; + type = ((IArrayTypeSymbol)type).ElementType; + } + + if (type == null) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + + // Unwrap nullable + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { + type = type.GetTypeArguments ().FirstOrDefault (); + } + + if (type.SpecialType == SpecialType.System_Void) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + + if (type.ContainsAnonymousType ()) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + + if (!type.CanBeReferencedByName) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + + // Normally the user can't say things like "new IList". Except for "IList[] x = new |". + // In this case we do want to allow them to preselect certain types in the completion + // list even if they can't new them directly. + if (!isArray) { + if (type.TypeKind == TypeKind.Interface || + type.TypeKind == TypeKind.Pointer || + type.TypeKind == TypeKind.Dynamic || + type.IsAbstract) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + + if (type.TypeKind == TypeKind.TypeParameter && + !((ITypeParameterSymbol)type).HasConstructorConstraint) { + return Task.FromResult (Enumerable.Empty<ISymbol> ()); + } + } + +// if (!type.IsEditorBrowsable(options.GetOption(RecommendationOptions.HideAdvancedMembers, context.SemanticModel.Language), context.SemanticModel.Compilation)) +// { +// return SpecializedTasks.EmptyEnumerable<ISymbol>(); +// } +// + return Task.FromResult (SpecializedCollections.SingletonEnumerable ((ISymbol)type)); + } + + static async Task<IEnumerable<ISymbol>> GetPreselectedSymbolsWorker (CSharpSyntaxContext context, ITypeSymbol inferredType, int position, CancellationToken cancellationToken) + { + var result = await GetPreselectedSymbolsWorker2 (context, inferredType, cancellationToken).ConfigureAwait (false); + if (result.Any ()) { + var type = (ITypeSymbol)result.Single (); + var alias = await type.FindApplicableAlias (position, context.SemanticModel, cancellationToken).ConfigureAwait (false); + if (alias != null) { + return SpecializedCollections.SingletonEnumerable (alias); + } + } + + return result; + } + + internal static SyntaxNode GetObjectCreationNewExpression (SyntaxTree tree, int position, CancellationToken cancellationToken) + { + if (tree != null) { + if (!tree.IsInNonUserCode (position, cancellationToken)) { + var tokenOnLeftOfPosition = tree.FindTokenOnLeftOfPosition (position, cancellationToken); + var newToken = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord (position); + + // Only after 'new'. + if (newToken.Kind () == SyntaxKind.NewKeyword) { + // Only if the 'new' belongs to an object creation expression (and isn't a 'new' + // modifier on a member). + if (tree.IsObjectCreationTypeContext (position, tokenOnLeftOfPosition, cancellationToken)) { + return newToken.Parent as ExpressionSyntax; + } + } + } + } + + return null; + } + + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectInitializerContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectInitializerContextHandler.cs new file mode 100644 index 0000000000..0e99411d2e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/ObjectInitializerContextHandler.cs @@ -0,0 +1,250 @@ +// +// ObjectInitializerContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class ObjectInitializerContextHandler : CompletionContextHandler + { + public override bool SendEnterThroughToEditor (CompletionData completionItem, string textTypedSoFar) + { + return false; + } + + public override async Task<bool> IsExclusiveAsync (Document document, int position, CompletionTriggerInfo triggerInfo, CancellationToken cancellationToken) + { + // We're exclusive if this context could only be an object initializer and not also a + // collection initializer. If we're initializing something that could be initialized as + // an object or as a collection, say we're not exclusive. That way the rest of + // intellisense can be used in the collection intitializer. + // + // Consider this case: + + // class c : IEnumerable<int> + // { + // public void Add(int addend) { } + // public int foo; + // } + + // void foo() + // { + // var b = new c {| + // } + + // There we could initialize b using either an object initializer or a collection + // initializer. Since we don't know which the user will use, we'll be non-exclusive, so + // the other providers can help the user write the collection initializer, if they want + // to. + var tree = await document.GetCSharpSyntaxTreeAsync (cancellationToken).ConfigureAwait (false); + + if (tree.IsInNonUserCode (position, cancellationToken)) { + return false; + } + + var token = tree.FindTokenOnLeftOfPosition (position, cancellationToken); + token = token.GetPreviousTokenIfTouchingWord (position); + + if (token.Parent == null) { + return false; + } + + var expression = token.Parent.Parent as ExpressionSyntax; + if (expression == null) { + return false; + } + + var semanticModel = await document.GetCSharpSemanticModelForNodeAsync (expression, cancellationToken).ConfigureAwait (false); + var initializedType = semanticModel.GetTypeInfo (expression, cancellationToken).Type; + if (initializedType == null) { + return false; + } + + // Non-exclusive if initializedType can be initialized as a collection. + if (initializedType.CanSupportCollectionInitializer ()) { + return false; + } + + // By default, only our member names will show up. + return true; + } + + public override bool IsTriggerCharacter (Microsoft.CodeAnalysis.Text.SourceText text, int characterPosition) + { + return base.IsTriggerCharacter (text, characterPosition) || text [characterPosition] == ' '; + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + var workspace = document.Project.Solution.Workspace; + var semanticModel = await document.GetSemanticModelForSpanAsync (new TextSpan (position, 0), cancellationToken).ConfigureAwait (false); + var typeAndLocation = GetInitializedType (document, semanticModel, position, cancellationToken); + if (typeAndLocation == null) + return Enumerable.Empty<CompletionData> (); + + var initializedType = typeAndLocation.Item1 as INamedTypeSymbol; + var initializerLocation = typeAndLocation.Item2; + if (initializedType == null) + return Enumerable.Empty<CompletionData> (); + + // Find the members that can be initialized. If we have a NamedTypeSymbol, also get the overridden members. + IEnumerable<ISymbol> members = semanticModel.LookupSymbols (position, initializedType); + members = members.Where (m => IsInitializable (m, initializedType) && + m.CanBeReferencedByName && + IsLegalFieldOrProperty (m) && + !m.IsImplicitlyDeclared); + + // Filter out those members that have already been typed + var alreadyTypedMembers = GetInitializedMembers (semanticModel.SyntaxTree, position, cancellationToken); + var uninitializedMembers = members.Where (m => !alreadyTypedMembers.Contains (m.Name)); + + uninitializedMembers = uninitializedMembers.Where (m => m.IsEditorBrowsable ()); + + // var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + // var changes = GetTextChangeSpan(text, position); + var list = new List<CompletionData> (); + + // Return the members + foreach (var member in uninitializedMembers) { + list.Add (engine.Factory.CreateSymbolCompletionData (this, member)); + } + return list; + } + + static bool IsLegalFieldOrProperty (ISymbol symbol) + { + var type = symbol.GetMemberType (); + if (type != null && type.CanSupportCollectionInitializer ()) { + return true; + } + + return symbol.IsWriteableFieldOrProperty (); + } + + + static bool IsInitializable (ISymbol member, INamedTypeSymbol containingType) + { + var propertySymbol = member as IPropertySymbol; + if (propertySymbol != null) { + if (propertySymbol.Parameters.Any (p => !p.IsOptional)) + return false; + } + + + return + !member.IsStatic && + member.MatchesKind (SymbolKind.Field, SymbolKind.Property) && + member.IsAccessibleWithin (containingType); + } + + + static Tuple<ITypeSymbol, Location> GetInitializedType (Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken) + { + var tree = semanticModel.SyntaxTree; + if (tree.IsInNonUserCode (position, cancellationToken)) { + return null; + } + + var token = tree.FindTokenOnLeftOfPosition (position, cancellationToken); + token = token.GetPreviousTokenIfTouchingWord (position); + + if (token.Kind () != SyntaxKind.CommaToken && token.Kind () != SyntaxKind.OpenBraceToken) { + return null; + } + + if (token.Parent == null || token.Parent.Parent == null) { + return null; + } + + // If we got a comma, we can syntactically find out if we're in an ObjectInitializerExpression + if (token.Kind () == SyntaxKind.CommaToken && + token.Parent.Kind () != SyntaxKind.ObjectInitializerExpression) { + return null; + } + + // new Foo { bar = $$ + if (token.Parent.Parent.IsKind (SyntaxKind.ObjectCreationExpression)) { + var objectCreation = token.Parent.Parent as ObjectCreationExpressionSyntax; + if (objectCreation == null) { + return null; + } + + var ctor = semanticModel.GetSymbolInfo (objectCreation, cancellationToken).Symbol; + var type = ctor != null ? ctor.ContainingType : null; + if (type == null) { + type = semanticModel.GetSpeculativeTypeInfo (objectCreation.SpanStart, objectCreation.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type as INamedTypeSymbol; + } + + return Tuple.Create<ITypeSymbol, Location> (type, token.GetLocation ()); + } + + // Nested: new Foo { bar = { $$ + if (token.Parent.Parent.IsKind (SyntaxKind.SimpleAssignmentExpression)) { + // Use the type inferrer to get the type being initialzied. + var typeInferenceService = TypeGuessing.typeInferenceService; + var parentInitializer = token.GetAncestor<InitializerExpressionSyntax> (); + + var expectedType = typeInferenceService.InferType (semanticModel, parentInitializer, objectAsDefault: false, cancellationToken: cancellationToken); + return Tuple.Create (expectedType, token.GetLocation ()); + } + + return null; + } + + static HashSet<string> GetInitializedMembers (SyntaxTree tree, int position, CancellationToken cancellationToken) + { + var token = tree.FindTokenOnLeftOfPosition (position, cancellationToken) + .GetPreviousTokenIfTouchingWord (position); + + // We should have gotten back a { or , + if (token.Kind () == SyntaxKind.CommaToken || token.Kind () == SyntaxKind.OpenBraceToken) { + if (token.Parent != null) { + var initializer = token.Parent as InitializerExpressionSyntax; + + if (initializer != null) { + return new HashSet<string> (initializer.Expressions.OfType<AssignmentExpressionSyntax> () + .Where (b => b.OperatorToken.Kind () == SyntaxKind.EqualsToken) + .Select (b => b.Left) + .OfType<IdentifierNameSyntax> () + .Select (i => i.Identifier.ValueText)); + } + } + } + + return new HashSet<string> (); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/OverrideContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/OverrideContextHandler.cs new file mode 100644 index 0000000000..e79c538df9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/OverrideContextHandler.cs @@ -0,0 +1,372 @@ +// +// OverrideContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using System; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class OverrideContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter (SourceText text, int position) + { + return IsTriggerAfterSpaceOrStartOfWordCharacter (text, position); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + // var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var document = completionContext.Document; + var semanticModel = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + var tree = await document.GetSyntaxTreeAsync (cancellationToken).ConfigureAwait (false); + if (tree.IsInNonUserCode(completionContext.Position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + + var text = await document.GetTextAsync (cancellationToken).ConfigureAwait (false); + + var startLineNumber = text.Lines.IndexOf (completionContext.Position); + + // modifiers* override modifiers* type? | + Accessibility seenAccessibility; + //DeclarationModifiers modifiers; + var token = tree.FindTokenOnLeftOfPosition(completionContext.Position, cancellationToken); + if (token.Parent == null) + return Enumerable.Empty<CompletionData> (); + + var parentMember = token.Parent.AncestorsAndSelf ().OfType<MemberDeclarationSyntax> ().FirstOrDefault (m => !m.IsKind (SyntaxKind.IncompleteMember)); + + if (!(parentMember is BaseTypeDeclarationSyntax) && + + /* May happen in case: + * + * override $ + * public override string Foo () {} + */ + !(token.IsKind (SyntaxKind.OverrideKeyword) && token.Span.Start <= parentMember.Span.Start)) + return Enumerable.Empty<CompletionData> (); + + var position = completionContext.Position; + var startToken = token.GetPreviousTokenIfTouchingWord(position); + ITypeSymbol returnType; + SyntaxToken tokenBeforeReturnType; + TryDetermineReturnType (startToken, semanticModel, cancellationToken, out returnType, out tokenBeforeReturnType); + if (returnType == null) { + var enclosingType = semanticModel.GetEnclosingSymbol (position, cancellationToken) as INamedTypeSymbol; + if (enclosingType != null && (startToken.IsKind (SyntaxKind.OpenBraceToken) || startToken.IsKind (SyntaxKind.CloseBraceToken) || startToken.IsKind (SyntaxKind.SemicolonToken))) { + return CreateCompletionData (engine, semanticModel, position, returnType, Accessibility.NotApplicable, startToken, tokenBeforeReturnType, false, cancellationToken); + } + } + + if (!TryDetermineModifiers(ref tokenBeforeReturnType, text, startLineNumber, out seenAccessibility/*, out modifiers*/) || + !TryCheckForTrailingTokens (tree, text, startLineNumber, position, cancellationToken)) { + return Enumerable.Empty<CompletionData> (); + } + + return CreateCompletionData (engine, semanticModel, position, returnType, seenAccessibility, startToken, tokenBeforeReturnType, true, cancellationToken); + } + + protected virtual IEnumerable<CompletionData> CreateCompletionData (CompletionEngine engine, SemanticModel semanticModel, int position, ITypeSymbol returnType, Accessibility seenAccessibility, SyntaxToken startToken, SyntaxToken tokenBeforeReturnType, bool afterKeyword, CancellationToken cancellationToken) + { + var result = new List<CompletionData> (); + ISet<ISymbol> overridableMembers; + if (!TryDetermineOverridableMembers (semanticModel, tokenBeforeReturnType, seenAccessibility, out overridableMembers, cancellationToken)) { + return result; + } + if (returnType != null) { + overridableMembers = FilterOverrides (overridableMembers, returnType); + } + var curType = semanticModel.GetEnclosingSymbol<INamedTypeSymbol> (position, cancellationToken); + var declarationBegin = afterKeyword ? startToken.Parent.SpanStart : position - 1; + foreach (var m in overridableMembers) { + var data = engine.Factory.CreateNewOverrideCompletionData (this, declarationBegin, curType, m, afterKeyword); + result.Add (data); + } + return result; + } + + protected static ISet<ISymbol> FilterOverrides(ISet<ISymbol> members, ITypeSymbol returnType) + { + var filteredMembers = new HashSet<ISymbol>( + from m in members + where m.GetReturnType ().ToString () == returnType.ToString () + select m); + + // Don't filter by return type if we would then have nothing to show. + // This way, the user gets completion even if they speculatively typed the wrong return type + if (filteredMembers.Count > 0) + { + members = filteredMembers; + } + + return members; + } + + static bool TryDetermineReturnType(SyntaxToken startToken, SemanticModel semanticModel, CancellationToken cancellationToken, out ITypeSymbol returnType, out SyntaxToken nextToken) + { + nextToken = startToken; + returnType = null; + if (startToken.Parent is TypeSyntax) + { + var typeSyntax = (TypeSyntax)startToken.Parent; + + // 'partial' is actually an identifier. If we see it just bail. This does mean + // we won't handle overrides that actually return a type called 'partial'. And + // not a single tear was shed. + if (typeSyntax is IdentifierNameSyntax && + ((IdentifierNameSyntax)typeSyntax).Identifier.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword)) + { + return false; + } + + returnType = semanticModel.GetTypeInfo(typeSyntax, cancellationToken).Type; + nextToken = typeSyntax.GetFirstToken().GetPreviousToken(); + } + + return true; + } + + + static bool HasOverridden (ISymbol original, ISymbol testSymbol) + { + if (original.Kind != testSymbol.Kind) + return false; + switch (testSymbol.Kind) { + case SymbolKind.Method: + return ((IMethodSymbol)testSymbol).OverriddenMethod == original; + case SymbolKind.Property: + return ((IPropertySymbol)testSymbol).OverriddenProperty == original; + case SymbolKind.Event: + return ((IEventSymbol)testSymbol).OverriddenEvent == original; + } + return false; + } + + public static bool IsOverridable(ISymbol member, INamedTypeSymbol containingType) + { + if (member.IsAbstract || member.IsVirtual || member.IsOverride) { + if (member.IsSealed) { + return false; + } + + if (!member.IsAccessibleWithin(containingType)) { + return false; + } + + switch (member.Kind) { + case SymbolKind.Event: + return true; + case SymbolKind.Method: + return ((IMethodSymbol)member).MethodKind == MethodKind.Ordinary; + case SymbolKind.Property: + return !((IPropertySymbol)member).IsWithEvents; + } + } + return false; + } + + static bool TryDetermineOverridableMembers(SemanticModel semanticModel, SyntaxToken startToken, Accessibility seenAccessibility, out ISet<ISymbol> overridableMembers, CancellationToken cancellationToken) + { + var result = new HashSet<ISymbol>(); + var containingType = semanticModel.GetEnclosingSymbol<INamedTypeSymbol>(startToken.SpanStart, cancellationToken); + if (containingType != null && !containingType.IsScriptClass && !containingType.IsImplicitClass) + { + if (containingType.TypeKind == TypeKind.Class || containingType.TypeKind == TypeKind.Struct) + { + var baseTypes = containingType.GetBaseTypes().Reverse(); + foreach (var type in baseTypes) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Prefer overrides in derived classes + RemoveOverriddenMembers(result, type, cancellationToken); + + // Retain overridable methods + AddOverridableMembers(result, containingType, type, cancellationToken); + } + // Don't suggest already overridden members + RemoveOverriddenMembers(result, containingType, cancellationToken); + } + } + + // Filter based on accessibility + if (seenAccessibility != Accessibility.NotApplicable) + { + result.RemoveWhere(m => m.DeclaredAccessibility != seenAccessibility); + } + + overridableMembers = result; + return overridableMembers.Count > 0; + } + + static void AddOverridableMembers(HashSet<ISymbol> result, INamedTypeSymbol containingType, INamedTypeSymbol type, CancellationToken cancellationToken) + { + foreach (var member in type.GetMembers()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (IsOverridable(member, containingType)) + { + result.Add(member); + } + } + } + + protected static void RemoveOverriddenMembers(HashSet<ISymbol> result, INamedTypeSymbol containingType, CancellationToken cancellationToken) + { + foreach (var member in containingType.GetMembers()) + { + cancellationToken.ThrowIfCancellationRequested(); + var overriddenMember = member.OverriddenMember(); + if (overriddenMember != null) + { + result.Remove(overriddenMember); + } + } + } + + + static bool TryCheckForTrailingTokens (SyntaxTree tree, SourceText text, int startLineNumber, int position, CancellationToken cancellationToken) + { + var root = tree.GetRoot (cancellationToken); + var token = root.FindToken (position); + + // Don't want to offer Override completion if there's a token after the current + // position. + if (token.SpanStart > position) { + return false; + } + + // If the next token is also on our line then we don't want to offer completion. + if (IsOnStartLine (text, startLineNumber, token.GetNextToken ().SpanStart)) { + return false; + } + + return true; + } + + static bool IsOnStartLine (SourceText text, int startLineNumber, int position) + { + return text.Lines.IndexOf (position) == startLineNumber; + } + + static bool TryDetermineModifiers(ref SyntaxToken startToken, SourceText text, int startLine, out Accessibility seenAccessibility/*, out DeclarationModifiers modifiers*/) + { + var token = startToken; + //modifiers = new DeclarationModifiers(); + seenAccessibility = Accessibility.NotApplicable; + var overrideToken = default(SyntaxToken); + bool isUnsafe = false; + bool isSealed = false; + bool isAbstract = false; + + while (IsOnStartLine(token.SpanStart, text, startLine) && !token.IsKind(SyntaxKind.None)) + { + switch (token.Kind()) + { + case SyntaxKind.UnsafeKeyword: + isUnsafe = true; + break; + case SyntaxKind.OverrideKeyword: + overrideToken = token; + break; + case SyntaxKind.SealedKeyword: + isSealed = true; + break; + case SyntaxKind.AbstractKeyword: + isAbstract = true; + break; + case SyntaxKind.ExternKeyword: + break; + + // Filter on the most recently typed accessibility; keep the first one we see + case SyntaxKind.PublicKeyword: + if (seenAccessibility == Accessibility.NotApplicable) + { + seenAccessibility = Accessibility.Public; + } + + break; + case SyntaxKind.InternalKeyword: + if (seenAccessibility == Accessibility.NotApplicable) + { + seenAccessibility = Accessibility.Internal; + } + + // If we see internal AND protected, filter for protected internal + if (seenAccessibility == Accessibility.Protected) + { + seenAccessibility = Accessibility.ProtectedOrInternal; + } + + break; + case SyntaxKind.ProtectedKeyword: + if (seenAccessibility == Accessibility.NotApplicable) + { + seenAccessibility = Accessibility.Protected; + } + + // If we see protected AND internal, filter for protected internal + if (seenAccessibility == Accessibility.Internal) + { + seenAccessibility = Accessibility.ProtectedOrInternal; + } + + break; + default: + // Anything else and we bail. + return false; + } + + var previousToken = token.GetPreviousToken(); + + // We want only want to consume modifiers + if (previousToken.IsKind(SyntaxKind.None) || !IsOnStartLine(previousToken.SpanStart, text, startLine)) + { + break; + } + + token = previousToken; + } + + startToken = token; + /* modifiers = new DeclarationModifiers () + .WithIsUnsafe (isUnsafe) + .WithIsAbstract (isAbstract) + .WithIsOverride (true) + .WithIsSealed (isSealed);*/ + return overrideToken.IsKind(SyntaxKind.OverrideKeyword) && IsOnStartLine(overrideToken.Parent.SpanStart, text, startLine); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PartialContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PartialContextHandler.cs new file mode 100644 index 0000000000..b9bf4bef0d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PartialContextHandler.cs @@ -0,0 +1,178 @@ +// +// PartialContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using System; + + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class PartialContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter (SourceText text, int position) + { + return IsTriggerAfterSpaceOrStartOfWordCharacter (text, position); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + var tree = await document.GetSyntaxTreeAsync (cancellationToken).ConfigureAwait (false); + + //DeclarationModifiers modifiers; + SyntaxToken token; + + var semanticModel = await document.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + var enclosingSymbol = semanticModel.GetEnclosingSymbol (position, cancellationToken) as INamedTypeSymbol; + + // Only inside classes and structs + if (enclosingSymbol == null || !(enclosingSymbol.TypeKind == TypeKind.Struct || enclosingSymbol.TypeKind == TypeKind.Class)) { + return Enumerable.Empty<CompletionData> (); + } + + if (!IsPartialCompletionContext (tree, position, cancellationToken/*, out modifiers*/, out token)) { + if (enclosingSymbol != null && (token.IsKind (SyntaxKind.OpenBraceToken) || token.IsKind (SyntaxKind.CloseBraceToken) || token.IsKind (SyntaxKind.SemicolonToken))) { + return CreateCompletionData (engine, semanticModel, position, enclosingSymbol, token, false, cancellationToken); + } + return Enumerable.Empty<CompletionData> (); + } + + return CreateCompletionData (engine, semanticModel, position, enclosingSymbol, token, true, cancellationToken); + } + + protected virtual IEnumerable<CompletionData> CreateCompletionData (CompletionEngine engine, SemanticModel semanticModel, int position, INamedTypeSymbol enclosingType, SyntaxToken token, bool afterPartialKeyword, CancellationToken cancellationToken) + { + var symbols = semanticModel.LookupSymbols(position, container: enclosingType) + .OfType<IMethodSymbol>() + .Where(m => IsPartial(m) && m.PartialImplementationPart == null); + + var list = new List<CompletionData> (); + + var declarationBegin = afterPartialKeyword ? token.Parent.SpanStart : position - 1; + foreach (var m in symbols) { + var data = engine.Factory.CreatePartialCompletionData ( + this, + declarationBegin, + enclosingType, + m, + afterPartialKeyword + ); + list.Add (data); + } + return list; + } + + static bool IsPartial(IMethodSymbol m) + { + if (m.DeclaredAccessibility != Accessibility.NotApplicable && + m.DeclaredAccessibility != Accessibility.Private) + { + return false; + } + + if (!m.ReturnsVoid) + { + return false; + } + + if (m.IsVirtual) + { + return false; + } + + var declarations = m.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).OfType<MethodDeclarationSyntax>(); + return declarations.Any(d => d.Body == null && d.Modifiers.Any(SyntaxKind.PartialKeyword)); + } + + static bool IsPartialCompletionContext(SyntaxTree tree, int position, CancellationToken cancellationToken, /*out DeclarationModifiers modifiers, */out SyntaxToken token) + { + var touchingToken = tree.FindTokenOnLeftOfPosition(position, cancellationToken); + var targetToken = touchingToken.GetPreviousTokenIfTouchingWord(position); + var text = tree.GetText(cancellationToken); + + token = targetToken; + + //modifiers = default(DeclarationModifiers); + + if (targetToken.IsKind(SyntaxKind.VoidKeyword, SyntaxKind.PartialKeyword) || + (targetToken.Kind() == SyntaxKind.IdentifierToken && targetToken.HasMatchingText(SyntaxKind.PartialKeyword))) + { + return !IsOnSameLine (touchingToken.GetNextToken (), touchingToken, text) && + VerifyModifiers (tree, position, cancellationToken/*, out modifiers*/); + } + + return false; + } + + static bool VerifyModifiers(SyntaxTree tree, int position, CancellationToken cancellationToken/*, out DeclarationModifiers modifiers*/) + { + var touchingToken = tree.FindTokenOnLeftOfPosition(position, cancellationToken); + var token = touchingToken.GetPreviousToken(); + + bool foundPartial = touchingToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword); + bool foundAsync = false; + + while (IsOnSameLine(token, touchingToken, tree.GetText(cancellationToken))) + { + if (token.IsKind(SyntaxKind.ExternKeyword, SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword)) + { + //modifiers = default(DeclarationModifiers); + return false; + } + + if (token.IsKindOrHasMatchingText(SyntaxKind.AsyncKeyword)) + { + foundAsync = true; + } + + foundPartial = foundPartial || token.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword); + + token = token.GetPreviousToken(); + } + + /*modifiers = new DeclarationModifiers() + .WithPartial(true) + .WithAsync (foundAsync);*/ + return foundPartial; + } + + static bool IsOnSameLine(SyntaxToken syntaxToken, SyntaxToken touchingToken, SourceText text) + { + return !syntaxToken.IsKind(SyntaxKind.None) + && !touchingToken.IsKind(SyntaxKind.None) + && text.Lines.IndexOf(syntaxToken.SpanStart) == text.Lines.IndexOf(touchingToken.SpanStart); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PreProcessorExpressionContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PreProcessorExpressionContextHandler.cs new file mode 100644 index 0000000000..281f3994e2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/PreProcessorExpressionContextHandler.cs @@ -0,0 +1,59 @@ +// +// PreProcessorExpressionContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class PreProcessorExpressionContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter (Microsoft.CodeAnalysis.Text.SourceText text, int position) + { + return IsTriggerAfterSpaceOrStartOfWordCharacter (text, position); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var model = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + + var result = new List<CompletionData> (); + if (ctx.IsPreProcessorExpressionContext) { + var parseOptions = model.SyntaxTree.Options as CSharpParseOptions; + foreach (var define in parseOptions.PreprocessorSymbolNames) { + result.Add(engine.Factory.CreateGenericData (this, define, GenericDataType.PreprocessorSymbol)); + } + } + return result; + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/RoslynRecommendationsCompletionContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/RoslynRecommendationsCompletionContextHandler.cs new file mode 100644 index 0000000000..7ba672d567 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/RoslynRecommendationsCompletionContextHandler.cs @@ -0,0 +1,178 @@ +// +// RoslynRecommendationsCompletionContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.Recommendations; +using Microsoft.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + + // public class CompletionEngineCache + // { + // public List<INamespace> namespaces; + // public ICompletionData[] importCompletion; + // } + + class RoslynRecommendationsCompletionContextHandler : CompletionContextHandler + { + public override bool IsTriggerCharacter (SourceText text, int position) + { + var ch = text [position]; + return ch == '.' || + ch == ' ' && position >= 1 && !char.IsWhiteSpace (text [position - 1]) || + ch == '#' || // pre processor directives + ch == '>' && position >= 1 && text [position - 1] == '-' || // pointer member access + ch == ':' && position >= 1 && text [position - 1] == ':' || // alias name + IsStartingNewWord (text, position); + } + + bool IsException (ITypeSymbol type) + { + if (type == null) + return false; + if (type.Name == "Exception" && type.ContainingNamespace.Name == "System") + return true; + return IsException (type.BaseType); + } + + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var semanticModel = await completionContext.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + var result = new List<CompletionData> (); + if (info.TriggerCharacter == ' ') { + var newExpression = ObjectCreationContextHandler.GetObjectCreationNewExpression (ctx.SyntaxTree, completionContext.Position, cancellationToken); + if (newExpression == null && info.CompletionTriggerReason == CompletionTriggerReason.CharTyped && !ctx.LeftToken.IsKind (SyntaxKind.EqualsToken) && !ctx.LeftToken.IsKind (SyntaxKind.EqualsEqualsToken)) + return Enumerable.Empty<CompletionData> (); + + completionResult.AutoCompleteEmptyMatch = false; + } + + var parent = ctx.TargetToken.Parent; + bool isInAttribute = ctx.CSharpSyntaxContext.IsAttributeNameContext; + bool isInBaseList = parent != null && parent.IsKind (SyntaxKind.BaseList); + bool isInUsingDirective = parent != null && parent.Parent != null && parent.Parent.IsKind (SyntaxKind.UsingDirective) && !parent.IsKind (SyntaxKind.QualifiedName); + var isInQuery = ctx.CSharpSyntaxContext.IsInQuery; + var completionDataLookup = new Dictionary<Tuple<string, SymbolKind>, ISymbolCompletionData> (); + bool isInCatchTypeExpression = parent.IsKind (SyntaxKind.CatchDeclaration) || + parent.IsKind (SyntaxKind.QualifiedName) && parent != null && parent.Parent.IsKind (SyntaxKind.CatchDeclaration); + Action<ISymbolCompletionData> addData = d => { + var key = Tuple.Create (d.DisplayText, d.Symbol.Kind); + ISymbolCompletionData data; + if (completionDataLookup.TryGetValue (key, out data)) { + data.AddOverload (d); + return; + } + completionDataLookup.Add (key, d); + result.Add (d); + }; + + var completionCategoryLookup = new Dictionary<string, ICompletionCategory> (); + foreach (var symbol in Recommender.GetRecommendedSymbolsAtPosition (semanticModel, completionContext.Position, engine.Workspace, null, cancellationToken)) { + if (symbol.Kind == SymbolKind.NamedType) { + if (isInAttribute) { + var type = (ITypeSymbol)symbol; + if (type.IsAttribute ()) { + var v = type.Name.Substring (0, type.Name.Length - "Attribute".Length); + var needsEscaping = SyntaxFacts.GetKeywordKind(v) != SyntaxKind.None; + needsEscaping = needsEscaping || (isInQuery && SyntaxFacts.IsQueryContextualKeyword(SyntaxFacts.GetContextualKeywordKind(v))); + if (!needsEscaping) { + addData (engine.Factory.CreateSymbolCompletionData (this, symbol, v)); + continue; + } + } + } + if (isInBaseList) { + var type = (ITypeSymbol)symbol; + if (type.IsSealed || type.IsStatic) + continue; + } + if (isInCatchTypeExpression) { + var type = (ITypeSymbol)symbol; + if (!IsException (type)) + continue; + } + } + + if (isInUsingDirective && symbol.Kind != SymbolKind.Namespace) + continue; + + var newData = engine.Factory.CreateSymbolCompletionData (this, symbol, symbol.Name.EscapeIdentifier (isInQuery)); + var categorySymbol = (ISymbol)symbol.ContainingType ?? symbol.ContainingNamespace; + if (categorySymbol != null) { + ICompletionCategory category; + var key = categorySymbol.ToDisplayString (); + if (!completionCategoryLookup.TryGetValue (key, out category)) { + completionCategoryLookup [key] = category = engine.Factory.CreateCompletionDataCategory (categorySymbol); + } + newData.CompletionCategory = category; + } + addData (newData); + } + return result; + } + + protected override async Task<bool> IsSemanticTriggerCharacterAsync(Document document, int characterPosition, CancellationToken cancellationToken) + { + bool? result = await IsTriggerOnDotAsync(document, characterPosition, cancellationToken).ConfigureAwait(false); + if (result.HasValue) + { + return result.Value; + } + + return true; + } + + private async Task<bool?> IsTriggerOnDotAsync(Document document, int characterPosition, CancellationToken cancellationToken) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (text[characterPosition] != '.') + { + return null; + } + + // don't want to trigger after a number. All other cases after dot are ok. + var tree = await document.GetCSharpSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var token = tree.FindToken(characterPosition); + if (token.Kind() == SyntaxKind.DotToken) + { + token = token.GetPreviousToken(); + } + + return token.Kind() != SyntaxKind.NumericLiteralToken; + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SenderCompletionContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SenderCompletionContextHandler.cs new file mode 100644 index 0000000000..045672e662 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SenderCompletionContextHandler.cs @@ -0,0 +1,122 @@ +// +// SenderCompletionContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class SenderCompletionContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var position = completionContext.Position; + var document = completionContext.Document; + var ctx = await completionContext.GetSyntaxContextAsync (engine.Workspace, cancellationToken).ConfigureAwait (false); + var syntaxTree = ctx.SyntaxTree; + if (syntaxTree.IsInNonUserCode(position, cancellationToken) || + syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + if (!syntaxTree.IsRightOfDotOrArrowOrColonColon(position, cancellationToken)) + return Enumerable.Empty<CompletionData> (); + var ma = ctx.LeftToken.Parent as MemberAccessExpressionSyntax; + if (ma == null) + return Enumerable.Empty<CompletionData> (); + + var model = ctx.CSharpSyntaxContext.SemanticModel; + + var symbolInfo = model.GetSymbolInfo (ma.Expression); + if (symbolInfo.Symbol == null || symbolInfo.Symbol.Kind != SymbolKind.Parameter) + return Enumerable.Empty<CompletionData> (); + var list = new List<CompletionData> (); + var within = model.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); + var addedSymbols = new HashSet<string> (); + + foreach (var ano in ma.AncestorsAndSelf ().OfType<AnonymousMethodExpressionSyntax> ()) { + Analyze (engine, model, ma.Expression, within, list, ano.ParameterList, symbolInfo.Symbol, addedSymbols, cancellationToken); + } + + foreach (var ano in ma.AncestorsAndSelf ().OfType<ParenthesizedLambdaExpressionSyntax> ()) { + Analyze (engine, model, ma.Expression, within, list, ano.ParameterList, symbolInfo.Symbol, addedSymbols, cancellationToken); + } + + return list; + } + + void Analyze (CompletionEngine engine,SemanticModel model, SyntaxNode node, ISymbol within, List<CompletionData> list, ParameterListSyntax parameterList, ISymbol symbol, HashSet<string> addedSymbols, CancellationToken cancellationToken) + { + var type = CheckParameterList (model, parameterList, symbol, cancellationToken); + if (type == null) + return; + var startType = type; + + while (type.SpecialType != SpecialType.System_Object) { + foreach (var member in type.GetMembers ()) { + if (member.IsImplicitlyDeclared || member.IsStatic) + continue; + if (member.IsOrdinaryMethod () || member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property) { + if (member.IsAccessibleWithin (within)) { + var completionData = engine.Factory.CreateCastCompletionData(this, member, node, startType); + if (addedSymbols.Contains (completionData.DisplayText)) + continue; + addedSymbols.Add (completionData.DisplayText); + list.Add (completionData); + } + } + } + + type = type.BaseType; + } + } + + static ITypeSymbol CheckParameterList (SemanticModel model, ParameterListSyntax listSyntax, ISymbol parameter, CancellationToken cancellationToken) + { + var param = listSyntax.Parameters.FirstOrDefault (); + if (param == null) + return null; + var declared = model.GetDeclaredSymbol (param, cancellationToken); + if (declared != parameter) + return null; + var assignmentExpr = listSyntax.Parent.Parent as AssignmentExpressionSyntax; + if (assignmentExpr == null || !assignmentExpr.IsKind (SyntaxKind.AddAssignmentExpression)) + return null; + var left = assignmentExpr.Left as MemberAccessExpressionSyntax; + if (left == null) + return null; + var symbolInfo = model.GetSymbolInfo (left.Expression); + if (symbolInfo.Symbol == null || symbolInfo.Symbol is ITypeSymbol) + return null; + return model.GetTypeInfo (left.Expression).Type; + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SnippetContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SnippetContextHandler.cs new file mode 100644 index 0000000000..1dce7bbb1e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SnippetContextHandler.cs @@ -0,0 +1,104 @@ +// +// SnippetContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class SnippetContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + + var syntaxTree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + if (syntaxTree.IsInNonUserCode(position, cancellationToken) || + syntaxTree.IsRightOfDotOrArrowOrColonColon(position, cancellationToken) || + syntaxTree.GetContainingTypeOrEnumDeclaration(position, cancellationToken) is EnumDeclarationSyntax) + { + return Enumerable.Empty<CompletionData>(); + } + + // var span = new TextSpan(position, 0); +// var semanticModel = await document.GetCSharpSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false); + if (syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken)) + { + var directive = syntaxTree.GetRoot(cancellationToken).FindTokenOnLeftOfPosition(position, includeDirectives: true).GetAncestor<DirectiveTriviaSyntax>(); + if (directive.DirectiveNameToken.IsKind( + SyntaxKind.IfKeyword, + SyntaxKind.RegionKeyword, + SyntaxKind.ElseKeyword, + SyntaxKind.ElifKeyword, + SyntaxKind.ErrorKeyword, + SyntaxKind.LineKeyword, + SyntaxKind.PragmaKeyword, + SyntaxKind.EndIfKeyword, + SyntaxKind.UndefKeyword, + SyntaxKind.EndRegionKeyword, + SyntaxKind.WarningKeyword)) + { + return Enumerable.Empty<CompletionData>(); + } + + return await GetSnippetCompletionItemsAsync(cancellationToken).ConfigureAwait(false); + } + var tokenLeftOfPosition = syntaxTree.FindTokenOnLeftOfPosition (position, cancellationToken); + + if (syntaxTree.IsGlobalStatementContext(position, cancellationToken) || + syntaxTree.IsExpressionContext(position, tokenLeftOfPosition, true, cancellationToken) || + syntaxTree.IsStatementContext(position, tokenLeftOfPosition, cancellationToken) || + syntaxTree.IsTypeContext(position, cancellationToken) || + syntaxTree.IsTypeDeclarationContext(position, tokenLeftOfPosition, cancellationToken) || + syntaxTree.IsNamespaceContext(position, cancellationToken) || + syntaxTree.IsMemberDeclarationContext(position, tokenLeftOfPosition, cancellationToken) || + syntaxTree.IsLabelContext(position, cancellationToken)) + { + return await GetSnippetCompletionItemsAsync(cancellationToken).ConfigureAwait(false); + } + + return Enumerable.Empty<CompletionData>(); + } + + Task<IEnumerable<CompletionData>> GetSnippetCompletionItemsAsync(CancellationToken cancellationToken) + { + if (CompletionEngine.SnippetCallback == null) + return Task.FromResult (Enumerable.Empty<CompletionData>()); + return CompletionEngine.SnippetCallback (cancellationToken); + } + + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeNameContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeNameContextHandler.cs new file mode 100644 index 0000000000..43f9795739 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeNameContextHandler.cs @@ -0,0 +1,125 @@ +// +// SpeculativeNameContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using System.Linq; +using Microsoft.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class SpeculativeNameContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var tree = await completionContext.Document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree.IsInNonUserCode(completionContext.Position, cancellationToken) || + tree.IsPreProcessorDirectiveContext(completionContext.Position, cancellationToken) || + info.CompletionTriggerReason != CompletionTriggerReason.CompletionCommand) + return Enumerable.Empty<CompletionData>(); + + var token = tree.FindTokenOnLeftOfPosition(completionContext.Position, cancellationToken); + var semanticModel = await completionContext.Document.GetSemanticModelAsync (cancellationToken).ConfigureAwait(false); + var parent = token.Parent.AncestorsAndSelf ().OfType<GenericNameSyntax> ().FirstOrDefault () ?? token.Parent; + + if (!parent.Parent.IsKind (SyntaxKind.IncompleteMember) && + !IsLocal(parent) && + !parent.Parent.IsKind (SyntaxKind.Parameter) && + !parent.Parent.IsKind (SyntaxKind.ForEachStatement)) { + return Enumerable.Empty<CompletionData>(); + } + + if (info.TriggerCharacter != ' ' && + parent.Parent.IsKind (SyntaxKind.ExpressionStatement)) { + return Enumerable.Empty<CompletionData>(); + } + var list = new List<CompletionData> (); + + if (parent.IsKind(SyntaxKind.PredefinedType)) { + switch (token.Kind()) { + case SyntaxKind.ObjectKeyword: + list.Add (engine.Factory.CreateGenericData(this, "o", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "obj", GenericDataType.NameProposal)); + return list; + case SyntaxKind.BoolKeyword: + list.Add (engine.Factory.CreateGenericData(this, "b", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "pred", GenericDataType.NameProposal)); + return list; + case SyntaxKind.CharKeyword: + list.Add (engine.Factory.CreateGenericData(this, "c", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "ch", GenericDataType.NameProposal)); + return list; + case SyntaxKind.StringKeyword: + list.Add (engine.Factory.CreateGenericData(this, "str", GenericDataType.NameProposal)); + return list; + case SyntaxKind.DoubleKeyword: + case SyntaxKind.FloatKeyword: + case SyntaxKind.DecimalKeyword: + list.Add (engine.Factory.CreateGenericData(this, "d", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "f", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "m", GenericDataType.NameProposal)); + return list; + default: + list.Add (engine.Factory.CreateGenericData(this, "i", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "j", GenericDataType.NameProposal)); + list.Add (engine.Factory.CreateGenericData(this, "k", GenericDataType.NameProposal)); + return list; + } + } else { + var gns = parent as GenericNameSyntax; + var names = WordParser.BreakWords (gns != null ? gns.Identifier.ToString () : token.ToString ().Trim ()); + var possibleName = new StringBuilder (); + for (int i = 0; i < names.Count; i++) { + possibleName.Length = 0; + for (int j = i; j < names.Count; j++) { + if (string.IsNullOrEmpty (names [j])) { + continue; + } + if (j == i) { + names [j] = Char.ToLower (names [j] [0]) + names [j].Substring (1); + } + possibleName.Append (names [j]); + } + list.Add (engine.Factory.CreateGenericData (this, possibleName.ToString (), GenericDataType.NameProposal)); + } + } + return list; + } + + bool IsLocal (SyntaxNode tokenParent) + { + if ((tokenParent.IsKind (SyntaxKind.GenericName) || tokenParent.IsKind (SyntaxKind.IdentifierName) || tokenParent.IsKind (SyntaxKind.PredefinedType)) && + (tokenParent.Parent.IsKind (SyntaxKind.ExpressionStatement) || tokenParent.Parent.IsKind (SyntaxKind.VariableDeclaration))) + return true; + return false; + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeTContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeTContextHandler.cs new file mode 100644 index 0000000000..c4cce6d676 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/SpeculativeTContextHandler.cs @@ -0,0 +1,103 @@ +// +// SpeculativeTContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class SpeculativeTContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + var document = completionContext.Document; + var position = completionContext.Position; + + return await document.GetUnionResultsFromDocumentAndLinks( + UnionCompletionItemComparer.Instance, + async (doc, ct) => await GetSpeculativeTCompletions(engine, doc, position, ct).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } + + private async Task<IEnumerable<CompletionData>> GetSpeculativeTCompletions(CompletionEngine engine, Document document, int position, CancellationToken cancellationToken) + { + var syntaxTree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxTree.IsInNonUserCode(position, cancellationToken) || + syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken)) + { + return Enumerable.Empty<CompletionData>(); + } + + // If we're in a generic type argument context, use the start of the generic type name + // as the position for the rest of the context checks. + int testPosition = position; + var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken); + + var semanticModel = await document.GetCSharpSemanticModelForNodeAsync(leftToken.Parent, cancellationToken).ConfigureAwait(false); + if (syntaxTree.IsGenericTypeArgumentContext(position, leftToken, cancellationToken, semanticModel)) + { + // Walk out until we find the start of the partial written generic + SyntaxToken nameToken; + while (syntaxTree.IsInPartiallyWrittenGeneric(testPosition, cancellationToken, out nameToken)) + { + testPosition = nameToken.SpanStart; + } + + // If the user types Foo<T, automatic brace completion will insert the close brace + // and the generic won't be "partially written". + if (testPosition == position) + { + var typeArgumentList = leftToken.GetAncestor<TypeArgumentListSyntax>(); + if (typeArgumentList != null) + { + if (typeArgumentList.LessThanToken != default(SyntaxToken) && typeArgumentList.GreaterThanToken != default(SyntaxToken)) + { + testPosition = typeArgumentList.LessThanToken.SpanStart; + } + } + } + } + + if ((!leftToken.GetPreviousTokenIfTouchingWord(position).IsKindOrHasMatchingText(SyntaxKind.AsyncKeyword) && + syntaxTree.IsMemberDeclarationContext(testPosition, contextOpt: null, validModifiers: SyntaxKindSet.AllMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) || + syntaxTree.IsGlobalMemberDeclarationContext(testPosition, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + syntaxTree.IsGlobalStatementContext(testPosition, cancellationToken) || + syntaxTree.IsDelegateReturnTypeContext(testPosition, syntaxTree.FindTokenOnLeftOfPosition(testPosition, cancellationToken), cancellationToken)) + { + const string T = "T"; + return new [] { engine.Factory.CreateGenericData (this, T, GenericDataType.Undefined) }; + } + return Enumerable.Empty<CompletionData>(); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/XmlDocCommentContextHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/XmlDocCommentContextHandler.cs new file mode 100644 index 0000000000..ce1b89b827 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ContextHandler/XmlDocCommentContextHandler.cs @@ -0,0 +1,399 @@ +// +// XmlDocCommentContextHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.Text; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + class XmlDocCommentContextHandler : CompletionContextHandler + { + protected async override Task<IEnumerable<CompletionData>> GetItemsWorkerAsync (CompletionResult completionResult, CompletionEngine engine, CompletionContext completionContext, CompletionTriggerInfo info, CancellationToken cancellationToken) + { + if (info.IsDebugger) + { + return null; + } + var document = completionContext.Document; + var position = completionContext.Position; + + var tree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); + var parentTrivia = token.GetAncestor<DocumentationCommentTriviaSyntax>(); + + if (parentTrivia == null) + { + return null; + } + + var items = new List<CompletionData>(); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var span = GetTextChangeSpan(text, position); + + var attachedToken = parentTrivia.ParentTrivia.Token; + if (attachedToken.Kind() == SyntaxKind.None) + { + return null; + } + + var semanticModel = await document.GetCSharpSemanticModelForNodeAsync(attachedToken.Parent, cancellationToken).ConfigureAwait(false); + + ISymbol declaredSymbol = null; + var memberDeclaration = attachedToken.GetAncestor<MemberDeclarationSyntax>(); + if (memberDeclaration != null) + { + declaredSymbol = semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken); + } + else + { + var typeDeclaration = attachedToken.GetAncestor<TypeDeclarationSyntax>(); + if (typeDeclaration != null) + { + declaredSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken); + } + } + + if (declaredSymbol != null) + { + items.AddRange(GetTagsForSymbol(engine, declaredSymbol, span, parentTrivia, token)); + } + + if (token.Parent.Kind() == SyntaxKind.XmlEmptyElement || token.Parent.Kind() == SyntaxKind.XmlText || + (token.Parent.IsKind(SyntaxKind.XmlElementEndTag) && token.IsKind(SyntaxKind.GreaterThanToken)) || + (token.Parent.IsKind(SyntaxKind.XmlName) && token.Parent.IsParentKind(SyntaxKind.XmlEmptyElement))) + { + if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement) + { + items.AddRange(GetNestedTags(engine, span)); + } + + if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement && ((XmlElementSyntax)token.Parent.Parent).StartTag.Name.LocalName.ValueText == "list") + { + items.AddRange(GetListItems(engine, span)); + } + + if (token.Parent.IsParentKind(SyntaxKind.XmlEmptyElement) & token.Parent.Parent.IsParentKind(SyntaxKind.XmlElement)) + { + var element = (XmlElementSyntax)token.Parent.Parent.Parent; + if (element.StartTag.Name.LocalName.ValueText == "list") + { + items.AddRange(GetListItems(engine, span)); + } + } + + if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement && ((XmlElementSyntax)token.Parent.Parent).StartTag.Name.LocalName.ValueText == "listheader") + { + items.AddRange(GetListHeaderItems(engine, span)); + } + + if (token.Parent.Parent is DocumentationCommentTriviaSyntax) + { + items.AddRange(GetTopLevelSingleUseNames(engine, parentTrivia, span)); + items.AddRange(GetTopLevelRepeatableItems(engine, span)); + } + } + + if (token.Parent.Kind() == SyntaxKind.XmlElementStartTag) + { + var startTag = (XmlElementStartTagSyntax)token.Parent; + + if (token == startTag.GreaterThanToken && startTag.Name.LocalName.ValueText == "list") + { + items.AddRange(GetListItems(engine, span)); + } + + if (token == startTag.GreaterThanToken && startTag.Name.LocalName.ValueText == "listheader") + { + items.AddRange(GetListHeaderItems(engine, span)); + } + } + + items.AddRange(GetAlwaysVisibleItems(engine, span)); + return items; + } + + public override bool IsCommitCharacter (CompletionData ICompletionData, char ch, string textTypedSoFar) + { + if ((ch == '"' || ch == ' ') + && ICompletionData.DisplayText.Contains(ch)) + { + return false; + } + + return base.IsCommitCharacter(ICompletionData, ch, textTypedSoFar) || ch == '>' || ch == '\t'; + } + + public override bool IsTriggerCharacter (SourceText text, int position) + { + return text[position] == '<'; + } +// +// public override bool SendEnterThroughToEditor(ICompletionData ICompletionData, string textTypedSoFar) +// { +// return false; +// } + + private IEnumerable<CompletionData> GetTopLevelSingleUseNames(CompletionEngine engine, DocumentationCommentTriviaSyntax parentTrivia, TextSpan span) + { + var names = new HashSet<string>(new[] { "summary", "remarks", "example", "completionlist" }); + + RemoveExistingTags(parentTrivia, names, (x) => x.StartTag.Name.LocalName.ValueText); + + return names.Select(n => GetItem(engine, n, span)); + } + + private void RemoveExistingTags(DocumentationCommentTriviaSyntax parentTrivia, ISet<string> names, Func<XmlElementSyntax, string> selector) + { + if (parentTrivia != null) + { + foreach (var node in parentTrivia.Content) + { + var element = node as XmlElementSyntax; + if (element != null) + { + names.Remove(selector(element)); + } + } + } + } + + private IEnumerable<CompletionData> GetTagsForSymbol(CompletionEngine engine, ISymbol symbol, TextSpan filterSpan, DocumentationCommentTriviaSyntax trivia, SyntaxToken token) + { + if (symbol is IMethodSymbol) + { + return GetTagsForMethod(engine, (IMethodSymbol)symbol, filterSpan, trivia, token); + } + + if (symbol is IPropertySymbol) + { + return GetTagsForProperty(engine, (IPropertySymbol)symbol, filterSpan, trivia); + } + + if (symbol is INamedTypeSymbol) + { + return GetTagsForType(engine, (INamedTypeSymbol)symbol, filterSpan, trivia); + } + + return SpecializedCollections.EmptyEnumerable<CompletionData>(); + } + + private IEnumerable<CompletionData> GetTagsForType(CompletionEngine engine, INamedTypeSymbol symbol, TextSpan filterSpan, DocumentationCommentTriviaSyntax trivia) + { + var items = new List<CompletionData>(); + + var typeParameters = symbol.TypeParameters.Select(p => p.Name).ToSet(); + + RemoveExistingTags(trivia, typeParameters, x => AttributeSelector(x, "typeparam")); + + items.AddRange(typeParameters.Select(t => engine.Factory.CreateXmlDocCompletionData ( + this, + FormatParameter("typeparam", t)))); + return items; + } + + private string AttributeSelector(XmlElementSyntax element, string attribute) + { + if (!element.StartTag.IsMissing && !element.EndTag.IsMissing) + { + var startTag = element.StartTag; + var nameAttribute = startTag.Attributes.OfType<XmlNameAttributeSyntax>().FirstOrDefault(a => a.Name.LocalName.ValueText == "name"); + if (nameAttribute != null) + { + if (startTag.Name.LocalName.ValueText == attribute) + { + return nameAttribute.Identifier.Identifier.ValueText; + } + } + } + + return null; + } + + private IEnumerable<CompletionData> GetTagsForProperty(CompletionEngine engine, IPropertySymbol symbol, TextSpan filterSpan, DocumentationCommentTriviaSyntax trivia) + { + var items = new List<CompletionData>(); + + var typeParameters = symbol.GetTypeArguments().Select(p => p.Name).ToSet(); + + RemoveExistingTags(trivia, typeParameters, x => AttributeSelector(x, "typeparam")); + + items.AddRange(typeParameters.Select(t => engine.Factory.CreateXmlDocCompletionData(this, "typeparam", null, "name$" + t))); + items.Add(engine.Factory.CreateXmlDocCompletionData(this, "value")); + return items; + } + + private IEnumerable<CompletionData> GetTagsForMethod(CompletionEngine engine, IMethodSymbol symbol, TextSpan filterSpan, DocumentationCommentTriviaSyntax trivia, SyntaxToken token) + { + var items = new List<CompletionData>(); + + var parameters = symbol.GetParameters().Select(p => p.Name).ToSet(); + var typeParameters = symbol.TypeParameters.Select(t => t.Name).ToSet(); + + // User is trying to write a name, try to suggest only names. + if (token.Parent.IsKind(SyntaxKind.XmlNameAttribute) || + (token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.IsParentKind(SyntaxKind.XmlNameAttribute))) + { + string parentElementName = null; + + var emptyElement = token.GetAncestor<XmlEmptyElementSyntax>(); + if (emptyElement != null) + { + parentElementName = emptyElement.Name.LocalName.Text; + } + + // We're writing the name of a paramref or typeparamref + if (parentElementName == "paramref") + { + items.AddRange(parameters.Select(p => engine.Factory.CreateXmlDocCompletionData (this, p))); + } + else if (parentElementName == "typeparamref") + { + items.AddRange(typeParameters.Select(t => engine.Factory.CreateXmlDocCompletionData (this, t))); + } + + return items; + } + + var returns = true; + + RemoveExistingTags(trivia, parameters, x => AttributeSelector(x, "param")); + RemoveExistingTags(trivia, typeParameters, x => AttributeSelector(x, "typeparam")); + + foreach (var node in trivia.Content) + { + var element = node as XmlElementSyntax; + if (element != null && !element.StartTag.IsMissing && !element.EndTag.IsMissing) + { + var startTag = element.StartTag; + + if (startTag.Name.LocalName.ValueText == "returns") + { + returns = false; + break; + } + } + } + + items.AddRange(parameters.Select(p => engine.Factory.CreateXmlDocCompletionData (this, FormatParameter("param", p)))); + items.AddRange(typeParameters.Select(t => engine.Factory.CreateXmlDocCompletionData (this, FormatParameter("typeparam", t)))); + + if (returns && !symbol.ReturnsVoid) + { + items.Add(engine.Factory.CreateXmlDocCompletionData (this, "returns")); + } + + return items; + } + + + readonly Dictionary<string, string[]> _tagMap = new Dictionary<string, string[]> { + { "exception", new[] { "<exception cref=\"", "\"" } }, + { "!--", new[] { "<!--", "-->" } }, + { "![CDATA[", new[] { "<![CDATA[", "]]>" } }, + { "include", new[] { "<include file=\'", "\' path=\'[@name=\"\"]\'/>" } }, + { "permission", new[] { "<permission cref=\"", "\"" } }, + { "see", new[] { "<see cref=\"", "\"/>" } }, + { "seealso", new[] { "<seealso cref=\"", "\"/>" } }, + { "list", new[] { "<list type=\"", "\"" } }, + { "paramref", new[] { "<paramref name=\"", "\"/>" } }, + { "typeparamref", new[] { "<typeparamref name=\"", "\"/>" } }, + { "completionlist", new[] { "<completionlist cref=\"", "\"/>" } }, + }; + + readonly string[][] _attributeMap = { + new [] { "exception", "cref", "cref=\"", "\"" }, + new [] { "permission", "cref", "cref=\"", "\"" }, + new [] { "see", "cref", "cref=\"", "\"" }, + new [] { "seealso", "cref", "cref=\"", "\"" }, + new [] { "list", "type", "type=\"", "\"" }, + new [] { "param", "name", "name=\"", "\"" }, + new [] { "include", "file", "file=\"", "\"" }, + new [] { "include", "path", "path=\"", "\"" } + }; + + protected CompletionData GetItem(CompletionEngine engine, string n, TextSpan span) + { + if (_tagMap.ContainsKey(n)) + { + var value = _tagMap[n]; + return engine.Factory.CreateXmlDocCompletionData (this, n, null, value [0] + "$" + value [1]); + } + return engine.Factory.CreateXmlDocCompletionData (this, n); + } + + protected IEnumerable<CompletionData> GetAttributeItem(CompletionEngine engine, string n, TextSpan span) + { + var items = _attributeMap.Where(x => x[0] == n).Select(x => engine.Factory.CreateXmlDocCompletionData(this, x[1],null, x[2] + "$" + x[3])); + if (items.Any ()) + return items; + + return new [] { engine.Factory.CreateXmlDocCompletionData (this, n) }; + } + + protected IEnumerable<CompletionData> GetAlwaysVisibleItems(CompletionEngine engine, TextSpan filterSpan) + { + return new[] { "see", "seealso", "![CDATA[", "!--" } + .Select(t => GetItem(engine, t, filterSpan)); + } + + protected IEnumerable<CompletionData> GetNestedTags(CompletionEngine engine, TextSpan filterSpan) + { + return new[] { "c", "code", "para", "list", "paramref", "typeparamref" } + .Select(t => GetItem(engine, t, filterSpan)); + } + + protected IEnumerable<CompletionData> GetTopLevelRepeatableItems(CompletionEngine engine, TextSpan filterSpan) + { + return new[] { "exception", "include", "permission" } + .Select(t => GetItem(engine, t, filterSpan)); + } + + protected IEnumerable<CompletionData> GetListItems(CompletionEngine engine, TextSpan span) + { + return new[] { "listheader", "term", "item", "description" } + .Select(t => GetItem(engine, t, span)); + } + + protected IEnumerable<CompletionData> GetListHeaderItems(CompletionEngine engine, TextSpan span) + { + return new[] { "term", "description" } + .Select(t => GetItem(engine, t, span)); + } + + protected string FormatParameter(string kind, string name) + { + return string.Format("{0} name=\"{1}\"", kind, name); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/DisplayFlags.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/DisplayFlags.cs new file mode 100644 index 0000000000..f910d187f7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/DisplayFlags.cs @@ -0,0 +1,42 @@ +// +// DisplayFlags.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + [Flags] + public enum DisplayFlags + { + None = 0, + Hidden = 1, + Obsolete = 2, + DescriptionHasMarkup = 4, + NamedArgument = 8, + IsImportCompletion = 16, + MarkedBold = 32 + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/EditorBrowsableBehavior.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/EditorBrowsableBehavior.cs new file mode 100644 index 0000000000..07e261a383 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/EditorBrowsableBehavior.cs @@ -0,0 +1,35 @@ +// +// EditorBrowsableBehavior.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public enum EditorBrowsableBehavior + { + Ignore, + Normal, + IncludeAdvanced + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionCategory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionCategory.cs new file mode 100644 index 0000000000..6b613941ea --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionCategory.cs @@ -0,0 +1,37 @@ +// +// CompletionCategory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public interface ICompletionCategory + { + string DisplayText { get; set; } + + string Icon { get; set; } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionDataFactory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionDataFactory.cs new file mode 100644 index 0000000000..3f3585b49f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionDataFactory.cs @@ -0,0 +1,77 @@ +// +// ICompletionDataFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public enum GenericDataType + { + AttributeTarget, + Undefined, + Keyword, + PreprocessorKeyword, + PreprocessorSymbol, + NameProposal, + NamedParameter + } + + public interface ICompletionDataFactory + { + CompletionData CreateGenericData (ICompletionKeyHandler keyHandler, string data, GenericDataType genericDataType = GenericDataType.Undefined); + + CompletionData CreateFormatItemCompletionData (ICompletionKeyHandler keyHandler, string format, string description, object example); + + CompletionData CreateXmlDocCompletionData (ICompletionKeyHandler keyHandler, string tag, string description = null, string tagInsertionText = null); + + ISymbolCompletionData CreateSymbolCompletionData (ICompletionKeyHandler keyHandler, ISymbol symbol); + ISymbolCompletionData CreateSymbolCompletionData (ICompletionKeyHandler keyHandler, ISymbol symbol, string text); + + /// <summary> + /// Creates enum member completion data. + /// Form: Type.Member + /// Used for generating enum members Foo.A, Foo.B where the enum 'Foo' is valid. + /// </summary> + ISymbolCompletionData CreateEnumMemberCompletionData (ICompletionKeyHandler keyHandler, ISymbol typeAlias, IFieldSymbol field); + + CompletionData CreateNewOverrideCompletionData (ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, ISymbol m, bool afterKeyword); + + CompletionData CreatePartialCompletionData (ICompletionKeyHandler keyHandler, int declarationBegin, ITypeSymbol currentType, IMethodSymbol method, bool afterKeyword); + + /// <summary> + /// Creates the event creation completion data. + /// </summary> + CompletionData CreateNewMethodDelegate (ICompletionKeyHandler keyHandler, ITypeSymbol delegateType, string varName, INamedTypeSymbol curType); + + CompletionData CreateAnonymousMethod (ICompletionKeyHandler keyHandler, string displayText, string description, string textBeforeCaret, string textAfterCaret); + + CompletionData CreateObjectCreation (ICompletionKeyHandler keyHandler, ITypeSymbol typeToCreate, ISymbol symbol, int declarationBegin, bool afterKeyword); + + CompletionData CreateCastCompletionData (ICompletionKeyHandler keyHandler, ISymbol member, SyntaxNode nodeToCast, ITypeSymbol targetType); + + ICompletionCategory CreateCompletionDataCategory (ISymbol forSymbol); + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionKeyHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionKeyHandler.cs new file mode 100644 index 0000000000..953516095b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/ICompletionKeyHandler.cs @@ -0,0 +1,56 @@ +// +// ICompletionKeyHandler.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis.Text; +using MonoDevelop.Ide.CodeCompletion; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public interface ICompletionKeyHandler + { + /// <summary> + /// Returns true if the character typed should be used to filter the specified completion + /// item. A character will be checked to see if it should filter an item. If not, it will be + /// checked to see if it should commit that item. If it does neither, then completion will + /// be dismissed. + /// </summary> + bool IsFilterCharacter(CompletionData completionItem, char ch, string textTypedSoFar); + + /// <summary> + /// Returns true if the character is one that can commit the specified completion item. A + /// character will be checked to see if it should filter an item. If not, it will be checked + /// to see if it should commit that item. If it does neither, then completion will be + /// dismissed. + /// </summary> + bool IsCommitCharacter(CompletionData completionItem, char ch, string textTypedSoFar); + + /// <summary> + /// Returns true if the enter key that was typed should also be sent through to the editor + /// after committing the provided completion item. + /// </summary> + bool SendEnterThroughToEditor(CompletionData completionItem, string textTypedSoFar); + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractKeywordRecommender.cs new file mode 100644 index 0000000000..1cf1e8e40a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractKeywordRecommender.cs @@ -0,0 +1,55 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AbstractKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.OverrideKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validTypeModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword + }; + + public AbstractKeywordRecommender() + : base(SyntaxKind.AbstractKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassOnlyTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken) || + context.IsTypeDeclarationContext( + validModifiers: s_validTypeModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractSyntacticSingleKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractSyntacticSingleKeywordRecommender.cs new file mode 100644 index 0000000000..584dd1d9b8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AbstractSyntacticSingleKeywordRecommender.cs @@ -0,0 +1,79 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal abstract partial class AbstractSyntacticSingleKeywordRecommender : IKeywordRecommender<CSharpSyntaxContext> + { + private readonly bool _isValidInPreprocessorContext; + + protected internal SyntaxKind KeywordKind { get; private set; } + + internal bool ShouldFormatOnCommit { get; private set; } + + protected AbstractSyntacticSingleKeywordRecommender( + SyntaxKind keywordKind, + bool isValidInPreprocessorContext = false, + bool shouldFormatOnCommit = false) + { + this.KeywordKind = keywordKind; + _isValidInPreprocessorContext = isValidInPreprocessorContext; + this.ShouldFormatOnCommit = shouldFormatOnCommit; + } + + protected abstract bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken); + + public IEnumerable<RecommendedKeyword> RecommendKeywords( + int position, + CSharpSyntaxContext context, + CancellationToken cancellationToken) + { + var syntaxKind = this.RecommendKeyword(position, context, cancellationToken); + if (syntaxKind.HasValue) + { + return new [] { + new RecommendedKeyword(SyntaxFacts.GetText(syntaxKind.Value), shouldFormatOnCommit: this.ShouldFormatOnCommit) + }; + } + + return null; + } + + internal IEnumerable<RecommendedKeyword> RecommendKeywords_Test(int position, CSharpSyntaxContext context) + { + var syntaxKind = this.RecommendKeyword(position, context, CancellationToken.None); + if (syntaxKind.HasValue) + { + return new [] { + new RecommendedKeyword(SyntaxFacts.GetText(syntaxKind.Value)) + }; + } + + return null; + } + + private SyntaxKind? RecommendKeyword(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // NOTE: The collector ensures that we're not in "NonUserCode" like comments, strings, inactive code + // for perf reasons. + var syntaxTree = context.SemanticModel.SyntaxTree; + if (!_isValidInPreprocessorContext && + context.IsPreProcessorDirectiveContext) + { + return null; + } + + if (!IsValidContext(position, context, cancellationToken)) + { + return null; + } + + return this.KeywordKind; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AddKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AddKeywordRecommender.cs new file mode 100644 index 0000000000..672e0502a2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AddKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AddKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AddKeywordRecommender() + : base(SyntaxKind.AddKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.TargetToken.IsAccessorDeclarationContext<EventDeclarationSyntax>(position, SyntaxKind.AddKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AliasKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AliasKeywordRecommender.cs new file mode 100644 index 0000000000..3d6b87302a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AliasKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AliasKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AliasKeywordRecommender() + : base(SyntaxKind.AliasKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // extern | + // extern a| + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.ExternKeyword) + { + // members can be 'extern' but we don't want + // 'alias' to show up in a 'type'. + return token.GetAncestor<TypeDeclarationSyntax>() == null; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsKeywordRecommender.cs new file mode 100644 index 0000000000..3d667239aa --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AsKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AsKeywordRecommender() + : base(SyntaxKind.AsKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return !context.IsInNonUserCode && context.IsIsOrAsContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AscendingKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AscendingKeywordRecommender.cs new file mode 100644 index 0000000000..ec3e54f007 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AscendingKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AscendingKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AscendingKeywordRecommender() + : base(SyntaxKind.AscendingKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.TargetToken.IsOrderByDirectionContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AssemblyKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AssemblyKeywordRecommender.cs new file mode 100644 index 0000000000..31cb8b3a47 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AssemblyKeywordRecommender.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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AssemblyKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AssemblyKeywordRecommender() + : base(SyntaxKind.AssemblyKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeAttributeContext(cancellationToken)) + { + var token = context.LeftToken; + var type = token.GetAncestor<MemberDeclarationSyntax>(); + + return type == null || type.IsParentKind(SyntaxKind.CompilationUnit); + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsyncKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsyncKeywordRecommender.cs new file mode 100644 index 0000000000..49abcb325d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AsyncKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AsyncKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AsyncKeywordRecommender() : + base(SyntaxKind.AsyncKeyword, isValidInPreprocessorContext: false) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsAnyExpressionContext) + { + return true; + } + + return !context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword) + && InMemberDeclarationContext(position, context, cancellationToken); + } + + private static bool InMemberDeclarationContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.SyntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) + || context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: true, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AwaitKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AwaitKeywordRecommender.cs new file mode 100644 index 0000000000..c2604eaad6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/AwaitKeywordRecommender.cs @@ -0,0 +1,54 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class AwaitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public AwaitKeywordRecommender() + : base(SyntaxKind.AwaitKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsAnyExpressionContext || context.IsStatementContext) + { + foreach (var node in context.LeftToken.GetAncestors<SyntaxNode>()) + { + if (node.IsAnyLambdaOrAnonymousMethod()) + { + return true; + } + + if (node.IsKind(SyntaxKind.QueryExpression)) + { + return false; + } + + if (node.IsKind(SyntaxKind.LockStatement)) + { + var lockStatement = (LockStatementSyntax)node; + if (lockStatement.Statement != null && + !lockStatement.Statement.IsMissing && + lockStatement.Statement.Span.Contains(position)) + { + return false; + } + } + } + + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BaseKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BaseKeywordRecommender.cs new file mode 100644 index 0000000000..68206249d8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BaseKeywordRecommender.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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class BaseKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public BaseKeywordRecommender() + : base(SyntaxKind.BaseKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // We need to at least be in a type declaration context. This prevents us from showing + // calls to 'base' in things like top level repl statements and whatnot. + if (context.ContainingTypeDeclaration != null) + { + return + IsConstructorInitializerContext(position, context, cancellationToken) || + IsInstanceExpressionOrStatement(context); + } + + return false; + } + + private static bool IsInstanceExpressionOrStatement(CSharpSyntaxContext context) + { + if (context.IsInstanceContext) + { + return context.IsNonAttributeExpressionContext || context.IsStatementContext; + } + + return false; + } + + private bool IsConstructorInitializerContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // Foo() : | + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.ColonToken && + token.Parent is ConstructorInitializerSyntax && + token.Parent.IsParentKind(SyntaxKind.ConstructorDeclaration) && + token.Parent.GetParent().IsParentKind(SyntaxKind.ClassDeclaration)) + { + var constructor = token.GetAncestor<ConstructorDeclarationSyntax>(); + if (constructor.Modifiers.Any(t => t.Kind () == SyntaxKind.StaticKeyword)) + { + return false; + } + + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BoolKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BoolKeywordRecommender.cs new file mode 100644 index 0000000000..44c5b2322f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BoolKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class BoolKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public BoolKeywordRecommender() + : base(SyntaxKind.BoolKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BreakKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BreakKeywordRecommender.cs new file mode 100644 index 0000000000..8275ac1434 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/BreakKeywordRecommender.cs @@ -0,0 +1,54 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class BreakKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public BreakKeywordRecommender() + : base(SyntaxKind.BreakKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsInBreakableConstructContext(context) || + context.TargetToken.IsAfterYieldKeyword(); + } + + private static bool IsInBreakableConstructContext(CSharpSyntaxContext context) + { + if (!context.IsStatementContext) + { + return false; + } + + // allowed if we're inside a loop/switch construct. + + var token = context.LeftToken; + foreach (var v in token.GetAncestors<SyntaxNode>()) + { + if (v.IsAnyLambdaOrAnonymousMethod()) + { + // if we hit a lambda while walking up, then we can't + // 'continue' any outer loops. + return false; + } + + if (v.IsBreakableConstruct()) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByKeywordRecommender.cs new file mode 100644 index 0000000000..07d5db338e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByKeywordRecommender.cs @@ -0,0 +1,53 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ByKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ByKeywordRecommender() + : base(SyntaxKind.ByKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // group e | + // group e b| + + var token = context.LeftToken; + var group = token.GetAncestor<GroupClauseSyntax>(); + + if (group == null) + { + return false; + } + + var lastToken = group.GroupExpression.GetLastToken(includeSkipped: true); + + // group e | + if (!token.IntersectsWith(position) && + token == lastToken) + { + return true; + } + + // group e b| + if (token.IntersectsWith(position) && + token.Kind() == SyntaxKind.IdentifierToken && + token.GetPreviousToken(includeSkipped: true) == lastToken) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByteKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByteKeywordRecommender.cs new file mode 100644 index 0000000000..c40eb00cd7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ByteKeywordRecommender.cs @@ -0,0 +1,49 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ByteKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ByteKeywordRecommender() + : base(SyntaxKind.ByteKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CaseKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CaseKeywordRecommender.cs new file mode 100644 index 0000000000..c8e9c0f1db --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CaseKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class CaseKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public CaseKeywordRecommender() + : base(SyntaxKind.CaseKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsSwitchLabelContext() || + IsAfterGotoInSwitchContext(context); + } + + internal static bool IsAfterGotoInSwitchContext(CSharpSyntaxContext context) + { + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.GotoKeyword && + token.GetAncestor<SwitchStatementSyntax>() != null) + { + // todo: what if we're in a lambda... or a try/finally or + // something? Might want to filter this out. + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CatchKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CatchKeywordRecommender.cs new file mode 100644 index 0000000000..26433adf28 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CatchKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class CatchKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public CatchKeywordRecommender() + : base(SyntaxKind.CatchKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.SyntaxTree.IsCatchOrFinallyContext(position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CharKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CharKeywordRecommender.cs new file mode 100644 index 0000000000..a0d530742c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CharKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class CharKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public CharKeywordRecommender() + : base(SyntaxKind.CharKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CheckedKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CheckedKeywordRecommender.cs new file mode 100644 index 0000000000..bdcb72853d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/CheckedKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class CheckedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public CheckedKeywordRecommender() + : base(SyntaxKind.CheckedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsNonAttributeExpressionContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ChecksumKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ChecksumKeywordRecommender.cs new file mode 100644 index 0000000000..14aceee892 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ChecksumKeywordRecommender.cs @@ -0,0 +1,28 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ChecksumKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ChecksumKeywordRecommender() + : base(SyntaxKind.ChecksumKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // # pragma | + // # pragma w| + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.PragmaKeyword && + previousToken2.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ClassKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ClassKeywordRecommender.cs new file mode 100644 index 0000000000..47a255c363 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ClassKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ClassKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.AbstractKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword + }; + + public ClassKeywordRecommender() + : base(SyntaxKind.ClassKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsGlobalStatementContext || + context.IsTypeDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: true, + cancellationToken: cancellationToken) || + syntaxTree.IsTypeParameterConstraintStartContext(position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ConstKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ConstKeywordRecommender.cs new file mode 100644 index 0000000000..49710fe6ed --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ConstKeywordRecommender.cs @@ -0,0 +1,65 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ConstKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validGlobalModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + }; + + public ConstKeywordRecommender() + : base(SyntaxKind.ConstKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsMemberDeclarationContext(context, cancellationToken) || + IsLocalVariableDeclaration(context); + } + + private bool IsMemberDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, s_validGlobalModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + + private bool IsLocalVariableDeclaration(CSharpSyntaxContext context) + { + // cases: + // void Foo() { + // | + // + // | + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ContinueKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ContinueKeywordRecommender.cs new file mode 100644 index 0000000000..e2f1aeb73b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ContinueKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ContinueKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ContinueKeywordRecommender() + : base(SyntaxKind.ContinueKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (!context.IsStatementContext) + { + return false; + } + + // allowed if we're inside a loop construct. + + var leaf = context.LeftToken; + foreach (var v in leaf.GetAncestors<SyntaxNode>()) + { + if (v.IsAnyLambdaOrAnonymousMethod()) + { + // if we hit a lambda while walking up, then we can't + // 'continue' any outer loops. + return false; + } + + if (v.IsContinuableConstruct()) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DecimalKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DecimalKeywordRecommender.cs new file mode 100644 index 0000000000..e0280702fb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DecimalKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DecimalKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DecimalKeywordRecommender() + : base(SyntaxKind.DecimalKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefaultKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefaultKeywordRecommender.cs new file mode 100644 index 0000000000..935b5b62eb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefaultKeywordRecommender.cs @@ -0,0 +1,42 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DefaultKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DefaultKeywordRecommender() + : base(SyntaxKind.DefaultKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsValidPreProcessorContext(context) || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsAnyExpressionContext || + context.TargetToken.IsSwitchLabelContext(); + } + + private static bool IsValidPreProcessorContext(CSharpSyntaxContext context) + { + // cases: + // #line | + // #line d| + // # line | + // # line d| + + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.LineKeyword && + previousToken2.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefineKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefineKeywordRecommender.cs new file mode 100644 index 0000000000..5ff0f4412a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DefineKeywordRecommender.cs @@ -0,0 +1,25 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DefineKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DefineKeywordRecommender() + : base(SyntaxKind.DefineKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsBeforeFirstToken(position, cancellationToken) && + context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DelegateKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DelegateKeywordRecommender.cs new file mode 100644 index 0000000000..42049a0b93 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DelegateKeywordRecommender.cs @@ -0,0 +1,53 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DelegateKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword + }; + + public DelegateKeywordRecommender() + : base(SyntaxKind.DelegateKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + (context.IsNonAttributeExpressionContext && !context.IsConstantExpressionContext) || + IsAfterAsyncKeywordInExpressionContext(context, cancellationToken) || + context.IsTypeDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + + private static bool IsAfterAsyncKeywordInExpressionContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.AsyncKeyword) && + context.SyntaxTree.IsExpressionContext( + context.TargetToken.SpanStart, + context.TargetToken, + attributes: false, + cancellationToken: cancellationToken, + semanticModelOpt: context.SemanticModel); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DescendingKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DescendingKeywordRecommender.cs new file mode 100644 index 0000000000..0c62897af0 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DescendingKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DescendingKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DescendingKeywordRecommender() + : base(SyntaxKind.DescendingKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.TargetToken.IsOrderByDirectionContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DisableKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DisableKeywordRecommender.cs new file mode 100644 index 0000000000..87e5166b5d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DisableKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DisableKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DisableKeywordRecommender() + : base(SyntaxKind.DisableKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // # pragma warning | + // # pragma warning d| + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + var previousToken3 = previousToken2.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.WarningKeyword && + previousToken2.Kind() == SyntaxKind.PragmaKeyword && + previousToken3.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoKeywordRecommender.cs new file mode 100644 index 0000000000..ca525b4868 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DoKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DoKeywordRecommender() + : base(SyntaxKind.DoKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoubleKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoubleKeywordRecommender.cs new file mode 100644 index 0000000000..51330c929c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DoubleKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DoubleKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public DoubleKeywordRecommender() + : base(SyntaxKind.DoubleKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DynamicKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DynamicKeywordRecommender.cs new file mode 100644 index 0000000000..1a6c2bf517 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/DynamicKeywordRecommender.cs @@ -0,0 +1,64 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class DynamicKeywordRecommender : IKeywordRecommender<CSharpSyntaxContext> + { + private bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + if (context.IsPreProcessorDirectiveContext) + { + return false; + } + + return IsDynamicTypeContext(position, context, cancellationToken); + } + + public IEnumerable<RecommendedKeyword> RecommendKeywords(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (IsValidContext(position, context, cancellationToken)) + { + yield return new RecommendedKeyword("dynamic"); + } + } + + protected static bool IsDynamicTypeContext( + int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + // first do quick exit check + if (syntaxTree.IsDefinitelyNotTypeContext(position, cancellationToken)) + { + return false; + } + + return + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsDefiniteCastTypeContext || + syntaxTree.IsPossibleCastTypeContext(position, context.LeftToken, cancellationToken) || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + syntaxTree.IsDefaultExpressionContext(position, context.LeftToken, cancellationToken) || + context.IsLocalVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElifKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElifKeywordRecommender.cs new file mode 100644 index 0000000000..0d2b5d21b9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElifKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ElifKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ElifKeywordRecommender() + : base(SyntaxKind.ElifKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElseKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElseKeywordRecommender.cs new file mode 100644 index 0000000000..9c6acbd43d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ElseKeywordRecommender.cs @@ -0,0 +1,63 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ElseKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ElseKeywordRecommender() + : base(SyntaxKind.ElseKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsPreProcessorKeywordContext) + { + return true; + } + + var token = context.TargetToken; + + var statement = token.GetAncestor<StatementSyntax>(); + var ifStatement = statement.GetAncestorOrThis<IfStatementSyntax>(); + + if (statement == null || ifStatement == null) + { + return false; + } + + // cases: + // if (foo) + // Console.WriteLine(); + // | + // if (foo) + // Console.WriteLine(); + // e| + if (token.IsKind(SyntaxKind.SemicolonToken) && ifStatement.Statement.GetLastToken(includeSkipped: true) == token) + { + return true; + } + + // if (foo) { + // Console.WriteLine(); + // } | + // if (foo) { + // Console.WriteLine(); + // } e| + if (token.IsKind(SyntaxKind.CloseBraceToken) && ifStatement.Statement is BlockSyntax && token == ((BlockSyntax)ifStatement.Statement).CloseBraceToken) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndIfKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndIfKeywordRecommender.cs new file mode 100644 index 0000000000..0042a43660 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndIfKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class EndIfKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public EndIfKeywordRecommender() + : base(SyntaxKind.EndIfKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndRegionKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndRegionKeywordRecommender.cs new file mode 100644 index 0000000000..741c814181 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EndRegionKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class EndRegionKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public EndRegionKeywordRecommender() + : base(SyntaxKind.EndRegionKeyword, isValidInPreprocessorContext: true, shouldFormatOnCommit: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EnumKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EnumKeywordRecommender.cs new file mode 100644 index 0000000000..69d31fad97 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EnumKeywordRecommender.cs @@ -0,0 +1,37 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class EnumKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + }; + + public EnumKeywordRecommender() + : base(SyntaxKind.EnumKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.IsTypeDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EqualsKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EqualsKeywordRecommender.cs new file mode 100644 index 0000000000..e2f4bd912a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EqualsKeywordRecommender.cs @@ -0,0 +1,45 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class EqualsKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public EqualsKeywordRecommender() + : base(SyntaxKind.EqualsKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // join a in expr o1 | + // join a in expr o1 e| + + var token = context.TargetToken; + + var join = token.GetAncestor<JoinClauseSyntax>(); + if (join == null) + { + return false; + } + + var lastToken = join.LeftExpression.GetLastToken(includeSkipped: true); + + // join a in expr | + if (join.LeftExpression.Width() > 0 && + token == lastToken) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ErrorKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ErrorKeywordRecommender.cs new file mode 100644 index 0000000000..5e21f268ae --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ErrorKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ErrorKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ErrorKeywordRecommender() + : base(SyntaxKind.ErrorKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EventKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EventKeywordRecommender.cs new file mode 100644 index 0000000000..04c955265e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/EventKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class EventKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.VirtualKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.AbstractKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.UnsafeKeyword + }; + + public EventKeywordRecommender() + : base(SyntaxKind.EventKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsGlobalStatementContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext(validModifiers: s_validModifiers, validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken) || + context.IsMemberAttributeContext(SyntaxKindSet.ClassInterfaceStructTypeDeclarations, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExplicitKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExplicitKeywordRecommender.cs new file mode 100644 index 0000000000..7931912b56 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExplicitKeywordRecommender.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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ExplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.UnsafeKeyword, + }; + + public ExplicitKeywordRecommender() + : base(SyntaxKind.ExplicitKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsMemberDeclarationContext(validModifiers: s_validMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // operators must be both public and static + var modifiers = context.PrecedingModifiers; + + return + modifiers.Contains(SyntaxKind.PublicKeyword) && + modifiers.Contains(SyntaxKind.StaticKeyword); + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExternKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExternKeywordRecommender.cs new file mode 100644 index 0000000000..7c6f740ab9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ExternKeywordRecommender.cs @@ -0,0 +1,100 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ExternKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.VirtualKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validGlobalModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword, + }; + + public ExternKeywordRecommender() + : base(SyntaxKind.ExternKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + IsExternAliasContext(context) || + context.IsGlobalStatementContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, s_validGlobalModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + + private static bool IsExternAliasContext(CSharpSyntaxContext context) + { + // cases: + // root: | + + // root: e| + + // extern alias a; + // | + + // extern alias a; + // e| + + // all the above, but inside a namespace. + // usings and other constructs *cannot* precede. + + var token = context.TargetToken; + + // root: | + if (token.Kind() == SyntaxKind.None) + { + // root namespace + return true; + } + + if (token.Kind() == SyntaxKind.OpenBraceToken && + token.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + { + return true; + } + + // extern alias a; + // | + if (token.Kind() == SyntaxKind.SemicolonToken && + token.Parent.IsKind(SyntaxKind.ExternAliasDirective)) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FalseKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FalseKeywordRecommender.cs new file mode 100644 index 0000000000..1fa404a42b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FalseKeywordRecommender.cs @@ -0,0 +1,26 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FalseKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public FalseKeywordRecommender() + : base(SyntaxKind.FalseKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsAnyExpressionContext || + context.IsPreProcessorExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.TargetToken.IsUnaryOperatorContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FieldKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FieldKeywordRecommender.cs new file mode 100644 index 0000000000..cb50cf176b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FieldKeywordRecommender.cs @@ -0,0 +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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + // interfaces don't have members that you can put a [field:] attribute on + private static readonly ISet<SyntaxKind> s_validTypeDeclarations = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.StructDeclaration, + SyntaxKind.ClassDeclaration, + SyntaxKind.EnumDeclaration, + }; + + public FieldKeywordRecommender() + : base(SyntaxKind.FieldKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsMemberAttributeContext(s_validTypeDeclarations, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FinallyKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FinallyKeywordRecommender.cs new file mode 100644 index 0000000000..0ac6a78e75 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FinallyKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FinallyKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public FinallyKeywordRecommender() + : base(SyntaxKind.FinallyKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return syntaxTree.IsCatchOrFinallyContext(position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FixedKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FixedKeywordRecommender.cs new file mode 100644 index 0000000000..371916e0a6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FixedKeywordRecommender.cs @@ -0,0 +1,50 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FixedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.UnsafeKeyword, + }; + + public FixedKeywordRecommender() + : base(SyntaxKind.FixedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsUnsafeStatementContext(context) || + IsMemberDeclarationContext(context, cancellationToken); + } + + private static bool IsMemberDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsUnsafeContext() && + (context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext(validModifiers: s_validModifiers, validTypeDeclarations: SyntaxKindSet.StructOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)); + } + + private static bool IsUnsafeStatementContext(CSharpSyntaxContext context) + { + return + context.TargetToken.IsUnsafeContext() && + context.IsStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FloatKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FloatKeywordRecommender.cs new file mode 100644 index 0000000000..e5759bfcd8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FloatKeywordRecommender.cs @@ -0,0 +1,47 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FloatKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public FloatKeywordRecommender() + : base(SyntaxKind.FloatKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForEachKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForEachKeywordRecommender.cs new file mode 100644 index 0000000000..f39dc09669 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForEachKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ForEachKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ForEachKeywordRecommender() + : base(SyntaxKind.ForEachKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForKeywordRecommender.cs new file mode 100644 index 0000000000..6013d73f54 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ForKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ForKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ForKeywordRecommender() + : base(SyntaxKind.ForKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FromKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FromKeywordRecommender.cs new file mode 100644 index 0000000000..c9db4eda7f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/FromKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class FromKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public FromKeywordRecommender() + : base(SyntaxKind.FromKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsGlobalStatementContext || + syntaxTree.IsValidContextForFromClause(position, context.LeftToken, cancellationToken, semanticModelOpt: context.SemanticModel); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GetKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GetKeywordRecommender.cs new file mode 100644 index 0000000000..22fe43af57 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GetKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class GetKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public GetKeywordRecommender() + : base(SyntaxKind.GetKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(position, SyntaxKind.GetKeyword) || + context.TargetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(position, SyntaxKind.GetKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GlobalKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GlobalKeywordRecommender.cs new file mode 100644 index 0000000000..7cb8df670e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GlobalKeywordRecommender.cs @@ -0,0 +1,42 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class GlobalKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public GlobalKeywordRecommender() + : base(SyntaxKind.GlobalKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + if (syntaxTree.IsMemberDeclarationContext(position, context.LeftToken, cancellationToken)) + { + var token = context.TargetToken; + + if (token.GetAncestor<EnumDeclarationSyntax>() == null) + { + return true; + } + } + + return + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsAnyExpressionContext || + context.IsObjectCreationTypeContext || + context.IsIsOrAsTypeContext || + syntaxTree.IsUsingAliasContext(position, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GotoKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GotoKeywordRecommender.cs new file mode 100644 index 0000000000..e252d97856 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GotoKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class GotoKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public GotoKeywordRecommender() + : base(SyntaxKind.GotoKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GroupKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GroupKeywordRecommender.cs new file mode 100644 index 0000000000..401c5825d6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/GroupKeywordRecommender.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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class GroupKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public GroupKeywordRecommender() + : base(SyntaxKind.GroupKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + // var q = from x in y + // | + if (!token.IntersectsWith(position) && + token.IsLastTokenOfQueryClause()) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/HiddenKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/HiddenKeywordRecommender.cs new file mode 100644 index 0000000000..f7047a9dcf --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/HiddenKeywordRecommender.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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class HiddenKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public HiddenKeywordRecommender() + : base(SyntaxKind.HiddenKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // #line | + // #line h| + // # line | + // # line h| + + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.LineKeyword && + previousToken2.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IfKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IfKeywordRecommender.cs new file mode 100644 index 0000000000..dd5839e814 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IfKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class IfKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public IfKeywordRecommender() + : base(SyntaxKind.IfKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsPreProcessorKeywordContext || + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ImplicitKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ImplicitKeywordRecommender.cs new file mode 100644 index 0000000000..ba32b643c0 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ImplicitKeywordRecommender.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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ImplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.UnsafeKeyword, + }; + + public ImplicitKeywordRecommender() + : base(SyntaxKind.ImplicitKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsMemberDeclarationContext(validModifiers: s_validMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // operators must be both public and static + var modifiers = context.PrecedingModifiers; + + return + modifiers.Contains(SyntaxKind.PublicKeyword) && + modifiers.Contains(SyntaxKind.StaticKeyword); + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InKeywordRecommender.cs new file mode 100644 index 0000000000..f8c14fef53 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InKeywordRecommender.cs @@ -0,0 +1,112 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class InKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public InKeywordRecommender() + : base(SyntaxKind.InKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsValidContextInForEachClause(context) || + IsValidContextInFromClause(context, cancellationToken) || + IsValidContextInJoinClause(context, cancellationToken) || + context.TargetToken.IsTypeParameterVarianceContext(); + } + + private bool IsValidContextInForEachClause(CSharpSyntaxContext context) + { + // cases: + // foreach (var v | + // foreach (var v i| + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.IdentifierToken) + { + var statement = token.GetAncestor<ForEachStatementSyntax>(); + if (statement != null && token == statement.Identifier) + { + return true; + } + } + + return false; + } + + private bool IsValidContextInFromClause(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.IdentifierToken) + { + // case: + // from x | + if (token.GetPreviousToken(includeSkipped: true).IsKindOrHasMatchingText(SyntaxKind.FromKeyword)) + { + var typeSyntax = token.Parent as TypeSyntax; + if (!typeSyntax.IsPotentialTypeName(context.SemanticModel, cancellationToken)) + { + return true; + } + } + + var fromClause = token.Parent as FromClauseSyntax; + if (fromClause != null) + { + // case: + // from int x | + if (token == fromClause.Identifier && fromClause.Type != null) + { + return true; + } + } + } + + return false; + } + + private bool IsValidContextInJoinClause(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.IdentifierToken) + { + var joinClause = token.Parent.FirstAncestorOrSelf<JoinClauseSyntax>(); + if (joinClause != null) + { + // case: + // join int x | + if (token == joinClause.Identifier && joinClause.Type != null) + { + return true; + } + + // case: + // join x | + if (joinClause.Type != null && + joinClause.Type.IsKind(SyntaxKind.IdentifierName) && + token == ((IdentifierNameSyntax)joinClause.Type).Identifier && + !joinClause.Type.IsPotentialTypeName(context.SemanticModel, cancellationToken)) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntKeywordRecommender.cs new file mode 100644 index 0000000000..91dc5e0fe8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class IntKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public IntKeywordRecommender() + : base(SyntaxKind.IntKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InterfaceKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InterfaceKeywordRecommender.cs new file mode 100644 index 0000000000..56107f6ff0 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InterfaceKeywordRecommender.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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class InterfaceKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword + }; + + public InterfaceKeywordRecommender() + : base(SyntaxKind.InterfaceKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.IsTypeDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: true, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InternalKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InternalKeywordRecommender.cs new file mode 100644 index 0000000000..9e975f2735 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/InternalKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class InternalKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public InternalKeywordRecommender() + : base(SyntaxKind.InternalKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + IsValidContextForAccessor(context) || + IsValidContextForType(context, cancellationToken) || + IsValidContextForMember(context, cancellationToken); + } + + private static bool IsValidContextForAccessor(CSharpSyntaxContext context) + { + if (context.TargetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(context.Position) || + context.TargetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(context.Position)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForMember(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext(validModifiers: SyntaxKindSet.AllMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForType(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeDeclarationContext(validModifiers: SyntaxKindSet.AllTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool CheckPreviousAccessibilityModifiers(CSharpSyntaxContext context) + { + // internal things can be protected. + var precedingModifiers = context.PrecedingModifiers; + return + !precedingModifiers.Contains(SyntaxKind.PublicKeyword) && + !precedingModifiers.Contains(SyntaxKind.InternalKeyword) && + !precedingModifiers.Contains(SyntaxKind.PrivateKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntoKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntoKeywordRecommender.cs new file mode 100644 index 0000000000..d19d8dcf64 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IntoKeywordRecommender.cs @@ -0,0 +1,117 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class IntoKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public IntoKeywordRecommender() + : base(SyntaxKind.IntoKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsValidContextForJoin(context) || + IsValidContextForSelect(context) || + IsValidContextForGroup(context); + } + + private bool IsValidContextForSelect(CSharpSyntaxContext context) + { + var token = context.TargetToken; + + var select = token.GetAncestor<SelectClauseSyntax>(); + if (select == null) + { + return false; + } + + if (select.Expression.Width() == 0) + { + return false; + } + + var lastToken = select.Expression.GetLastToken(includeSkipped: true); + + if (lastToken == token) + { + return true; + } + + return false; + } + + private bool IsValidContextForGroup(CSharpSyntaxContext context) + { + var token = context.TargetToken; + + var group = token.GetAncestor<GroupClauseSyntax>(); + if (group == null) + { + return false; + } + + if (group.ByExpression.Width() == 0 || + group.GroupExpression.Width() == 0) + { + return false; + } + + var lastToken = group.ByExpression.GetLastToken(includeSkipped: true); + + if (lastToken == token) + { + return true; + } + + return false; + } + + private static bool IsValidContextForJoin(CSharpSyntaxContext context) + { + // cases: + // join a in expr o1 equals o2 | + // join a in expr o1 equals o2 i| + + var token = context.TargetToken; + var join = token.GetAncestor<JoinClauseSyntax>(); + + if (join == null) + { + // happens for: + // join a in expr on o1 equals o2 e| + if (!token.IntersectsWith(context.Position)) + { + return false; + } + + token = token.GetPreviousToken(includeSkipped: true); + join = token.GetAncestor<JoinClauseSyntax>(); + + if (join == null) + { + return false; + } + } + + var lastToken = join.RightExpression.GetLastToken(includeSkipped: true); + + // join a in expr on o1 equals o2 | + if (token == lastToken && + !lastToken.IntersectsWith(context.Position)) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IsKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IsKeywordRecommender.cs new file mode 100644 index 0000000000..e9359aae81 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/IsKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class IsKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public IsKeywordRecommender() + : base(SyntaxKind.IsKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // expr | + return !context.IsInNonUserCode && context.IsIsOrAsContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/JoinKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/JoinKeywordRecommender.cs new file mode 100644 index 0000000000..d61b937922 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/JoinKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class JoinKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public JoinKeywordRecommender() + : base(SyntaxKind.JoinKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.SyntaxTree.IsValidContextForJoinClause(position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LetKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LetKeywordRecommender.cs new file mode 100644 index 0000000000..f76d9b97ca --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LetKeywordRecommender.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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class LetKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public LetKeywordRecommender() + : base(SyntaxKind.LetKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + // var q = from x in y + // | + if (!token.IntersectsWith(position) && + token.IsLastTokenOfQueryClause()) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LineKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LineKeywordRecommender.cs new file mode 100644 index 0000000000..f481ef3436 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LineKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class LineKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public LineKeywordRecommender() + : base(SyntaxKind.LineKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LockKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LockKeywordRecommender.cs new file mode 100644 index 0000000000..0ce70f2edb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LockKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class LockKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public LockKeywordRecommender() + : base(SyntaxKind.LockKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LongKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LongKeywordRecommender.cs new file mode 100644 index 0000000000..c2e8ce8963 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/LongKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class LongKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public LongKeywordRecommender() + : base(SyntaxKind.LongKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/MethodKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/MethodKeywordRecommender.cs new file mode 100644 index 0000000000..e9e61a1240 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/MethodKeywordRecommender.cs @@ -0,0 +1,43 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class MethodKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public MethodKeywordRecommender() + : base(SyntaxKind.MethodKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsMemberAttributeContext(SyntaxKindSet.ClassInterfaceStructTypeDeclarations, cancellationToken)) + { + return true; + } + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.OpenBracketToken && + token.Parent.IsKind(SyntaxKind.AttributeList)) + { + if (token.GetAncestor<PropertyDeclarationSyntax>() != null || + token.GetAncestor<EventDeclarationSyntax>() != null) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ModuleKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ModuleKeywordRecommender.cs new file mode 100644 index 0000000000..f86d6d6a11 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ModuleKeywordRecommender.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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ModuleKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ModuleKeywordRecommender() + : base(SyntaxKind.ModuleKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeAttributeContext(cancellationToken)) + { + var token = context.LeftToken; + var type = token.GetAncestor<MemberDeclarationSyntax>(); + + return type == null || type.IsParentKind(SyntaxKind.CompilationUnit); + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NameOfKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NameOfKeywordRecommender.cs new file mode 100644 index 0000000000..dc6589997f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NameOfKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class NameOfKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public NameOfKeywordRecommender() + : base(SyntaxKind.NameOfKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsAnyExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + IsAttributeArgumentContext(context); + } + + private bool IsAttributeArgumentContext(CSharpSyntaxContext context) + { + return + context.IsAnyExpressionContext && + context.LeftToken.GetAncestor<AttributeSyntax>() != null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NamespaceKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NamespaceKeywordRecommender.cs new file mode 100644 index 0000000000..f2cd4ef2fc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NamespaceKeywordRecommender.cs @@ -0,0 +1,160 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class NamespaceKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public NamespaceKeywordRecommender() + : base(SyntaxKind.NamespaceKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + // namespaces are illegal in interactive code: + if (syntaxTree.IsInteractiveOrScript()) + { + return false; + } + + // cases: + // root: | + + // root: n| + + // extern alias a; + // | + + // extern alias a; + // n| + + // using Foo; + // | + + // using Foo; + // n| + + // using Foo = Bar; + // | + + // using Foo = Bar; + // n| + + // namespace N {} + // | + + // namespace N {} + // n| + + // class C {} + // | + + // class C {} + // n| + + var leftToken = context.LeftToken; + var token = context.TargetToken; + + // root: n| + + // ns Foo { n| + + // extern alias a; + // n| + + // using Foo; + // n| + + // using Foo = Bar; + // n| + + // a namespace can't come before usings/externs + // a child namespace can't come before usings/externs + if (leftToken.GetNextToken(includeSkipped: true).IsUsingOrExternKeyword()) + { + return false; + } + + // root: | + if (token.Kind() == SyntaxKind.None) + { + // root namespace + var root = syntaxTree.GetRoot(cancellationToken) as CompilationUnitSyntax; + if (root.Externs.Count > 0 || + root.Usings.Count > 0) + { + return false; + } + + return true; + } + + if (token.Kind() == SyntaxKind.OpenBraceToken && + token.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + { + return true; + } + + // extern alias a; + // | + + // using Foo; + // | + if (token.Kind() == SyntaxKind.SemicolonToken) + { + if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective, SyntaxKind.UsingDirective)) + { + return true; + } + } + + // class C {} + // | + if (token.Kind() == SyntaxKind.CloseBraceToken) + { + if (token.Parent is TypeDeclarationSyntax && + !(token.Parent.GetParent() is TypeDeclarationSyntax)) + { + return true; + } + else if (token.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + { + return true; + } + } + + // delegate void D(); + // | + + if (token.Kind() == SyntaxKind.SemicolonToken) + { + if (token.Parent.IsKind(SyntaxKind.DelegateDeclaration) && + !(token.Parent.GetParent() is TypeDeclarationSyntax)) + { + return true; + } + } + + // [assembly: foo] + // | + + if (token.Kind() == SyntaxKind.CloseBracketToken && + token.Parent.IsKind(SyntaxKind.AttributeList) && + token.Parent.IsParentKind(SyntaxKind.CompilationUnit)) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NewKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NewKeywordRecommender.cs new file mode 100644 index 0000000000..f5c8c5742f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NewKeywordRecommender.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.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class NewKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AbstractKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.VirtualKeyword, + SyntaxKind.VolatileKeyword, + }; + + protected static readonly ISet<SyntaxKind> ValidTypeModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AbstractKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword + }; + + public NewKeywordRecommender() + : base(SyntaxKind.NewKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return IsValid (position, context, cancellationToken); + } + + public bool IsValid(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsNewConstraintContext(context, cancellationToken) || + context.IsAnyExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + IsMemberDeclarationContext(context, cancellationToken) || + IsTypeDeclarationContext(context, cancellationToken); + } + + private bool IsTypeDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeDeclarationContext(validModifiers: ValidTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // we must be on a nested type. + var token = context.LeftToken; + return token.GetAncestors<TypeDeclarationSyntax>() + .Any(t => token.SpanStart > t.OpenBraceToken.Span.End && + token.Span.End < t.CloseBraceToken.SpanStart); + } + + return false; + } + + private bool IsMemberDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + + private static bool IsNewConstraintContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // where T : | + // where T : class, | + // where T : Foo, | + // note: 'new()' can't come after a 'struct' constraint. + + if (context.SyntaxTree.IsTypeParameterConstraintStartContext(context.Position, context.LeftToken, cancellationToken)) + { + return true; + } + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.CommaToken && + token.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause)) + { + var constraintClause = token.Parent as TypeParameterConstraintClauseSyntax; + if (!constraintClause.Constraints + .OfType<ClassOrStructConstraintSyntax>() + .Any(c => c.ClassOrStructKeyword.Kind() == SyntaxKind.StructKeyword)) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NullKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NullKeywordRecommender.cs new file mode 100644 index 0000000000..3200470fb2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/NullKeywordRecommender.cs @@ -0,0 +1,52 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class NullKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public NullKeywordRecommender() + : base(SyntaxKind.NullKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + return + context.IsAnyExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + IsInSelectCaseContext(context, cancellationToken); + } + + private bool IsInSelectCaseContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + if (token.Kind() != SyntaxKind.CaseKeyword) + { + return false; + } + + var switchStatement = token.GetAncestor<SwitchStatementSyntax>(); + if (switchStatement != null) + { + var info = context.SemanticModel.GetTypeInfo(switchStatement.Expression, cancellationToken); + if (info.Type != null && + info.Type.IsValueType) + { + return false; + } + } + + return true; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ObjectKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ObjectKeywordRecommender.cs new file mode 100644 index 0000000000..15293d61e3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ObjectKeywordRecommender.cs @@ -0,0 +1,46 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ObjectKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ObjectKeywordRecommender() + : base(SyntaxKind.ObjectKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsNonAttributeExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsTypeOfExpressionContext || + context.IsCrefContext || + syntaxTree.IsDefaultExpressionContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OnKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OnKeywordRecommender.cs new file mode 100644 index 0000000000..0665f64786 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OnKeywordRecommender.cs @@ -0,0 +1,49 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class OnKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public OnKeywordRecommender() + : base(SyntaxKind.OnKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // join a in expr | + // join a in expr o| + // join a.b c in expr | + // join a.b c in expr o| + + var token = context.TargetToken; + + var join = token.GetAncestor<JoinClauseSyntax>(); + if (join == null) + { + return false; + } + + // join a in expr | + // join a.b c in expr | + + var lastToken = join.InExpression.GetLastToken(includeSkipped: true); + + if (join.InExpression.Width() > 0 && + token == lastToken) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OperatorKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OperatorKeywordRecommender.cs new file mode 100644 index 0000000000..2cc67a5057 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OperatorKeywordRecommender.cs @@ -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. + +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class OperatorKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { +// private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) +// { +// SyntaxKind.StaticKeyword, +// SyntaxKind.PublicKeyword, +// SyntaxKind.ExternKeyword, +// }; + + public OperatorKeywordRecommender() + : base(SyntaxKind.OperatorKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // public static implicit | + // public static explicit | + var token = context.TargetToken; + + return + token.Kind() == SyntaxKind.ImplicitKeyword || + token.Kind() == SyntaxKind.ExplicitKeyword; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OrderByKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OrderByKeywordRecommender.cs new file mode 100644 index 0000000000..7bc71ac08c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OrderByKeywordRecommender.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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class OrderByKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public OrderByKeywordRecommender() + : base(SyntaxKind.OrderByKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + // var q = from x in y + // | + if (!token.IntersectsWith(position) && + token.IsLastTokenOfQueryClause()) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OutKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OutKeywordRecommender.cs new file mode 100644 index 0000000000..8b81204b29 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OutKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class OutKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public OutKeywordRecommender() + : base(SyntaxKind.OutKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + + // TODO(cyrusn): lambda/anon methods can have out/ref parameters + return + context.TargetToken.IsTypeParameterVarianceContext() || + syntaxTree.IsParameterModifierContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsAnonymousMethodParameterModifierContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsPossibleLambdaParameterModifierContext(position, context.LeftToken, cancellationToken) || + context.TargetToken.IsConstructorOrMethodParameterArgumentContext() || + context.TargetToken.IsXmlCrefParameterModifierContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OverrideKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OverrideKeywordRecommender.cs new file mode 100644 index 0000000000..d01a356317 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/OverrideKeywordRecommender.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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class OverrideKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.AbstractKeyword, + }; + + public OverrideKeywordRecommender() + : base(SyntaxKind.OverrideKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamKeywordRecommender.cs new file mode 100644 index 0000000000..6d4cd307f1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamKeywordRecommender.cs @@ -0,0 +1,37 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ParamKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ParamKeywordRecommender() + : base(SyntaxKind.ParamKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.OpenBracketToken && + token.Parent.IsKind(SyntaxKind.AttributeList)) + { + if (token.GetAncestor<PropertyDeclarationSyntax>() != null || + token.GetAncestor<EventDeclarationSyntax>() != null) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamsKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamsKeywordRecommender.cs new file mode 100644 index 0000000000..dbb7434508 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ParamsKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ParamsKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ParamsKeywordRecommender() + : base(SyntaxKind.ParamsKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.SyntaxTree.IsParameterModifierContext(context.Position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PartialKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PartialKeywordRecommender.cs new file mode 100644 index 0000000000..e9105b4619 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PartialKeywordRecommender.cs @@ -0,0 +1,64 @@ +// 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.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class PartialKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AsyncKeyword, + SyntaxKind.StaticKeyword + }; + + public PartialKeywordRecommender() + : base(SyntaxKind.PartialKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + IsMemberDeclarationContext(context, cancellationToken) || + IsTypeDeclarationContext(context, cancellationToken); + } + + private bool IsMemberDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsMemberDeclarationContext(validModifiers: s_validMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + var token = context.LeftToken; + var decl = token.GetAncestor<TypeDeclarationSyntax>(); + + // partial methods must be in partial types + if (!decl.Modifiers.Any(t => t.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword))) + { + return false; + } + + return true; + } + + return false; + } + + private bool IsTypeDeclarationContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsTypeDeclarationContext( + validModifiers: SyntaxKindSet.AllTypeModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PragmaKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PragmaKeywordRecommender.cs new file mode 100644 index 0000000000..556d2ee144 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PragmaKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class PragmaKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public PragmaKeywordRecommender() + : base(SyntaxKind.PragmaKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PrivateKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PrivateKeywordRecommender.cs new file mode 100644 index 0000000000..26a1f1d8f2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PrivateKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class PrivateKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public PrivateKeywordRecommender() + : base(SyntaxKind.PrivateKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + IsValidContextForAccessor(context) || + IsValidContextForType(context, cancellationToken) || + IsValidContextForMember(context, cancellationToken); + } + + private static bool IsValidContextForAccessor(CSharpSyntaxContext context) + { + if (context.TargetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(context.Position) || + context.TargetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(context.Position)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForMember(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext(validModifiers: SyntaxKindSet.AllMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + var modifiers = context.PrecedingModifiers; + + // can't have private + abstract/virtual/override/sealed + if (modifiers.Contains(SyntaxKind.AbstractKeyword) || + modifiers.Contains(SyntaxKind.VirtualKeyword) || + modifiers.Contains(SyntaxKind.OverrideKeyword) || + modifiers.Contains(SyntaxKind.SealedKeyword)) + { + return false; + } + + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForType(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeDeclarationContext(validModifiers: SyntaxKindSet.AllTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // private things can't be in namespaces. + var typeDecl = context.ContainingTypeDeclaration; + if (typeDecl == null) + { + return false; + } + + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool CheckPreviousAccessibilityModifiers(CSharpSyntaxContext context) + { + var precedingModifiers = context.PrecedingModifiers; + return + !precedingModifiers.Contains(SyntaxKind.PublicKeyword) && + !precedingModifiers.Contains(SyntaxKind.InternalKeyword) && + !precedingModifiers.Contains(SyntaxKind.ProtectedKeyword) && + !precedingModifiers.Contains(SyntaxKind.PrivateKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PropertyKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PropertyKeywordRecommender.cs new file mode 100644 index 0000000000..6be63e7031 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PropertyKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class PropertyKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public PropertyKeywordRecommender() + : base(SyntaxKind.PropertyKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsMemberAttributeContext(SyntaxKindSet.ClassInterfaceStructTypeDeclarations, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ProtectedKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ProtectedKeywordRecommender.cs new file mode 100644 index 0000000000..3c7d897e22 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ProtectedKeywordRecommender.cs @@ -0,0 +1,74 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ProtectedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ProtectedKeywordRecommender() + : base(SyntaxKind.ProtectedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsValidContextForAccessor(context) || + IsValidContextForType(context, cancellationToken) || + IsValidContextForMember(context, cancellationToken); + } + + private static bool IsValidContextForAccessor(CSharpSyntaxContext context) + { + if (context.TargetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(context.Position) || + context.TargetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(context.Position)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForMember(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsMemberDeclarationContext(validModifiers: SyntaxKindSet.AllMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForType(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeDeclarationContext(validModifiers: SyntaxKindSet.AllTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // protected things can't be in namespaces. + var typeDecl = context.ContainingTypeDeclaration; + if (typeDecl == null) + { + return false; + } + + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool CheckPreviousAccessibilityModifiers(CSharpSyntaxContext context) + { + // We can show up after 'internal'. + var precedingModifiers = context.PrecedingModifiers; + return + !precedingModifiers.Contains(SyntaxKind.PublicKeyword) && + !precedingModifiers.Contains(SyntaxKind.ProtectedKeyword) && + !precedingModifiers.Contains(SyntaxKind.PrivateKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PublicKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PublicKeywordRecommender.cs new file mode 100644 index 0000000000..21debc6f36 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/PublicKeywordRecommender.cs @@ -0,0 +1,60 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class PublicKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public PublicKeywordRecommender() + : base(SyntaxKind.PublicKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + IsValidContextForType(context, cancellationToken) || + IsValidContextForMember(context, cancellationToken); + } + + private static bool IsValidContextForMember(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool IsValidContextForType(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsTypeDeclarationContext(validModifiers: SyntaxKindSet.AllTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return CheckPreviousAccessibilityModifiers(context); + } + + return false; + } + + private static bool CheckPreviousAccessibilityModifiers(CSharpSyntaxContext context) + { + var precedingModifiers = context.PrecedingModifiers; + return + !precedingModifiers.Contains(SyntaxKind.PublicKeyword) && + !precedingModifiers.Contains(SyntaxKind.InternalKeyword) && + !precedingModifiers.Contains(SyntaxKind.ProtectedKeyword) && + !precedingModifiers.Contains(SyntaxKind.PrivateKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReadOnlyKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReadOnlyKeywordRecommender.cs new file mode 100644 index 0000000000..0e845ed6e9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReadOnlyKeywordRecommender.cs @@ -0,0 +1,40 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ReadOnlyKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.StaticKeyword, + }; + + public ReadOnlyKeywordRecommender() + : base(SyntaxKind.ReadOnlyKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RefKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RefKeywordRecommender.cs new file mode 100644 index 0000000000..83081f1ebd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RefKeywordRecommender.cs @@ -0,0 +1,27 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class RefKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public RefKeywordRecommender() + : base(SyntaxKind.RefKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsParameterModifierContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsAnonymousMethodParameterModifierContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsPossibleLambdaParameterModifierContext(position, context.LeftToken, cancellationToken) || + context.TargetToken.IsConstructorOrMethodParameterArgumentContext() || + context.TargetToken.IsXmlCrefParameterModifierContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReferenceKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReferenceKeywordRecommender.cs new file mode 100644 index 0000000000..5fb24646fe --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReferenceKeywordRecommender.cs @@ -0,0 +1,26 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ReferenceKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ReferenceKeywordRecommender() + : base(SyntaxKind.ReferenceKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsInteractiveOrScript() && + syntaxTree.IsBeforeFirstToken(position, cancellationToken) && + context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RegionKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RegionKeywordRecommender.cs new file mode 100644 index 0000000000..67fffd3948 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RegionKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class RegionKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public RegionKeywordRecommender() + : base(SyntaxKind.RegionKeyword, isValidInPreprocessorContext: true, shouldFormatOnCommit: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RemoveKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RemoveKeywordRecommender.cs new file mode 100644 index 0000000000..e9abae057c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RemoveKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class RemoveKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public RemoveKeywordRecommender() + : base(SyntaxKind.RemoveKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.TargetToken.IsAccessorDeclarationContext<EventDeclarationSyntax>(position, SyntaxKind.RemoveKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RestoreKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RestoreKeywordRecommender.cs new file mode 100644 index 0000000000..715edd7c22 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/RestoreKeywordRecommender.cs @@ -0,0 +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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class RestoreKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public RestoreKeywordRecommender() + : base(SyntaxKind.RestoreKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // # pragma warning | + // # pragma warning r| + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + var previousToken3 = previousToken2.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.WarningKeyword && + previousToken2.Kind() == SyntaxKind.PragmaKeyword && + previousToken3.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReturnKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReturnKeywordRecommender.cs new file mode 100644 index 0000000000..da927d71be --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ReturnKeywordRecommender.cs @@ -0,0 +1,33 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ReturnKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ReturnKeywordRecommender() + : base(SyntaxKind.ReturnKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.TargetToken.IsAfterYieldKeyword() || + IsAttributeContext(context, cancellationToken); + } + + private static bool IsAttributeContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsMemberAttributeContext(SyntaxKindSet.ClassInterfaceStructTypeDeclarations, cancellationToken) || + (context.SyntaxTree.IsInteractiveOrScript() && context.IsTypeAttributeContext(cancellationToken)); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SByteKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SByteKeywordRecommender.cs new file mode 100644 index 0000000000..c8edd8e150 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SByteKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SByteKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public SByteKeywordRecommender() + : base(SyntaxKind.SByteKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsNonAttributeExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SealedKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SealedKeywordRecommender.cs new file mode 100644 index 0000000000..ac89445eee --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SealedKeywordRecommender.cs @@ -0,0 +1,55 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SealedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validTypeModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword + }; + + public SealedKeywordRecommender() + : base(SyntaxKind.SealedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassOnlyTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken) || + context.IsTypeDeclarationContext( + validModifiers: s_validTypeModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SelectKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SelectKeywordRecommender.cs new file mode 100644 index 0000000000..76afd5cc48 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SelectKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SelectKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public SelectKeywordRecommender() + : base(SyntaxKind.SelectKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + // for orderby, ascending is the default so select should be available in the orderby direction context + if (token.IsOrderByDirectionContext()) + { + return true; + } + + // var q = from x in y + // | + if (!token.IntersectsWith(position) && + token.IsLastTokenOfQueryClause()) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SetKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SetKeywordRecommender.cs new file mode 100644 index 0000000000..bd37ed26a0 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SetKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SetKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public SetKeywordRecommender() + : base(SyntaxKind.SetKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(position, SyntaxKind.SetKeyword) || + context.TargetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(position, SyntaxKind.SetKeyword); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ShortKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ShortKeywordRecommender.cs new file mode 100644 index 0000000000..e017ee200c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ShortKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ShortKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ShortKeywordRecommender() + : base(SyntaxKind.ShortKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SizeOfKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SizeOfKeywordRecommender.cs new file mode 100644 index 0000000000..b789d84171 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SizeOfKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SizeOfKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public SizeOfKeywordRecommender() + : base(SyntaxKind.SizeOfKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsNonAttributeExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StackAllocKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StackAllocKeywordRecommender.cs new file mode 100644 index 0000000000..eb66b49210 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StackAllocKeywordRecommender.cs @@ -0,0 +1,42 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class StackAllocKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public StackAllocKeywordRecommender() + : base(SyntaxKind.StackAllocKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // type t = | + var token = context.TargetToken; + if (token.IsUnsafeContext()) + { + if (token.Kind() == SyntaxKind.EqualsToken && + token.Parent.IsKind(SyntaxKind.EqualsValueClause) && + token.Parent.IsParentKind(SyntaxKind.VariableDeclarator) && + token.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration)) + { + var variableDeclaration = (VariableDeclarationSyntax)token.Parent.Parent.Parent; + if (variableDeclaration.IsParentKind(SyntaxKind.LocalDeclarationStatement) || + variableDeclaration.IsParentKind(SyntaxKind.ForStatement)) + { + return variableDeclaration.Type.IsVar || variableDeclaration.Type.IsKind(SyntaxKind.PointerType); + } + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StaticKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StaticKeywordRecommender.cs new file mode 100644 index 0000000000..8daf3e29b5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StaticKeywordRecommender.cs @@ -0,0 +1,83 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class StaticKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validTypeModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AsyncKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.VolatileKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validGlobalMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.VolatileKeyword, + }; + + public StaticKeywordRecommender() + : base(SyntaxKind.StaticKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.TargetToken.IsUsingKeywordInUsingDirective() || + IsValidContextForType(context, cancellationToken) || + IsValidContextForMember(context, cancellationToken); + } + + private static bool IsValidContextForMember(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, s_validGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + + private static bool IsValidContextForType(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsTypeDeclarationContext( + validModifiers: s_validTypeModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StringKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StringKeywordRecommender.cs new file mode 100644 index 0000000000..a064a15360 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StringKeywordRecommender.cs @@ -0,0 +1,46 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class StringKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public StringKeywordRecommender() + : base(SyntaxKind.StringKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsTypeOfExpressionContext || + context.IsCrefContext || + syntaxTree.IsDefaultExpressionContext(position, context.LeftToken, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StructKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StructKeywordRecommender.cs new file mode 100644 index 0000000000..ad11333782 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/StructKeywordRecommender.cs @@ -0,0 +1,40 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class StructKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InternalKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword + }; + + public StructKeywordRecommender() + : base(SyntaxKind.StructKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsGlobalStatementContext || + context.IsTypeDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: true, + cancellationToken: cancellationToken) || + syntaxTree.IsTypeParameterConstraintStartContext(position, context.LeftToken, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SwitchKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SwitchKeywordRecommender.cs new file mode 100644 index 0000000000..628463284a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/SwitchKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class SwitchKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public SwitchKeywordRecommender() + : base(SyntaxKind.SwitchKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThisKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThisKeywordRecommender.cs new file mode 100644 index 0000000000..ded002dfd5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThisKeywordRecommender.cs @@ -0,0 +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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ThisKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ThisKeywordRecommender() + : base(SyntaxKind.ThisKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsInstanceExpressionOrStatement(context) || + IsExtensionMethodParameterContext(context, cancellationToken) || + IsConstructorInitializerContext(context); + } + + private static bool IsInstanceExpressionOrStatement(CSharpSyntaxContext context) + { + if (context.IsInstanceContext) + { + return context.IsNonAttributeExpressionContext || context.IsStatementContext; + } + + return false; + } + + private bool IsConstructorInitializerContext(CSharpSyntaxContext context) + { + // cases: + // Foo() : | + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.ColonToken && + token.Parent is ConstructorInitializerSyntax && + token.Parent.IsParentKind(SyntaxKind.ConstructorDeclaration)) + { + var constructor = token.GetAncestor<ConstructorDeclarationSyntax>(); + if (constructor.Modifiers.Any(m => m.IsKind (SyntaxKind.StaticKeyword))) + { + return false; + } + + return true; + } + + return false; + } + + private static bool IsExtensionMethodParameterContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // TODO(cyrusn): lambda/anon methods can have out/ref parameters + if (!context.SyntaxTree.IsParameterModifierContext(context.Position, context.LeftToken, cancellationToken, allowableIndex: 0)) + { + return false; + } + + var token = context.LeftToken; + var method = token.GetAncestor<MethodDeclarationSyntax>(); + var typeDecl = method.GetAncestorOrThis<TypeDeclarationSyntax>(); + + if (method == null || typeDecl == null) + { + return false; + } + + if (typeDecl.Kind() != SyntaxKind.ClassDeclaration) + { + return false; + } + + if (!method.Modifiers.Any(t => t.Kind() == SyntaxKind.StaticKeyword)) + { + return false; + } + + if (!typeDecl.Modifiers.Any(t => t.Kind() == SyntaxKind.StaticKeyword)) + { + return false; + } + + return true; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThrowKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThrowKeywordRecommender.cs new file mode 100644 index 0000000000..9572ebe9ab --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ThrowKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ThrowKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ThrowKeywordRecommender() + : base(SyntaxKind.ThrowKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TrueKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TrueKeywordRecommender.cs new file mode 100644 index 0000000000..a190aae0b8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TrueKeywordRecommender.cs @@ -0,0 +1,26 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class TrueKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public TrueKeywordRecommender() + : base(SyntaxKind.TrueKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsAnyExpressionContext || + context.IsPreProcessorExpressionContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.TargetToken.IsUnaryOperatorContext(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TryKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TryKeywordRecommender.cs new file mode 100644 index 0000000000..27945aa024 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TryKeywordRecommender.cs @@ -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. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class TryKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public TryKeywordRecommender() + : base(SyntaxKind.TryKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeKeywordRecommender.cs new file mode 100644 index 0000000000..4d9bf61e9d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class TypeKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public TypeKeywordRecommender() + : base(SyntaxKind.TypeKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsTypeAttributeContext(cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeOfKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeOfKeywordRecommender.cs new file mode 100644 index 0000000000..c68e66d8e5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeOfKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class TypeOfKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public TypeOfKeywordRecommender() + : base(SyntaxKind.TypeOfKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + (context.IsAnyExpressionContext && !context.IsConstantExpressionContext) || + context.IsStatementContext || + context.IsGlobalStatementContext || + IsAttributeArgumentContext(context); + } + + private bool IsAttributeArgumentContext(CSharpSyntaxContext context) + { + return + context.IsAnyExpressionContext && + context.LeftToken.GetAncestor<AttributeSyntax>() != null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeVarKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeVarKeywordRecommender.cs new file mode 100644 index 0000000000..cac63dd97a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/TypeVarKeywordRecommender.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class TypeVarKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public TypeVarKeywordRecommender() + : base(SyntaxKind.TypeVarKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.OpenBracketToken && + token.Parent.IsKind(SyntaxKind.AttributeList)) + { + var typeParameters = token.GetAncestor<TypeParameterListSyntax>(); + var type = typeParameters.GetAncestorOrThis<TypeDeclarationSyntax>(); + + if (type != null && type.TypeParameterList == typeParameters) + { + return true; + } + + var @delegate = typeParameters.GetAncestorOrThis<DelegateDeclarationSyntax>(); + if (@delegate != null && @delegate.TypeParameterList == typeParameters) + { + return true; + } + + var method = typeParameters.GetAncestorOrThis<MethodDeclarationSyntax>(); + if (method != null && method.TypeParameterList == typeParameters) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UIntKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UIntKeywordRecommender.cs new file mode 100644 index 0000000000..282af54e04 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UIntKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UIntKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public UIntKeywordRecommender() + : base(SyntaxKind.UIntKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ULongKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ULongKeywordRecommender.cs new file mode 100644 index 0000000000..08c52123d8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/ULongKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class ULongKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ULongKeywordRecommender() + : base(SyntaxKind.ULongKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UShortKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UShortKeywordRecommender.cs new file mode 100644 index 0000000000..42406d6d2a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UShortKeywordRecommender.cs @@ -0,0 +1,48 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UShortKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public UShortKeywordRecommender() + : base(SyntaxKind.UShortKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsAnyExpressionContext || + context.IsDefiniteCastTypeContext || + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsObjectCreationTypeContext || + context.IsGenericTypeArgumentContext || + context.IsEnumBaseListContext || + context.IsIsOrAsTypeContext || + context.IsLocalVariableDeclarationContext || + context.IsFixedVariableDeclarationContext || + context.IsParameterTypeContext || + context.IsPossibleLambdaOrAnonymousMethodParameterTypeContext || + context.IsImplicitOrExplicitOperatorTypeContext || + context.IsPrimaryFunctionExpressionContext || + context.IsCrefContext || + syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || + syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) || + context.IsDelegateReturnTypeContext || + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: SyntaxKindSet.AllMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UncheckedKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UncheckedKeywordRecommender.cs new file mode 100644 index 0000000000..118703f82e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UncheckedKeywordRecommender.cs @@ -0,0 +1,24 @@ +// 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.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UncheckedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public UncheckedKeywordRecommender() + : base(SyntaxKind.UncheckedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsNonAttributeExpressionContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UndefKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UndefKeywordRecommender.cs new file mode 100644 index 0000000000..e887b40db7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UndefKeywordRecommender.cs @@ -0,0 +1,25 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UndefKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public UndefKeywordRecommender() + : base(SyntaxKind.UndefKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsBeforeFirstToken(position, cancellationToken) && + context.IsPreProcessorKeywordContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UnsafeKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UnsafeKeywordRecommender.cs new file mode 100644 index 0000000000..1a1accb611 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UnsafeKeywordRecommender.cs @@ -0,0 +1,74 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UnsafeKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validTypeModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AbstractKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AbstractKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.VirtualKeyword, + SyntaxKind.VolatileKeyword, + }; + + private static readonly ISet<SyntaxKind> s_validGlobalMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.VolatileKeyword, + }; + + public UnsafeKeywordRecommender() + : base(SyntaxKind.UnsafeKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + context.IsStatementContext || + context.IsGlobalStatementContext || + context.IsTypeDeclarationContext(validModifiers: s_validTypeModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken) || + syntaxTree.IsGlobalMemberDeclarationContext(position, s_validGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UsingKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UsingKeywordRecommender.cs new file mode 100644 index 0000000000..f1dc791e6f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/UsingKeywordRecommender.cs @@ -0,0 +1,139 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class UsingKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public UsingKeywordRecommender() + : base(SyntaxKind.UsingKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // using (foo) { } + // using Foo; + // using Foo = Bar; + return + context.IsStatementContext || + context.IsGlobalStatementContext || + IsUsingDirectiveContext(context, cancellationToken); + } + + private static bool IsUsingDirectiveContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // cases: + // root: | + + // root: u| + + // extern alias a; + // | + + // extern alias a; + // u| + + // using Foo; + // | + + // using Foo; + // u| + + // using Foo = Bar; + // | + + // using Foo = Bar; + // u| + + // t valid: + // namespace N {} + // | + + // namespace N {} + // u| + + // class C {} + // | + + // class C {} + // u| + + // | + // extern alias a; + + // u| + // extern alias a; + + var originalToken = context.LeftToken; + var token = context.TargetToken; + + // root: u| + + // ns Foo { u| + + // extern alias a; + // u| + + // using Foo; + // u| + + // using Foo = Bar; + // u| + + // root: | + if (token.Kind() == SyntaxKind.None) + { + // root namespace + + // a using can't come before externs + var nextToken = originalToken.GetNextToken(includeSkipped: true); + if (nextToken.Kind() == SyntaxKind.ExternKeyword || + ((CompilationUnitSyntax)context.SyntaxTree.GetRoot(cancellationToken)).Externs.Count > 0) + { + return false; + } + + return true; + } + + if (token.Kind() == SyntaxKind.OpenBraceToken && + token.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + { + var ns = (NamespaceDeclarationSyntax)token.Parent; + + // a child using can't come before externs + var nextToken = originalToken.GetNextToken(includeSkipped: true); + if (nextToken.Kind() == SyntaxKind.ExternKeyword) + { + return false; + } + + return true; + } + + // extern alias a; + // | + + // using Foo; + // | + if (token.Kind() == SyntaxKind.SemicolonToken) + { + if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective) || + token.Parent.IsKind(SyntaxKind.UsingDirective)) + { + return true; + } + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VarKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VarKeywordRecommender.cs new file mode 100644 index 0000000000..4f08a6f772 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VarKeywordRecommender.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 System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class VarKeywordRecommender : IKeywordRecommender<CSharpSyntaxContext> + { + public VarKeywordRecommender() + { + } + + private bool IsValidContext(CSharpSyntaxContext context) + { + if (context.IsStatementContext || + context.IsGlobalStatementContext) + { + return true; + } + + return context.IsLocalVariableDeclarationContext; + } + + public IEnumerable<RecommendedKeyword> RecommendKeywords(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (IsValidContext(context)) + { + yield return new RecommendedKeyword("var"); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VirtualKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VirtualKeywordRecommender.cs new file mode 100644 index 0000000000..d98e7b1616 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VirtualKeywordRecommender.cs @@ -0,0 +1,37 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class VirtualKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.UnsafeKeyword, + }; + + public VirtualKeywordRecommender() + : base(SyntaxKind.VirtualKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassOnlyTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VoidKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VoidKeywordRecommender.cs new file mode 100644 index 0000000000..4228baa2ea --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VoidKeywordRecommender.cs @@ -0,0 +1,112 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class VoidKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.VirtualKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.AbstractKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.AsyncKeyword + }; + + public VoidKeywordRecommender() + : base(SyntaxKind.VoidKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + IsMemberReturnTypeContext(position, context, cancellationToken) || + context.IsGlobalStatementContext || + context.IsTypeOfExpressionContext || + syntaxTree.IsSizeOfExpressionContext(position, context.LeftToken, cancellationToken) || + context.IsDelegateReturnTypeContext || + IsUnsafeLocalVariableDeclarationContext(context) || + IsUnsafeParameterTypeContext(context) || + IsUnsafeCastTypeContext(context) || + IsUnsafeDefaultExpressionContext(context, cancellationToken) || + context.IsFixedVariableDeclarationContext; + } + + private bool IsUnsafeDefaultExpressionContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.TargetToken.IsUnsafeContext() && + context.SyntaxTree.IsDefaultExpressionContext(context.Position, context.LeftToken, cancellationToken); + } + + private bool IsUnsafeCastTypeContext(CSharpSyntaxContext context) + { + if (context.TargetToken.IsUnsafeContext()) + { + if (context.IsDefiniteCastTypeContext) + { + return true; + } + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.OpenParenToken && + token.Parent.IsKind(SyntaxKind.ParenthesizedExpression)) + { + return true; + } + } + + return false; + } + + private bool IsUnsafeParameterTypeContext(CSharpSyntaxContext context) + { + return + context.TargetToken.IsUnsafeContext() && + context.IsParameterTypeContext; + } + + private bool IsUnsafeLocalVariableDeclarationContext(CSharpSyntaxContext context) + { + if (context.TargetToken.IsUnsafeContext()) + { + return + context.IsLocalVariableDeclarationContext || + context.IsStatementContext; + } + + return false; + } + + private bool IsMemberReturnTypeContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validModifiers, + validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructTypeDeclarations, + canBePartial: true, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VolatileKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VolatileKeywordRecommender.cs new file mode 100644 index 0000000000..c23381f2a2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/VolatileKeywordRecommender.cs @@ -0,0 +1,40 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class VolatileKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + private static readonly ISet<SyntaxKind> s_validMemberModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer) + { + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.StaticKeyword, + }; + + public VolatileKeywordRecommender() + : base(SyntaxKind.VolatileKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + context.IsGlobalStatementContext || + context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WarningKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WarningKeywordRecommender.cs new file mode 100644 index 0000000000..60cfc7c5c2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WarningKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class WarningKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public WarningKeywordRecommender() + : base(SyntaxKind.WarningKeyword, isValidInPreprocessorContext: true) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + // # warning + if (context.IsPreProcessorKeywordContext) + { + return true; + } + + // # pragma | + // # pragma w| + var previousToken1 = context.TargetToken; + var previousToken2 = previousToken1.GetPreviousToken(includeSkipped: true); + + return + previousToken1.Kind() == SyntaxKind.PragmaKeyword && + previousToken2.Kind() == SyntaxKind.HashToken; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhenKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhenKeywordRecommender.cs new file mode 100644 index 0000000000..bccfe04c80 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhenKeywordRecommender.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.Threading; +//using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +//using Microsoft.CodeAnalysis.CSharp; +// +//namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +//{ +// internal class WhenKeywordRecommender : AbstractSyntacticSingleKeywordRecommender +// { +// public WhenKeywordRecommender() +// : base(SyntaxKind.WhenKeyword, isValidInPreprocessorContext: true) +// { +// } +// +// protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) +// { +// return context.IsCatchFilterContext; +// } +// } +//} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhereKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhereKeywordRecommender.cs new file mode 100644 index 0000000000..e0ba199c16 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhereKeywordRecommender.cs @@ -0,0 +1,142 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class WhereKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public WhereKeywordRecommender() + : base(SyntaxKind.WhereKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return + IsQueryContext(context) || + IsTypeParameterConstraintContext(context); + } + + private bool IsTypeParameterConstraintContext(CSharpSyntaxContext context) + { + // cases: + // class C<T> | + // class C<T> : IFoo | + // class C<T> where T : IFoo | + // delegate void D<T> | + // delegate void D<T> where T : IFoo | + // void Foo<T>() | + // void Foo<T>() where T : IFoo | + + var token = context.TargetToken; + + // class C<T> | + + if (token.Kind() == SyntaxKind.GreaterThanToken) + { + var typeParameters = token.GetAncestor<TypeParameterListSyntax>(); + if (typeParameters != null && token == typeParameters.GetLastToken(includeSkipped: true)) + { + var decl = typeParameters.GetAncestorOrThis<TypeDeclarationSyntax>(); + if (decl != null && decl.TypeParameterList == typeParameters) + { + return true; + } + } + } + + // delegate void D<T>() | + if (token.Kind() == SyntaxKind.CloseParenToken && + token.Parent.IsKind(SyntaxKind.ParameterList) && + token.Parent.IsParentKind(SyntaxKind.DelegateDeclaration)) + { + var decl = token.GetAncestor<DelegateDeclarationSyntax>(); + if (decl != null && decl.TypeParameterList != null) + { + return true; + } + } + + // void Foo<T>() | + + if (token.Kind() == SyntaxKind.CloseParenToken && + token.Parent.IsKind(SyntaxKind.ParameterList) && + token.Parent.IsParentKind(SyntaxKind.MethodDeclaration)) + { + var decl = token.GetAncestor<MethodDeclarationSyntax>(); + if (decl != null && decl.Arity > 0) + { + return true; + } + } + + // class C<T> : IFoo | + var baseList = token.GetAncestor<BaseListSyntax>(); + if (baseList.GetParent() is TypeDeclarationSyntax) + { + var typeDecl = baseList.GetParent() as TypeDeclarationSyntax; + if (typeDecl.TypeParameterList != null && + typeDecl.BaseList.Types.Any(t => token == t.GetLastToken(includeSkipped: true))) + { + // token is IdentifierName "where" + // only suggest "where" if token's previous token is also "where" + if (token.Parent is IdentifierNameSyntax && token.HasMatchingText(SyntaxKind.WhereKeyword)) + { + // Check for zero-width tokens in case there is a missing comma in the base list. + // For example: class C<T> : Foo where where | + return token + .GetPreviousToken(includeZeroWidth: true) + .IsKindOrHasMatchingText(SyntaxKind.WhereKeyword); + } + + // System.| + // Not done typing the qualified name + if (token.IsKind(SyntaxKind.DotToken)) + { + return false; + } + + return true; + } + } + + // class C<T> where T : IFoo | + // delegate void D<T> where T : IFoo | + var constraintClause = token.GetAncestor<TypeParameterConstraintClauseSyntax>(); + + if (constraintClause != null) + { + if (constraintClause.Constraints.Any(c => token == c.GetLastToken(includeSkipped: true))) + { + return true; + } + } + + return false; + } + + private static bool IsQueryContext(CSharpSyntaxContext context) + { + var token = context.TargetToken; + + // var q = from x in y + // | + if (!token.IntersectsWith(context.Position) && + token.IsLastTokenOfQueryClause()) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhileKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhileKeywordRecommender.cs new file mode 100644 index 0000000000..8185dc4a93 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/WhileKeywordRecommender.cs @@ -0,0 +1,50 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class WhileKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public WhileKeywordRecommender() + : base(SyntaxKind.WhileKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + if (context.IsStatementContext || + context.IsGlobalStatementContext) + { + return true; + } + + // do { + // } | + + // do { + // } w| + + // Note: the case of + // do + // Foo(); + // | + // is taken care of in the IsStatementContext case. + + var token = context.TargetToken; + + if (token.Kind() == SyntaxKind.CloseBraceToken && + token.Parent.IsKind(SyntaxKind.Block) && + token.Parent.IsParentKind(SyntaxKind.DoStatement)) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/YieldKeywordRecommender.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/YieldKeywordRecommender.cs new file mode 100644 index 0000000000..cf875303bd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Completion/KeywordRecommender/YieldKeywordRecommender.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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Completion.KeywordRecommenders +{ + internal class YieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public YieldKeywordRecommender() + : base(SyntaxKind.YieldKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.IsStatementContext; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractExtractMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractExtractMethodService.cs new file mode 100644 index 0000000000..2db2d4dc1f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractExtractMethodService.cs @@ -0,0 +1,45 @@ +// 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.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract class AbstractExtractMethodService<TValidator, TExtractor, TResult> : IExtractMethodService + where TValidator : SelectionValidator + where TExtractor : MethodExtractor + where TResult : SelectionResult + { + protected abstract TValidator CreateSelectionValidator(SemanticDocument document, TextSpan textSpan, OptionSet options); + protected abstract TExtractor CreateMethodExtractor(TResult selectionResult); + + public async Task<ExtractMethodResult> ExtractMethodAsync( + Document document, + TextSpan textSpan, + OptionSet options, + CancellationToken cancellationToken) + { + options = options ?? document.Project.Solution.Workspace.Options; + + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + var validator = this.CreateSelectionValidator(semanticDocument, textSpan, options); + + var selectionResult = await validator.GetValidSelectionAsync(cancellationToken).ConfigureAwait(false); + if (!selectionResult.ContainsValidContext) + { + return new FailedExtractMethodResult(selectionResult.Status); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // extract method + var extractor = this.CreateMethodExtractor((TResult)selectionResult); + + return await extractor.ExtractMethodAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.Result.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.Result.cs new file mode 100644 index 0000000000..6ce04841d1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.Result.cs @@ -0,0 +1,296 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class AbstractSyntaxTriviaService + { + private class Result : ITriviaSavedResult + { + private static readonly AnnotationResolver s_defaultAnnotationResolver = ResolveAnnotation; + private static readonly TriviaResolver s_defaultTriviaResolver = ResolveTrivia; + + private readonly SyntaxNode _root; + private readonly int _endOfLineKind; + + private readonly Dictionary<TriviaLocation, SyntaxAnnotation> _annotations; + private readonly Dictionary<TriviaLocation, IEnumerable<SyntaxTrivia>> _triviaList; + + public Result( + SyntaxNode root, + int endOfLineKind, + Dictionary<TriviaLocation, SyntaxAnnotation> annotations, + Dictionary<TriviaLocation, IEnumerable<SyntaxTrivia>> triviaList) + { + //Contract.ThrowIfNull(root); + //Contract.ThrowIfNull(annotations); + //Contract.ThrowIfNull(triviaList); + + _root = root; + _endOfLineKind = endOfLineKind; + + _annotations = annotations; + _triviaList = triviaList; + } + + public SyntaxNode Root + { + get { return _root; } + } + + public SyntaxNode RestoreTrivia( + SyntaxNode root, + AnnotationResolver annotationResolver = null, + TriviaResolver triviaResolver = null) + { + var tokens = RecoverTokensAtEdges(root, annotationResolver); + var map = CreateOldToNewTokensMap(tokens, triviaResolver); + + return root.ReplaceTokens(map.Keys, (o, n) => map[o]); + } + + private Dictionary<SyntaxToken, SyntaxToken> CreateOldToNewTokensMap( + Dictionary<TriviaLocation, PreviousNextTokenPair> tokenPairs, + Dictionary<TriviaLocation, LeadingTrailingTriviaPair> triviaPairs) + { + var map = new Dictionary<SyntaxToken, SyntaxToken>(); + foreach (var pair in CreateUniqueTokenTriviaPairs(tokenPairs, triviaPairs)) + { + var localCopy = pair; + var previousToken = map.GetOrAdd(localCopy.Item1.PreviousToken, _ => localCopy.Item1.PreviousToken); + map[localCopy.Item1.PreviousToken] = previousToken.WithTrailingTrivia(localCopy.Item2.TrailingTrivia); + + var nextToken = map.GetOrAdd(localCopy.Item1.NextToken, _ => localCopy.Item1.NextToken); + map[localCopy.Item1.NextToken] = nextToken.WithLeadingTrivia(localCopy.Item2.LeadingTrivia); + } + + return map; + } + + private LeadingTrailingTriviaPair GetTrailingAndLeadingTrivia(TriviaLocation locationKind, PreviousNextTokenPair tokenPair, IEnumerable<SyntaxTrivia> trivia) + { + var list = trivia.ToList(); + + // there are some noisy trivia + var index = GetFirstEndOfLineIndex(list); + + return new LeadingTrailingTriviaPair + { + TrailingTrivia = CreateTriviaListFromTo(list, 0, index), + LeadingTrivia = CreateTriviaListFromTo(list, index + 1, list.Count - 1) + }; + } + + private int GetFirstEndOfLineIndex(List<SyntaxTrivia> list) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].RawKind == _endOfLineKind) + { + return i; + } + } + + return list.Count - 1; + } + + private Dictionary<TriviaLocation, SyntaxToken> RecoverTokensAtEdges( + SyntaxNode root, + AnnotationResolver annotationResolver) + { + var resolver = annotationResolver ?? s_defaultAnnotationResolver; + + var tokens = Enumerable.Range((int)TriviaLocation.BeforeBeginningOfSpan, TriviaLocationsCount) + .Cast<TriviaLocation>() + .ToDictionary( + location => location, + location => resolver(root, location, _annotations[location])); + + // check variable assumption. ordering of two pairs can't be changed + //Contract.ThrowIfFalse( + // (tokens[TriviaLocation.BeforeBeginningOfSpan].RawKind == 0 && tokens[TriviaLocation.AfterEndOfSpan].RawKind == 0) || + // (tokens[TriviaLocation.BeforeBeginningOfSpan].RawKind == 0 /* && don't care */) || + // (/* don't care && */ tokens[TriviaLocation.AfterEndOfSpan].RawKind == 0) || + // (tokens[TriviaLocation.BeforeBeginningOfSpan].Span.End <= tokens[TriviaLocation.AfterEndOfSpan].SpanStart)); + + //Contract.ThrowIfFalse( + // (tokens[TriviaLocation.AfterBeginningOfSpan].RawKind == 0 && tokens[TriviaLocation.BeforeEndOfSpan].RawKind == 0) || + // (tokens[TriviaLocation.AfterBeginningOfSpan].RawKind == 0 /* && don't care */) || + // (/* don't care && */ tokens[TriviaLocation.BeforeEndOfSpan].RawKind == 0) || + // (tokens[TriviaLocation.AfterBeginningOfSpan] == tokens[TriviaLocation.BeforeEndOfSpan]) || + // (tokens[TriviaLocation.AfterBeginningOfSpan].GetPreviousToken(includeZeroWidth: true) == tokens[TriviaLocation.BeforeEndOfSpan]) || + // (tokens[TriviaLocation.AfterBeginningOfSpan].Span.End <= tokens[TriviaLocation.BeforeEndOfSpan].SpanStart)); + + return tokens; + } + + private Dictionary<SyntaxToken, SyntaxToken> CreateOldToNewTokensMap( + Dictionary<TriviaLocation, SyntaxToken> tokens, + TriviaResolver triviaResolver) + { + var tokenPairs = CreatePreviousNextTokenPairs(tokens); + var tokenToLeadingTrailingTriviaMap = CreateTokenLeadingTrailingTriviaMap(tokens); + + var resolver = triviaResolver ?? s_defaultTriviaResolver; + + var triviaPairs = Enumerable.Range((int)TriviaLocation.BeforeBeginningOfSpan, TriviaLocationsCount) + .Cast<TriviaLocation>() + .ToDictionary( + location => location, + location => CreateTriviaPairs( + location, + tokenPairs[location], + resolver(location, tokenPairs[location], tokenToLeadingTrailingTriviaMap))); + + return CreateOldToNewTokensMap(tokenPairs, triviaPairs); + } + + private LeadingTrailingTriviaPair CreateTriviaPairs( + TriviaLocation locationKind, + PreviousNextTokenPair tokenPair, + IEnumerable<SyntaxTrivia> trivia) + { + // beginning of the tree + if (tokenPair.PreviousToken.RawKind == 0) + { + return new LeadingTrailingTriviaPair { TrailingTrivia = SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(), LeadingTrivia = trivia }; + } + + return GetTrailingAndLeadingTrivia(locationKind, tokenPair, trivia); + } + + private IEnumerable<Tuple<PreviousNextTokenPair, LeadingTrailingTriviaPair>> CreateUniqueTokenTriviaPairs( + Dictionary<TriviaLocation, PreviousNextTokenPair> tokenPairs, + Dictionary<TriviaLocation, LeadingTrailingTriviaPair> triviaPairs) + { + // if there are dup, duplicated one will be ignored. + var set = new HashSet<PreviousNextTokenPair>(); + for (int i = (int)TriviaLocation.BeforeBeginningOfSpan; i <= (int)TriviaLocation.AfterEndOfSpan; i++) + { + var location = (TriviaLocation)i; + var key = tokenPairs[location]; + if (set.Contains(key)) + { + continue; + } + + yield return Tuple.Create(key, triviaPairs[location]); + set.Add(key); + } + } + + private Dictionary<SyntaxToken, LeadingTrailingTriviaPair> CreateTokenLeadingTrailingTriviaMap( + Dictionary<TriviaLocation, SyntaxToken> tokens) + { + var tuple = default(LeadingTrailingTriviaPair); + var map = new Dictionary<SyntaxToken, LeadingTrailingTriviaPair>(); + + tuple = map.GetOrAdd(tokens[TriviaLocation.BeforeBeginningOfSpan], _ => default(LeadingTrailingTriviaPair)); + map[tokens[TriviaLocation.BeforeBeginningOfSpan]] = new LeadingTrailingTriviaPair + { + LeadingTrivia = tuple.LeadingTrivia, + TrailingTrivia = _triviaList[TriviaLocation.BeforeBeginningOfSpan] + }; + + tuple = map.GetOrAdd(tokens[TriviaLocation.AfterBeginningOfSpan], _ => default(LeadingTrailingTriviaPair)); + map[tokens[TriviaLocation.AfterBeginningOfSpan]] = new LeadingTrailingTriviaPair + { + LeadingTrivia = _triviaList[TriviaLocation.AfterBeginningOfSpan], + TrailingTrivia = tuple.TrailingTrivia + }; + + tuple = map.GetOrAdd(tokens[TriviaLocation.BeforeEndOfSpan], _ => default(LeadingTrailingTriviaPair)); + map[tokens[TriviaLocation.BeforeEndOfSpan]] = new LeadingTrailingTriviaPair + { + LeadingTrivia = tuple.LeadingTrivia, + TrailingTrivia = _triviaList[TriviaLocation.BeforeEndOfSpan] + }; + + tuple = map.GetOrAdd(tokens[TriviaLocation.AfterEndOfSpan], _ => default(LeadingTrailingTriviaPair)); + map[tokens[TriviaLocation.AfterEndOfSpan]] = new LeadingTrailingTriviaPair + { + LeadingTrivia = _triviaList[TriviaLocation.AfterEndOfSpan], + TrailingTrivia = tuple.TrailingTrivia + }; + + return map; + } + + private Dictionary<TriviaLocation, PreviousNextTokenPair> CreatePreviousNextTokenPairs( + Dictionary<TriviaLocation, SyntaxToken> tokens) + { + var tokenPairs = new Dictionary<TriviaLocation, PreviousNextTokenPair>(); + + tokenPairs[TriviaLocation.BeforeBeginningOfSpan] = new PreviousNextTokenPair + { + PreviousToken = tokens[TriviaLocation.BeforeBeginningOfSpan], + NextToken = tokens[TriviaLocation.BeforeBeginningOfSpan].GetNextToken(includeZeroWidth: true) + }; + + tokenPairs[TriviaLocation.AfterBeginningOfSpan] = new PreviousNextTokenPair + { + PreviousToken = tokens[TriviaLocation.AfterBeginningOfSpan].GetPreviousToken(includeZeroWidth: true), + NextToken = tokens[TriviaLocation.AfterBeginningOfSpan] + }; + + tokenPairs[TriviaLocation.BeforeEndOfSpan] = new PreviousNextTokenPair + { + PreviousToken = tokens[TriviaLocation.BeforeEndOfSpan], + NextToken = tokens[TriviaLocation.BeforeEndOfSpan].GetNextToken(includeZeroWidth: true) + }; + + tokenPairs[TriviaLocation.AfterEndOfSpan] = new PreviousNextTokenPair + { + PreviousToken = tokens[TriviaLocation.AfterEndOfSpan].GetPreviousToken(includeZeroWidth: true), + NextToken = tokens[TriviaLocation.AfterEndOfSpan] + }; + + return tokenPairs; + } + + private IEnumerable<SyntaxTrivia> CreateTriviaListFromTo( + List<SyntaxTrivia> list, + int startIndex, + int endIndex) + { + if (startIndex > endIndex) + { + yield break; + } + + for (int i = startIndex; i <= endIndex; i++) + { + yield return list[i]; + } + } + + private static SyntaxToken ResolveAnnotation( + SyntaxNode root, + TriviaLocation location, + SyntaxAnnotation annotation) + { + return root.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken(); + } + + private static IEnumerable<SyntaxTrivia> ResolveTrivia( + TriviaLocation location, + PreviousNextTokenPair tokenPair, + Dictionary<SyntaxToken, LeadingTrailingTriviaPair> triviaMap) + { + var previousTriviaPair = triviaMap.ContainsKey(tokenPair.PreviousToken) ? triviaMap[tokenPair.PreviousToken] : default(LeadingTrailingTriviaPair); + var nextTriviaPair = triviaMap.ContainsKey(tokenPair.NextToken) ? triviaMap[tokenPair.NextToken] : default(LeadingTrailingTriviaPair); + + var trailingTrivia = previousTriviaPair.TrailingTrivia ?? SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + var leadingTrivia = nextTriviaPair.LeadingTrivia ?? SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + + return tokenPair.PreviousToken.TrailingTrivia.Concat(trailingTrivia).Concat(leadingTrivia).Concat(tokenPair.NextToken.LeadingTrivia); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.cs new file mode 100644 index 0000000000..61116b6aa8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/AbstractSyntaxTriviaService.cs @@ -0,0 +1,131 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class AbstractSyntaxTriviaService : ISyntaxTriviaService + { + private const int TriviaLocationsCount = 4; + + //private readonly ISyntaxFactsService _syntaxFactsService; + private readonly int _endOfLineKind; + + protected AbstractSyntaxTriviaService(/*ISyntaxFactsService syntaxFactsService, */int endOfLineKind) + { +// _syntaxFactsService = syntaxFactsService; + _endOfLineKind = endOfLineKind; + } + + public ITriviaSavedResult SaveTriviaAroundSelection(SyntaxNode root, TextSpan textSpan) + { + var tokens = GetTokensAtEdges(root, textSpan); + + // span must contain after and before spans at the both edges + + var triviaList = GetTriviaAtEdges(tokens, textSpan); + + var annotations = Enumerable.Range((int)TriviaLocation.BeforeBeginningOfSpan, TriviaLocationsCount) + .Cast<TriviaLocation>() + .ToDictionary(location => location, _ => new SyntaxAnnotation()); + + var map = CreateOldToNewTokensMap(tokens, annotations); + var rootWithAnnotation = ReplaceTokens(root, map.Keys, (o, n) => map[o]); + + return CreateResult(rootWithAnnotation, annotations, triviaList); + } + + private SyntaxNode ReplaceTokens( + SyntaxNode root, + IEnumerable<SyntaxToken> oldTokens, + Func<SyntaxToken, SyntaxToken, SyntaxToken> computeReplacementToken) + { + return root.ReplaceTokens(oldTokens, (o, n) => computeReplacementToken(o, n)); + } + + private ITriviaSavedResult CreateResult( + SyntaxNode root, + Dictionary<TriviaLocation, SyntaxAnnotation> annotations, + Dictionary<TriviaLocation, IEnumerable<SyntaxTrivia>> triviaList) + { + return new Result(root, _endOfLineKind, annotations, triviaList); + } + + private Dictionary<SyntaxToken, SyntaxToken> CreateOldToNewTokensMap( + Dictionary<TriviaLocation, SyntaxToken> tokens, + Dictionary<TriviaLocation, SyntaxAnnotation> annotations) + { + var token = default(SyntaxToken); + var map = new Dictionary<SyntaxToken, SyntaxToken>(); + var emptyList = SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + + token = map.GetOrAdd(tokens[TriviaLocation.BeforeBeginningOfSpan], _ => tokens[TriviaLocation.BeforeBeginningOfSpan]); + map[tokens[TriviaLocation.BeforeBeginningOfSpan]] = token.WithTrailingTrivia(emptyList).WithAdditionalAnnotations(annotations[TriviaLocation.BeforeBeginningOfSpan]); + + token = map.GetOrAdd(tokens[TriviaLocation.AfterBeginningOfSpan], _ => tokens[TriviaLocation.AfterBeginningOfSpan]); + map[tokens[TriviaLocation.AfterBeginningOfSpan]] = token.WithLeadingTrivia(emptyList).WithAdditionalAnnotations(annotations[TriviaLocation.AfterBeginningOfSpan]); + + token = map.GetOrAdd(tokens[TriviaLocation.BeforeEndOfSpan], _ => tokens[TriviaLocation.BeforeEndOfSpan]); + map[tokens[TriviaLocation.BeforeEndOfSpan]] = token.WithTrailingTrivia(emptyList).WithAdditionalAnnotations(annotations[TriviaLocation.BeforeEndOfSpan]); + + token = map.GetOrAdd(tokens[TriviaLocation.AfterEndOfSpan], _ => tokens[TriviaLocation.AfterEndOfSpan]); + map[tokens[TriviaLocation.AfterEndOfSpan]] = token.WithLeadingTrivia(emptyList).WithAdditionalAnnotations(annotations[TriviaLocation.AfterEndOfSpan]); + + return map; + } + + private Dictionary<TriviaLocation, IEnumerable<SyntaxTrivia>> GetTriviaAtEdges(Dictionary<TriviaLocation, SyntaxToken> tokens, TextSpan textSpan) + { + var triviaAtBeginning = SplitTrivia(tokens[TriviaLocation.BeforeBeginningOfSpan], tokens[TriviaLocation.AfterBeginningOfSpan], t => t.FullSpan.End <= textSpan.Start); + var triviaAtEnd = SplitTrivia(tokens[TriviaLocation.BeforeEndOfSpan], tokens[TriviaLocation.AfterEndOfSpan], t => t.FullSpan.Start < textSpan.End); + + var triviaList = new Dictionary<TriviaLocation, IEnumerable<SyntaxTrivia>>(); + triviaList[TriviaLocation.BeforeBeginningOfSpan] = triviaAtBeginning.Item1; + triviaList[TriviaLocation.AfterBeginningOfSpan] = triviaAtBeginning.Item2; + + triviaList[TriviaLocation.BeforeEndOfSpan] = triviaAtEnd.Item1; + triviaList[TriviaLocation.AfterEndOfSpan] = triviaAtEnd.Item2; + return triviaList; + } + + private Dictionary<TriviaLocation, SyntaxToken> GetTokensAtEdges(SyntaxNode root, TextSpan textSpan) + { + var tokens = new Dictionary<TriviaLocation, SyntaxToken>(); + tokens[TriviaLocation.AfterBeginningOfSpan] = root.FindTokenOnRightOfPosition(textSpan.Start, includeSkipped: false); + tokens[TriviaLocation.BeforeBeginningOfSpan] = tokens[TriviaLocation.AfterBeginningOfSpan].GetPreviousToken(includeZeroWidth: true); + tokens[TriviaLocation.BeforeEndOfSpan] = root.FindTokenOnLeftOfPosition(textSpan.End, includeSkipped: false); + tokens[TriviaLocation.AfterEndOfSpan] = tokens[TriviaLocation.BeforeEndOfSpan].GetNextToken(includeZeroWidth: true); + return tokens; + } + + private static Tuple<List<SyntaxTrivia>, List<SyntaxTrivia>> SplitTrivia( + SyntaxToken token1, + SyntaxToken token2, + Func<SyntaxTrivia, bool> conditionToLeftAtCallSite) + { + var triviaLeftAtCallSite = new List<SyntaxTrivia>(); + var triviaMovedToDefinition = new List<SyntaxTrivia>(); + + foreach (var trivia in token1.TrailingTrivia.Concat(token2.LeadingTrivia)) + { + if (conditionToLeftAtCallSite(trivia)) + { + triviaLeftAtCallSite.Add(trivia); + } + else + { + triviaMovedToDefinition.Add(trivia); + } + } + + return Tuple.Create(triviaLeftAtCallSite, triviaMovedToDefinition); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpExtractMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpExtractMethodService.cs new file mode 100644 index 0000000000..dbbbfd6639 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpExtractMethodService.cs @@ -0,0 +1,27 @@ +// 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; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class CSharpExtractMethodService : AbstractExtractMethodService<CSharpSelectionValidator, CSharpMethodExtractor, CSharpSelectionResult> + { + [ImportingConstructor] + public CSharpExtractMethodService() + { + } + + protected override CSharpSelectionValidator CreateSelectionValidator(SemanticDocument document, TextSpan textSpan, OptionSet options) + { + return new CSharpSelectionValidator(document, textSpan, options); + } + + protected override CSharpMethodExtractor CreateMethodExtractor(CSharpSelectionResult selectionResult) + { + return new CSharpMethodExtractor(selectionResult); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.Analyzer.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.Analyzer.cs new file mode 100644 index 0000000000..682014ea29 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.Analyzer.cs @@ -0,0 +1,136 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor : MethodExtractor + { + private class CSharpAnalyzer : Analyzer + { + private static readonly HashSet<int> s_nonNoisySyntaxKindSet = new HashSet<int>(new int[] { (int)SyntaxKind.WhitespaceTrivia, (int)SyntaxKind.EndOfLineTrivia }); + + public static Task<AnalyzerResult> AnalyzeAsync(SelectionResult selectionResult, CancellationToken cancellationToken) + { + var analyzer = new CSharpAnalyzer(selectionResult, cancellationToken); + return analyzer.AnalyzeAsync(); + } + + public CSharpAnalyzer(SelectionResult selectionResult, CancellationToken cancellationToken) : + base(selectionResult, cancellationToken) + { + } + + protected override VariableInfo CreateFromSymbol( + Compilation compilation, + ISymbol symbol, + ITypeSymbol type, + VariableStyle style, + bool variableDeclared) + { + return CreateFromSymbolCommon<LocalDeclarationStatementSyntax>(compilation, symbol, type, style, s_nonNoisySyntaxKindSet); + } + + protected override int GetIndexOfVariableInfoToUseAsReturnValue(IList<VariableInfo> variableInfo) + { + var numberOfOutParameters = 0; + var numberOfRefParameters = 0; + + int outSymbolIndex = -1; + int refSymbolIndex = -1; + + for (int i = 0; i < variableInfo.Count; i++) + { + var variable = variableInfo[i]; + + // there should be no one set as return value yet + //Contract.ThrowIfTrue(variable.UseAsReturnValue); + + if (!variable.CanBeUsedAsReturnValue) + { + continue; + } + + // check modifier + if (variable.ParameterModifier == ParameterBehavior.Ref) + { + numberOfRefParameters++; + refSymbolIndex = i; + } + else if (variable.ParameterModifier == ParameterBehavior.Out) + { + numberOfOutParameters++; + outSymbolIndex = i; + } + } + + // if there is only one "out" or "ref", that will be converted to return statement. + if (numberOfOutParameters == 1) + { + return outSymbolIndex; + } + + if (numberOfRefParameters == 1) + { + return refSymbolIndex; + } + + return -1; + } + + protected override ITypeSymbol GetRangeVariableType(SemanticModel model, IRangeVariableSymbol symbol) + { + var info = model.GetSpeculativeTypeInfo(this.SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression); + if (info.Type.IsErrorType()) + { + return null; + } + + return info.Type == null || info.Type.SpecialType == Microsoft.CodeAnalysis.SpecialType.System_Object + ? info.Type + : info.ConvertedType; + } + + protected override Tuple<SyntaxNode, SyntaxNode> GetFlowAnalysisNodeRange() + { + var csharpSelectionResult = this.SelectionResult as CSharpSelectionResult; + + var first = csharpSelectionResult.GetFirstStatement(); + var last = csharpSelectionResult.GetLastStatement(); + + // single statement case + if (first == last || + first.Span.Contains(last.Span)) + { + return new Tuple<SyntaxNode, SyntaxNode>(first, first); + } + + // multiple statement case + var firstUnderContainer = csharpSelectionResult.GetFirstStatementUnderContainer(); + var lastUnderContainer = csharpSelectionResult.GetLastStatementUnderContainer(); + return new Tuple<SyntaxNode, SyntaxNode>(firstUnderContainer, lastUnderContainer); + } + + protected override bool ContainsReturnStatementInSelectedCode(IEnumerable<SyntaxNode> jumpOutOfRegionStatements) + { + return jumpOutOfRegionStatements.Where(n => n is ReturnStatementSyntax).Any(); + } + + protected override bool ReadOnlyFieldAllowed() + { + var scope = this.SelectionResult.GetContainingScopeOf<ConstructorDeclarationSyntax>(); + return scope == null; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs new file mode 100644 index 0000000000..d171ae5cec --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs @@ -0,0 +1,392 @@ +// 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.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private abstract partial class CSharpCodeGenerator + { + private class CallSiteContainerRewriter : CSharpSyntaxRewriter + { + private readonly SyntaxNode _outmostCallSiteContainer; + private readonly IEnumerable<SyntaxNode> _statementsOrFieldToInsert; + private readonly HashSet<SyntaxAnnotation> _variableToRemoveMap; + private readonly SyntaxNode _firstStatementOrFieldToReplace; + private readonly SyntaxNode _lastStatementOrFieldToReplace; + + public CallSiteContainerRewriter( + SyntaxNode outmostCallSiteContainer, + HashSet<SyntaxAnnotation> variableToRemoveMap, + SyntaxNode firstStatementOrFieldToReplace, + SyntaxNode lastStatementOrFieldToReplace, + IEnumerable<SyntaxNode> statementsOrFieldToInsert) + { +// Contract.ThrowIfNull(outmostCallSiteContainer); +// Contract.ThrowIfNull(variableToRemoveMap); +// Contract.ThrowIfNull(firstStatementOrFieldToReplace); +// Contract.ThrowIfNull(lastStatementOrFieldToReplace); +// Contract.ThrowIfNull(statementsOrFieldToInsert); +// Contract.ThrowIfTrue(statementsOrFieldToInsert.IsEmpty()); + + _outmostCallSiteContainer = outmostCallSiteContainer; + + _variableToRemoveMap = variableToRemoveMap; + _statementsOrFieldToInsert = statementsOrFieldToInsert; + + _firstStatementOrFieldToReplace = firstStatementOrFieldToReplace; + _lastStatementOrFieldToReplace = lastStatementOrFieldToReplace; + + //Contract.ThrowIfFalse(_firstStatementOrFieldToReplace.Parent == _lastStatementOrFieldToReplace.Parent); + } + + public SyntaxNode Generate() + { + return Visit(_outmostCallSiteContainer); + } + + private SyntaxNode ContainerOfStatementsOrFieldToReplace + { + get { return _firstStatementOrFieldToReplace.Parent; } + } + + public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) + { + node = (LocalDeclarationStatementSyntax)base.VisitLocalDeclarationStatement(node); + + var list = new List<VariableDeclaratorSyntax>(); + var triviaList = new List<SyntaxTrivia>(); + + // go through each var decls in decl statement + foreach (var variable in node.Declaration.Variables) + { + if (_variableToRemoveMap.HasSyntaxAnnotation(variable)) + { + // if it had initialization, it shouldn't reach here. + //Contract.ThrowIfFalse(variable.Initializer == null); + + // we don't remove trivia around tokens we remove + triviaList.AddRange(variable.GetLeadingTrivia()); + triviaList.AddRange(variable.GetTrailingTrivia()); + continue; + } + + if (triviaList.Count > 0) + { + list.Add(variable.WithPrependedLeadingTrivia(triviaList)); + triviaList.Clear(); + continue; + } + + list.Add(variable); + } + + if (list.Count == 0) + { + // nothing has survived. remove this from the list + if (triviaList.Count == 0) + { + return null; + } + + // well, there are trivia associated with the node. + // we can't just delete the node since then, we will lose + // the trivia. unfortunately, it is not easy to attach the trivia + // to next token. for now, create an empty statement and associate the + // trivia to the statement + + // TODO : think about a way to move the trivia to next token. + return SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxFactory.TriviaList(triviaList), SyntaxKind.SemicolonToken, SyntaxTriviaList.Create(SyntaxFactory.ElasticMarker))); + } + + if (list.Count == node.Declaration.Variables.Count) + { + // nothing has changed, return as it is + return node; + } + + // TODO : fix how it manipulate trivia later + + // if there is left over syntax trivia, it will be attached to leading trivia + // of semicolon + return + SyntaxFactory.LocalDeclarationStatement( + node.Modifiers, + SyntaxFactory.VariableDeclaration( + node.Declaration.Type, + SyntaxFactory.SeparatedList(list)), + node.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); + } + + // for every kind of extract methods + public override SyntaxNode VisitBlock(BlockSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + // make sure we visit nodes under the block + return base.VisitBlock(node); + } + + return node.WithStatements(VisitList(ReplaceStatements(node.Statements)).ToSyntaxList()); + } + + public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + // make sure we visit nodes under the switch section + return base.VisitSwitchSection(node); + } + + return node.WithStatements(VisitList(ReplaceStatements(node.Statements)).ToSyntaxList()); + } + + // only for single statement or expression + public override SyntaxNode VisitLabeledStatement(LabeledStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitLabeledStatement(node); + } + + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitElseClause(ElseClauseSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitElseClause(node); + } + + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitIfStatement(IfStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitIfStatement(node); + } + + return node.WithCondition(VisitNode(node.Condition)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)) + .WithElse(VisitNode(node.Else)); + } + + public override SyntaxNode VisitLockStatement(LockStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitLockStatement(node); + } + + return node.WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitFixedStatement(FixedStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitFixedStatement(node); + } + + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitUsingStatement(UsingStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitUsingStatement(node); + } + + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitForEachStatement(ForEachStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitForEachStatement(node); + } + + return node.WithExpression(VisitNode(node.Expression)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitForStatement(ForStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitForStatement(node); + } + + return node.WithDeclaration(VisitNode(node.Declaration)) + .WithInitializers(VisitList(node.Initializers)) + .WithCondition(VisitNode(node.Condition)) + .WithIncrementors(VisitList(node.Incrementors)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitDoStatement(DoStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitDoStatement(node); + } + + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)) + .WithCondition(VisitNode(node.Condition)); + } + + public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitWhileStatement(node); + } + + return node.WithCondition(VisitNode(node.Condition)) + .WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + private TNode VisitNode<TNode>(TNode node) where TNode : SyntaxNode + { + return (TNode)Visit(node); + } + + private StatementSyntax ReplaceStatementIfNeeded(StatementSyntax statement) + { + //Contract.ThrowIfNull(statement); + + // if all three same + if ((statement != _firstStatementOrFieldToReplace) || (_firstStatementOrFieldToReplace != _lastStatementOrFieldToReplace)) + { + return statement; + } + + // replace one statement with another + if (_statementsOrFieldToInsert.Count() == 1) + { + return _statementsOrFieldToInsert.Cast<StatementSyntax>().Single(); + } + + // replace one statement with multiple statements (see bug # 6310) + return SyntaxFactory.Block(SyntaxFactory.List(_statementsOrFieldToInsert.Cast<StatementSyntax>())); + } + + private SyntaxList<StatementSyntax> ReplaceStatements(SyntaxList<StatementSyntax> statements) + { + // okay, this visit contains the statement + var newStatements = new List<StatementSyntax>(statements); + + var firstStatementIndex = newStatements.FindIndex(s => s == _firstStatementOrFieldToReplace); + //Contract.ThrowIfFalse(firstStatementIndex >= 0); + + var lastStatementIndex = newStatements.FindIndex(s => s == _lastStatementOrFieldToReplace); + //Contract.ThrowIfFalse(lastStatementIndex >= 0); + + //Contract.ThrowIfFalse(firstStatementIndex <= lastStatementIndex); + + // remove statement that must be removed + newStatements.RemoveRange(firstStatementIndex, lastStatementIndex - firstStatementIndex + 1); + + // add new statements to replace + newStatements.InsertRange(firstStatementIndex, _statementsOrFieldToInsert.Cast<StatementSyntax>()); + + return newStatements.ToSyntaxList(); + } + + private SyntaxList<MemberDeclarationSyntax> ReplaceMembers(SyntaxList<MemberDeclarationSyntax> members, bool global) + { + // okay, this visit contains the statement + var newMembers = new List<MemberDeclarationSyntax>(members); + + var firstMemberIndex = newMembers.FindIndex(s => s == (global ? _firstStatementOrFieldToReplace.Parent : _firstStatementOrFieldToReplace)); + //Contract.ThrowIfFalse(firstMemberIndex >= 0); + + var lastMemberIndex = newMembers.FindIndex(s => s == (global ? _lastStatementOrFieldToReplace.Parent : _lastStatementOrFieldToReplace)); + //Contract.ThrowIfFalse(lastMemberIndex >= 0); + + //Contract.ThrowIfFalse(firstMemberIndex <= lastMemberIndex); + + // remove statement that must be removed + newMembers.RemoveRange(firstMemberIndex, lastMemberIndex - firstMemberIndex + 1); + + // add new statements to replace + newMembers.InsertRange(firstMemberIndex, + _statementsOrFieldToInsert.Select(s => global ? SyntaxFactory.GlobalStatement((StatementSyntax)s) : (MemberDeclarationSyntax)s)); + + return newMembers.ToSyntaxList(); + } + + public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitGlobalStatement(node); + } + + return node.WithStatement(ReplaceStatementIfNeeded(node.Statement)); + } + + public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitConstructorDeclaration(node); + } + + //Contract.ThrowIfFalse(_firstStatementOrFieldToReplace == _lastStatementOrFieldToReplace); + return node.WithInitializer((ConstructorInitializerSyntax)_statementsOrFieldToInsert.Single()); + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitClassDeclaration(node); + } + + var newMembers = VisitList(ReplaceMembers(node.Members, global: false)); + return node.WithMembers(newMembers); + } + + public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace) + { + return base.VisitStructDeclaration(node); + } + + var newMembers = VisitList(ReplaceMembers(node.Members, global: false)); + return node.WithMembers(newMembers); + } + + public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) + { + if (node != this.ContainerOfStatementsOrFieldToReplace.Parent) + { + // make sure we visit nodes under the block + return base.VisitCompilationUnit(node); + } + + var newMembers = VisitList(ReplaceMembers(node.Members, global: true)); + return node.WithMembers(newMembers); + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs new file mode 100644 index 0000000000..0bbe81fccf --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs @@ -0,0 +1,242 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private partial class CSharpCodeGenerator + { + private class ExpressionCodeGenerator : CSharpCodeGenerator + { + public ExpressionCodeGenerator( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult) : + base(insertionPoint, selectionResult, analyzerResult) + { + } + + public static bool IsExtractMethodOnExpression(SelectionResult code) + { + return code.SelectionInExpression; + } + + protected override SyntaxToken CreateMethodName() + { + var methodName = "NewMethod"; + var containingScope = this.CSharpSelectionResult.GetContainingScope(); + + methodName = GetMethodNameBasedOnExpression(methodName, containingScope); + + var semanticModel = this.SemanticDocument.SemanticModel; + var nameGenerator = new UniqueNameGenerator(semanticModel); + return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, methodName)); + } + + private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression) + { + if (expression.Parent != null && + expression.Parent.Kind() == SyntaxKind.EqualsValueClause && + expression.Parent.Parent != null && + expression.Parent.Parent.Kind() == SyntaxKind.VariableDeclarator) + { + var name = ((VariableDeclaratorSyntax)expression.Parent.Parent).Identifier.ValueText; + return (name != null && name.Length > 0) ? MakeMethodName("Get", name) : methodName; + } + + if (expression is MemberAccessExpressionSyntax) + { + expression = ((MemberAccessExpressionSyntax)expression).Name; + } + + if (expression is NameSyntax) + { + SimpleNameSyntax unqualifiedName; + + switch (expression.Kind()) + { + case SyntaxKind.IdentifierName: + case SyntaxKind.GenericName: + unqualifiedName = (SimpleNameSyntax)expression; + break; + case SyntaxKind.QualifiedName: + unqualifiedName = ((QualifiedNameSyntax)expression).Right; + break; + case SyntaxKind.AliasQualifiedName: + unqualifiedName = ((AliasQualifiedNameSyntax)expression).Name; + break; + default: + throw new System.NotSupportedException("Unexpected name kind: " + expression.Kind().ToString()); + } + + var unqualifiedNameIdentifierValueText = unqualifiedName.Identifier.ValueText; + return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ? MakeMethodName("Get", unqualifiedNameIdentifierValueText) : methodName; + } + + return methodName; + } + + protected override IEnumerable<StatementSyntax> GetInitialStatementsForMethodDefinitions() + { + //Contract.ThrowIfFalse(IsExtractMethodOnExpression(this.CSharpSelectionResult)); + + ExpressionSyntax expression = null; + + // special case for array initializer + var returnType = (ITypeSymbol)this.AnalyzerResult.ReturnType; + var containingScope = this.CSharpSelectionResult.GetContainingScope(); + + if (returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax) + { + var typeSyntax = returnType.GenerateTypeSyntax(); + + expression = SyntaxFactory.ArrayCreationExpression(typeSyntax as ArrayTypeSyntax, containingScope as InitializerExpressionSyntax); + } + else + { + expression = containingScope as ExpressionSyntax; + } + + if (this.AnalyzerResult.HasReturnType) + { + return SpecializedCollections.SingletonEnumerable<StatementSyntax>( + SyntaxFactory.ReturnStatement( + WrapInCheckedExpressionIfNeeded(expression))); + } + else + { + return SpecializedCollections.SingletonEnumerable<StatementSyntax>( + SyntaxFactory.ExpressionStatement( + WrapInCheckedExpressionIfNeeded(expression))); + } + } + + private ExpressionSyntax WrapInCheckedExpressionIfNeeded(ExpressionSyntax expression) + { + var kind = this.CSharpSelectionResult.UnderCheckedExpressionContext(); + if (kind == SyntaxKind.None) + { + return expression; + } + + return SyntaxFactory.CheckedExpression(kind, expression); + } + + protected override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) + { + var callSiteContainer = GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken); + if (callSiteContainer != null) + { + return callSiteContainer; + } + else + { + return GetCallSiteContainerFromExpression(); + } + } + + private SyntaxNode GetCallSiteContainerFromExpression() + { + var container = this.CSharpSelectionResult.GetInnermostStatementContainer(); + +// Contract.ThrowIfNull(container); +// Contract.ThrowIfFalse(container.IsStatementContainerNode() || +// container is TypeDeclarationSyntax || +// container is ConstructorDeclarationSyntax || +// container is CompilationUnitSyntax); + + return container; + } + + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + { + var scope = (SyntaxNode)this.CSharpSelectionResult.GetContainingScopeOf<StatementSyntax>(); + if (scope == null) + { + scope = this.CSharpSelectionResult.GetContainingScopeOf<FieldDeclarationSyntax>(); + } + + if (scope == null) + { + scope = this.CSharpSelectionResult.GetContainingScopeOf<ConstructorInitializerSyntax>(); + } + + return scope; + } + + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + { + return GetFirstStatementOrInitializerSelectedAtCallSite(); + } + + protected override async Task<SyntaxNode> GetStatementOrInitializerContainingInvocationToExtractedMethodAsync( + SyntaxAnnotation callSiteAnnotation, CancellationToken cancellationToken) + { + var enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite(); + var callSignature = CreateCallSignature().WithAdditionalAnnotations(callSiteAnnotation); + var invocation = callSignature.IsKind(SyntaxKind.AwaitExpression) ? ((AwaitExpressionSyntax)callSignature).Expression : callSignature; + + var sourceNode = this.CSharpSelectionResult.GetContainingScope(); +// Contract.ThrowIfTrue( +// sourceNode.Parent is MemberAccessExpressionSyntax && ((MemberAccessExpressionSyntax)sourceNode.Parent).Name == sourceNode, +// "invalid scope. given scope is not an expression"); + + // To lower the chances that replacing sourceNode with callSignature will break the user's + // code, we make the enclosing statement semantically explicit. This ends up being a little + // bit more work because we need to annotate the sourceNode so that we can get back to it + // after rewriting the enclosing statement. + var updatedDocument = this.SemanticDocument.Document; + var sourceNodeAnnotation = new SyntaxAnnotation(); + var enclosingStatementAnnotation = new SyntaxAnnotation(); + var newEnclosingStatement = enclosingStatement + .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation)) + .WithAdditionalAnnotations(enclosingStatementAnnotation); + + updatedDocument = await updatedDocument.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(false); + + var updatedRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + newEnclosingStatement = updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode(); + + // because of the complexifiction we cannot guarantee that there is only one annotation. + // however complexification of names is prepended, so the last annotation should be the original one. + sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode(); + + // we want to replace the old identifier with a invocation expression, but because of MakeExplicit we might have + // a member access now instead of the identifer. So more syntax fiddling is needed. + if (sourceNode.Parent.Kind() == SyntaxKind.SimpleMemberAccessExpression && + ((ExpressionSyntax)sourceNode).IsRightSideOfDot()) + { + var explicitMemberAccess = (MemberAccessExpressionSyntax)sourceNode.Parent; + var replacementMemberAccess = explicitMemberAccess.CopyAnnotationsTo( + SyntaxFactory.MemberAccessExpression( + sourceNode.Parent.Kind(), + explicitMemberAccess.Expression, + (SimpleNameSyntax)((InvocationExpressionSyntax)invocation).Expression)); + var newInvocation = SyntaxFactory.InvocationExpression( + replacementMemberAccess, + ((InvocationExpressionSyntax)invocation).ArgumentList); + + var newCallSignature = callSignature != invocation ? + callSignature.ReplaceNode(invocation, newInvocation) : invocation.CopyAnnotationsTo(newInvocation); + + sourceNode = sourceNode.Parent; + callSignature = newCallSignature; + } + + return newEnclosingStatement.ReplaceNode(sourceNode, callSignature); + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs new file mode 100644 index 0000000000..205d488fc5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs @@ -0,0 +1,139 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private partial class CSharpCodeGenerator + { + public class MultipleStatementsCodeGenerator : CSharpCodeGenerator + { + public MultipleStatementsCodeGenerator( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult) : + base(insertionPoint, selectionResult, analyzerResult) + { + } + + public static bool IsExtractMethodOnMultipleStatements(SelectionResult code) + { + var result = (CSharpSelectionResult)code; + var first = result.GetFirstStatement(); + var last = result.GetLastStatement(); + + if (first != last) + { + var firstUnderContainer = result.GetFirstStatementUnderContainer(); + var lastUnderContainer = result.GetLastStatementUnderContainer(); + //Contract.ThrowIfFalse(firstUnderContainer.Parent == lastUnderContainer.Parent); + return true; + } + + return false; + } + + protected override SyntaxToken CreateMethodName() + { + // change this to more smarter one. + var semanticModel = this.SemanticDocument.SemanticModel; + var nameGenerator = new UniqueNameGenerator(semanticModel); + var scope = this.CSharpSelectionResult.GetContainingScope(); + return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(scope, "NewMethod")); + } + + protected override IEnumerable<StatementSyntax> GetInitialStatementsForMethodDefinitions() + { + var firstSeen = false; + var firstStatementUnderContainer = this.CSharpSelectionResult.GetFirstStatementUnderContainer(); + var lastStatementUnderContainer = this.CSharpSelectionResult.GetLastStatementUnderContainer(); + + var list = new List<StatementSyntax>(); + foreach (var statement in GetStatementsFromContainer(firstStatementUnderContainer.Parent)) + { + // reset first seen + if (!firstSeen) + { + firstSeen = statement == firstStatementUnderContainer; + } + + // continue until we see the first statement + if (!firstSeen) + { + continue; + } + + list.Add(statement); + + // exit if we see last statement + if (statement == lastStatementUnderContainer) + { + break; + } + } + + return list; + } + + protected override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) + { + var callSiteContainer = GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken); + if (callSiteContainer != null) + { + return callSiteContainer; + } + else + { + var firstStatement = this.CSharpSelectionResult.GetFirstStatementUnderContainer(); + return firstStatement.Parent; + } + } + + private SyntaxList<StatementSyntax> GetStatementsFromContainer(SyntaxNode node) + { +// Contract.ThrowIfNull(node); +// Contract.ThrowIfFalse(node.IsStatementContainerNode()); + + var blockNode = node as BlockSyntax; + if (blockNode != null) + { + return blockNode.Statements; + } + + var switchSecionNode = node as SwitchSectionSyntax; + if (switchSecionNode != null) + { + return switchSecionNode.Statements; + } + + return new SyntaxList<StatementSyntax> ();//Contract.FailWithReturn<SyntaxList<StatementSyntax>>("unknown statements container!"); + } + + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + { + return this.CSharpSelectionResult.GetFirstStatementUnderContainer(); + } + + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + { + return this.CSharpSelectionResult.GetLastStatementUnderContainer(); + } + + protected override Task<SyntaxNode> GetStatementOrInitializerContainingInvocationToExtractedMethodAsync( + SyntaxAnnotation callSiteAnnotation, CancellationToken cancellationToken) + { + var statement = GetStatementContainingInvocationToExtractedMethodWorker(); + return Task.FromResult<SyntaxNode>(statement.WithAdditionalAnnotations(callSiteAnnotation)); + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.cs new file mode 100644 index 0000000000..de7e6ca9d1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.SingleStatementCodeGenerator.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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private partial class CSharpCodeGenerator + { + public class SingleStatementCodeGenerator : CSharpCodeGenerator + { + public SingleStatementCodeGenerator( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult) : + base(insertionPoint, selectionResult, analyzerResult) + { + } + + public static bool IsExtractMethodOnSingleStatement(SelectionResult code) + { + var result = (CSharpSelectionResult)code; + var firstStatement = result.GetFirstStatement(); + var lastStatement = result.GetLastStatement(); + + return firstStatement == lastStatement || firstStatement.Span.Contains(lastStatement.Span); + } + + protected override SyntaxToken CreateMethodName() + { + // change this to more smarter one. + var semanticModel = this.SemanticDocument.SemanticModel; + var nameGenerator = new UniqueNameGenerator(semanticModel); + var scope = this.CSharpSelectionResult.GetContainingScope(); + return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(scope, "NewMethod")); + } + + protected override IEnumerable<StatementSyntax> GetInitialStatementsForMethodDefinitions() + { + // Contract.ThrowIfFalse(IsExtractMethodOnSingleStatement(this.CSharpSelectionResult)); + + return SpecializedCollections.SingletonEnumerable<StatementSyntax>(this.CSharpSelectionResult.GetFirstStatement()); + } + + protected override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken) + { + var callSiteContainer = GetCallSiteContainerFromOutermostMoveInVariable(cancellationToken); + if (callSiteContainer != null) + { + return callSiteContainer; + } + else + { + var firstStatement = this.CSharpSelectionResult.GetFirstStatement(); + return firstStatement.Parent; + } + } + + protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() + { + return this.CSharpSelectionResult.GetFirstStatement(); + } + + protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite() + { + // it is a single statement case. either first statement is same as last statement or + // last statement belongs (embedded statement) to the first statement. + return this.CSharpSelectionResult.GetFirstStatement(); + } + + protected override Task<SyntaxNode> GetStatementOrInitializerContainingInvocationToExtractedMethodAsync( + SyntaxAnnotation callSiteAnnotation, CancellationToken cancellationToken) + { + var statement = GetStatementContainingInvocationToExtractedMethodWorker(); + return Task.FromResult<SyntaxNode>(statement.WithAdditionalAnnotations(callSiteAnnotation)); + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.cs new file mode 100644 index 0000000000..32bab2eebb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -0,0 +1,583 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private abstract partial class CSharpCodeGenerator : CodeGenerator<StatementSyntax, ExpressionSyntax, SyntaxNode> + { + private SyntaxToken _methodName; + + public static async Task<GeneratedCode> GenerateAsync( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult, + CancellationToken cancellationToken) + { + var codeGenerator = Create(insertionPoint, selectionResult, analyzerResult); + return await codeGenerator.GenerateAsync(cancellationToken).ConfigureAwait(false); + } + + private static CSharpCodeGenerator Create( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult) + { + if (ExpressionCodeGenerator.IsExtractMethodOnExpression(selectionResult)) + { + return new ExpressionCodeGenerator(insertionPoint, selectionResult, analyzerResult); + } + + if (SingleStatementCodeGenerator.IsExtractMethodOnSingleStatement(selectionResult)) + { + return new SingleStatementCodeGenerator(insertionPoint, selectionResult, analyzerResult); + } + + if (MultipleStatementsCodeGenerator.IsExtractMethodOnMultipleStatements(selectionResult)) + { + return new MultipleStatementsCodeGenerator(insertionPoint, selectionResult, analyzerResult); + } + + return null;//Contract.FailWithReturn<CSharpCodeGenerator>("Unknown selection"); + } + + protected CSharpCodeGenerator( + InsertionPoint insertionPoint, + SelectionResult selectionResult, + AnalyzerResult analyzerResult) : + base(insertionPoint, selectionResult, analyzerResult) + { + //Contract.ThrowIfFalse(this.SemanticDocument == selectionResult.SemanticDocument); + + var nameToken = (SyntaxToken)CreateMethodName(); + _methodName = nameToken.WithAdditionalAnnotations(this.MethodNameAnnotation); + } + + private CSharpSelectionResult CSharpSelectionResult + { + get { return (CSharpSelectionResult)this.SelectionResult; } + } + + protected override SyntaxNode GetPreviousMember(SemanticDocument document) + { + var node = this.InsertionPoint.With(document).GetContext(); + return (node.Parent is GlobalStatementSyntax) ? node.Parent : node; + } + + protected override OperationStatus<IMethodSymbol> GenerateMethodDefinition(CancellationToken cancellationToken) + { + var result = CreateMethodBody(cancellationToken); + + var methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes: SpecializedCollections.EmptyList<AttributeData>(), + accessibility: Accessibility.Private, + modifiers: CreateMethodModifiers(), + returnType: this.AnalyzerResult.ReturnType, + explicitInterfaceSymbol: null, + name: _methodName.ToString(), + typeParameters: CreateMethodTypeParameters(cancellationToken), + parameters: CreateMethodParameters(), + statements: result.Data); + + return result.With( + this.MethodDefinitionAnnotation.AddAnnotationToSymbol( + Formatter.Annotation.AddAnnotationToSymbol(methodSymbol))); + } + + protected override async Task<SyntaxNode> GenerateBodyForCallSiteContainerAsync(CancellationToken cancellationToken) + { + var container = this.GetOutermostCallSiteContainerToProcess(cancellationToken); + var variableMapToRemove = CreateVariableDeclarationToRemoveMap( + this.AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), cancellationToken); + var firstStatementToRemove = GetFirstStatementOrInitializerSelectedAtCallSite(); + var lastStatementToRemove = GetLastStatementOrInitializerSelectedAtCallSite(); + + //Contract.ThrowIfFalse(firstStatementToRemove.Parent == lastStatementToRemove.Parent); + + var statementsToInsert = await CreateStatementsOrInitializerToInsertAtCallSiteAsync(cancellationToken).ConfigureAwait(false); + + var callSiteGenerator = + new CallSiteContainerRewriter( + container, + variableMapToRemove, + firstStatementToRemove, + lastStatementToRemove, + statementsToInsert); + + return container.CopyAnnotationsTo(callSiteGenerator.Generate()).WithAdditionalAnnotations(Formatter.Annotation); + } + + private async Task<IEnumerable<SyntaxNode>> CreateStatementsOrInitializerToInsertAtCallSiteAsync(CancellationToken cancellationToken) + { + var selectedNode = this.GetFirstStatementOrInitializerSelectedAtCallSite(); + + // field initializer and constructor initializer case + if (selectedNode is ConstructorInitializerSyntax || + selectedNode is FieldDeclarationSyntax) + { + var statement = await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(this.CallSiteAnnotation, cancellationToken).ConfigureAwait(false); + return SpecializedCollections.SingletonEnumerable(statement); + } + + // regular case + var semanticModel = this.SemanticDocument.SemanticModel; + var context = this.InsertionPoint.GetContext(); + var postProcessor = new PostProcessor(semanticModel, context.SpanStart); + var statements = SpecializedCollections.EmptyEnumerable<StatementSyntax>(); + + statements = AddSplitOrMoveDeclarationOutStatementsToCallSite(statements, cancellationToken); + statements = postProcessor.MergeDeclarationStatements(statements); + statements = AddAssignmentStatementToCallSite(statements, cancellationToken); + statements = await AddInvocationAtCallSiteAsync(statements, cancellationToken).ConfigureAwait(false); + statements = AddReturnIfUnreachable(statements, cancellationToken); + + return statements; + } + + private SimpleNameSyntax CreateMethodNameForInvocation() + { + return this.AnalyzerResult.MethodTypeParametersInDeclaration.Count == 0 + ? (SimpleNameSyntax)SyntaxFactory.IdentifierName(_methodName) + : SyntaxFactory.GenericName(_methodName, SyntaxFactory.TypeArgumentList(CreateMethodCallTypeVariables())); + } + + private SeparatedSyntaxList<TypeSyntax> CreateMethodCallTypeVariables() + { + //Contract.ThrowIfTrue(this.AnalyzerResult.MethodTypeParametersInDeclaration.Count == 0); + + // propagate any type variable used in extracted code + var typeVariables = new List<TypeSyntax>(); + foreach (var methodTypeParameter in this.AnalyzerResult.MethodTypeParametersInDeclaration) + { + typeVariables.Add(SyntaxFactory.ParseTypeName(methodTypeParameter.Name)); + } + + return SyntaxFactory.SeparatedList(typeVariables); + } + + protected SyntaxNode GetCallSiteContainerFromOutermostMoveInVariable(CancellationToken cancellationToken) + { + var outmostVariable = GetOutermostVariableToMoveIntoMethodDefinition(cancellationToken); + if (outmostVariable == null) + { + return null; + } + + var idToken = outmostVariable.GetIdentifierTokenAtDeclaration(this.SemanticDocument); + var declStatement = idToken.GetAncestor<LocalDeclarationStatementSyntax>(); +// Contract.ThrowIfNull(declStatement); +// Contract.ThrowIfFalse(declStatement.Parent.IsStatementContainerNode()); + + return declStatement.Parent; + } + + private DeclarationModifiers CreateMethodModifiers() + { + var isUnsafe = this.CSharpSelectionResult.ShouldPutUnsafeModifier(); + var isAsync = this.CSharpSelectionResult.ShouldPutAsyncModifier(); + var result = DeclarationModifiers.None; + if (isUnsafe) + result = result.WithIsUnsafe (true); + if (isAsync) + result = result.WithAsync (true); + if (!this.AnalyzerResult.UseInstanceMember) + result = result.WithIsStatic (true); + return result; + } + + private static SyntaxKind GetParameterRefSyntaxKind(ParameterBehavior parameterBehavior) + { + return parameterBehavior == ParameterBehavior.Ref ? + SyntaxKind.RefKeyword : + parameterBehavior == ParameterBehavior.Out ? + SyntaxKind.OutKeyword : SyntaxKind.None; + } + + private OperationStatus<List<SyntaxNode>> CreateMethodBody(CancellationToken cancellationToken) + { + var statements = GetInitialStatementsForMethodDefinitions(); + + statements = SplitOrMoveDeclarationIntoMethodDefinition(statements, cancellationToken); + statements = MoveDeclarationOutFromMethodDefinition(statements, cancellationToken); + statements = AppendReturnStatementIfNeeded(statements); + statements = CleanupCode(statements); + + // set output so that we can use it in negative preview + var wrapped = WrapInCheckStatementIfNeeded(statements); + return CheckActiveStatements(statements).With(wrapped.ToList<SyntaxNode>()); + } + + private IEnumerable<StatementSyntax> WrapInCheckStatementIfNeeded(IEnumerable<StatementSyntax> statements) + { + var kind = this.CSharpSelectionResult.UnderCheckedStatementContext(); + if (kind == SyntaxKind.None) + { + return statements; + } + + if (statements.Skip(1).Any()) + { + return SpecializedCollections.SingletonEnumerable<StatementSyntax>(SyntaxFactory.CheckedStatement(kind, SyntaxFactory.Block(statements))); + } + + var block = statements.Single() as BlockSyntax; + if (block != null) + { + return SpecializedCollections.SingletonEnumerable<StatementSyntax>(SyntaxFactory.CheckedStatement(kind, block)); + } + + return SpecializedCollections.SingletonEnumerable<StatementSyntax>(SyntaxFactory.CheckedStatement(kind, SyntaxFactory.Block(statements))); + } + + private IEnumerable<StatementSyntax> CleanupCode(IEnumerable<StatementSyntax> statements) + { + var semanticModel = this.SemanticDocument.SemanticModel; + var context = this.InsertionPoint.GetContext(); + var postProcessor = new PostProcessor(semanticModel, context.SpanStart); + + statements = postProcessor.RemoveRedundantBlock(statements); + statements = postProcessor.RemoveDeclarationAssignmentPattern(statements); + statements = postProcessor.RemoveInitializedDeclarationAndReturnPattern(statements); + + return statements; + } + + private OperationStatus CheckActiveStatements(IEnumerable<StatementSyntax> statements) + { + var count = statements.Count(); + if (count == 0) + { + return OperationStatus.NoActiveStatement; + } + + if (count == 1) + { + var returnStatement = statements.Single() as ReturnStatementSyntax; + if (returnStatement != null && returnStatement.Expression == null) + { + return OperationStatus.NoActiveStatement; + } + } + + foreach (var statement in statements) + { + var declStatement = statement as LocalDeclarationStatementSyntax; + if (declStatement == null) + { + // found one + return OperationStatus.Succeeded; + } + + foreach (var variable in declStatement.Declaration.Variables) + { + if (variable.Initializer != null) + { + // found one + return OperationStatus.Succeeded; + } + } + } + + return OperationStatus.NoActiveStatement; + } + + private IEnumerable<StatementSyntax> MoveDeclarationOutFromMethodDefinition( + IEnumerable<StatementSyntax> statements, CancellationToken cancellationToken) + { + var variableToRemoveMap = CreateVariableDeclarationToRemoveMap( + this.AnalyzerResult.GetVariablesToMoveOutToCallSiteOrDelete(cancellationToken), cancellationToken); + + foreach (var statement in statements) + { + var declarationStatement = statement as LocalDeclarationStatementSyntax; + if (declarationStatement == null) + { + // if given statement is not decl statement, do nothing. + yield return statement; + continue; + } + + var expressionStatements = new List<StatementSyntax>(); + var list = new List<VariableDeclaratorSyntax>(); + var triviaList = new List<SyntaxTrivia>(); + + // When we modify the declaration to an initialization we have to preserve the leading trivia + var firstVariableToAttachTrivia = true; + + // go through each var decls in decl statement, and create new assignment if + // variable is initialized at decl. + foreach (var variableDeclaration in declarationStatement.Declaration.Variables) + { + if (variableToRemoveMap.HasSyntaxAnnotation(variableDeclaration)) + { + if (variableDeclaration.Initializer != null) + { + SyntaxToken identifier = ApplyTriviaFromDeclarationToAssignmentIdentifier(declarationStatement, firstVariableToAttachTrivia, variableDeclaration); + + // move comments with the variable here + expressionStatements.Add(CreateAssignmentExpressionStatement(identifier, variableDeclaration.Initializer.Value)); + } + else + { + // we don't remove trivia around tokens we remove + triviaList.AddRange(variableDeclaration.GetLeadingTrivia()); + triviaList.AddRange(variableDeclaration.GetTrailingTrivia()); + } + + firstVariableToAttachTrivia = false; + continue; + } + + // Prepend the trivia from the declarations without initialization to the next persisting variable declaration + if (triviaList.Count > 0) + { + list.Add(variableDeclaration.WithPrependedLeadingTrivia(triviaList)); + triviaList.Clear(); + firstVariableToAttachTrivia = false; + continue; + } + + firstVariableToAttachTrivia = false; + list.Add(variableDeclaration); + } + + if (list.Count == 0 && triviaList.Count > 0) + { + // well, there are trivia associated with the node. + // we can't just delete the node since then, we will lose + // the trivia. unfortunately, it is not easy to attach the trivia + // to next token. for now, create an empty statement and associate the + // trivia to the statement + + // TODO : think about a way to trivia attached to next token + yield return SyntaxFactory.EmptyStatement(SyntaxFactory.Token(SyntaxFactory.TriviaList(triviaList), SyntaxKind.SemicolonToken, SyntaxTriviaList.Create(SyntaxFactory.ElasticMarker))); + triviaList.Clear(); + } + + // return survived var decls + if (list.Count > 0) + { + yield return + SyntaxFactory.LocalDeclarationStatement( + declarationStatement.Modifiers, + SyntaxFactory.VariableDeclaration( + declarationStatement.Declaration.Type, + SyntaxFactory.SeparatedList(list)), + declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); + triviaList.Clear(); + } + + // return any expression statement if there was any + foreach (var expressionStatement in expressionStatements) + { + yield return expressionStatement; + } + } + } + + private static SyntaxToken ApplyTriviaFromDeclarationToAssignmentIdentifier(LocalDeclarationStatementSyntax declarationStatement, bool firstVariableToAttachTrivia, VariableDeclaratorSyntax variable) + { + var identifier = variable.Identifier; + var typeSyntax = declarationStatement.Declaration.Type; + if (firstVariableToAttachTrivia && typeSyntax != null) + { + var identifierLeadingTrivia = new SyntaxTriviaList(); + + if (typeSyntax.HasLeadingTrivia) + { + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(typeSyntax.GetLeadingTrivia()); + } + + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(identifier.LeadingTrivia); + identifier = identifier.WithLeadingTrivia(identifierLeadingTrivia); + } + + return identifier; + } + + private static SyntaxToken GetIdentifierTokenAndTrivia(SyntaxToken identifier, TypeSyntax typeSyntax) + { + if (typeSyntax != null) + { + var identifierLeadingTrivia = new SyntaxTriviaList(); + var identifierTrailingTrivia = new SyntaxTriviaList(); + if (typeSyntax.HasLeadingTrivia) + { + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(typeSyntax.GetLeadingTrivia()); + } + + if (typeSyntax.HasTrailingTrivia) + { + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(typeSyntax.GetTrailingTrivia()); + } + + identifierLeadingTrivia = identifierLeadingTrivia.AddRange(identifier.LeadingTrivia); + identifierTrailingTrivia = identifierTrailingTrivia.AddRange(identifier.TrailingTrivia); + identifier = identifier.WithLeadingTrivia(identifierLeadingTrivia) + .WithTrailingTrivia(identifierTrailingTrivia); + } + + return identifier; + } + + private IEnumerable<StatementSyntax> SplitOrMoveDeclarationIntoMethodDefinition( + IEnumerable<StatementSyntax> statements, + CancellationToken cancellationToken) + { + var semanticModel = this.SemanticDocument.SemanticModel; + var context = this.InsertionPoint.GetContext(); + var postProcessor = new PostProcessor(semanticModel, context.SpanStart); + + var declStatements = CreateDeclarationStatements(AnalyzerResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), cancellationToken); + declStatements = postProcessor.MergeDeclarationStatements(declStatements); + + return declStatements.Concat(statements); + } + + private ExpressionSyntax CreateAssignmentExpression(SyntaxToken identifier, ExpressionSyntax rvalue) + { + return SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName(identifier), + rvalue); + } + + protected override bool LastStatementOrHasReturnStatementInReturnableConstruct() + { + var lastStatement = this.GetLastStatementOrInitializerSelectedAtCallSite(); + var container = lastStatement.GetAncestorsOrThis<SyntaxNode>().FirstOrDefault(n => n.IsReturnableConstruct()); + if (container == null) + { + // case such as field initializer + return false; + } + + var blockBody = container.GetBlockBody(); + if (blockBody == null) + { + // such as expression lambda. there is no statement + return false; + } + + // check whether it is last statement except return statement + var statements = blockBody.Statements; + if (statements.Last() == lastStatement) + { + return true; + } + + var index = statements.IndexOf((StatementSyntax)lastStatement); + return statements[index + 1].Kind() == SyntaxKind.ReturnStatement; + } + + protected override SyntaxToken CreateIdentifier(string name) + { + return SyntaxFactory.Identifier(name); + } + + protected override StatementSyntax CreateReturnStatement(string identifierName = null) + { + return string.IsNullOrEmpty(identifierName) + ? SyntaxFactory.ReturnStatement() + : SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName(identifierName)); + } + + protected override ExpressionSyntax CreateCallSignature() + { + var methodName = CreateMethodNameForInvocation().WithAdditionalAnnotations(Simplifier.Annotation); + + var arguments = new List<ArgumentSyntax>(); + foreach (var argument in this.AnalyzerResult.MethodParameters) + { + var modifier = GetParameterRefSyntaxKind(argument.ParameterModifier); + var refOrOut = modifier == SyntaxKind.None ? default(SyntaxToken) : SyntaxFactory.Token(modifier); + + arguments.Add(SyntaxFactory.Argument(SyntaxFactory.IdentifierName(argument.Name)).WithRefOrOutKeyword(refOrOut)); + } + + var invocation = SyntaxFactory.InvocationExpression(methodName, + SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(arguments))); + + var shouldPutAsyncModifier = this.CSharpSelectionResult.ShouldPutAsyncModifier(); + if (!shouldPutAsyncModifier) + { + return invocation; + } + + return SyntaxFactory.AwaitExpression(invocation); + } + + protected override StatementSyntax CreateAssignmentExpressionStatement(SyntaxToken identifier, ExpressionSyntax rvalue) + { + return SyntaxFactory.ExpressionStatement(CreateAssignmentExpression((SyntaxToken)identifier, rvalue)); + } + + protected override StatementSyntax CreateDeclarationStatement( + VariableInfo variable, + CancellationToken cancellationToken, + ExpressionSyntax initialValue = null) + { + var type = variable.GetVariableType(this.SemanticDocument); + var typeNode = type.GenerateTypeSyntax(); + + var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); + + return SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration(typeNode) + .AddVariables(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(variable.Name)).WithInitializer(equalsValueClause))); + } + + protected override async Task<GeneratedCode> CreateGeneratedCodeAsync(OperationStatus status, SemanticDocument newDocument, CancellationToken cancellationToken) + { + if (status.Succeeded()) + { + // in hybrid code cases such as extract method, formatter will have some difficulties on where it breaks lines in two. + // here, we explicitly insert newline at the end of "{" of auto generated method decl so that anchor knows how to find out + // indentation of inserted statements (from users code) with user code style preserved + var root = newDocument.Root; + var methodDefinition = root.GetAnnotatedNodes<MethodDeclarationSyntax>(this.MethodDefinitionAnnotation).First(); + + var newMethodDefinition = + methodDefinition.ReplaceToken( + methodDefinition.Body.OpenBraceToken, + methodDefinition.Body.OpenBraceToken.WithAppendedTrailingTrivia( + SpecializedCollections.SingletonEnumerable(SyntaxFactory.CarriageReturnLineFeed))); + + newDocument = await newDocument.WithSyntaxRootAsync(root.ReplaceNode(methodDefinition, newMethodDefinition), cancellationToken).ConfigureAwait(false); + } + + return await base.CreateGeneratedCodeAsync(status, newDocument, cancellationToken).ConfigureAwait(false); + } + + protected StatementSyntax GetStatementContainingInvocationToExtractedMethodWorker() + { + var callSignature = CreateCallSignature(); + + if (this.AnalyzerResult.HasReturnType) + { + //Contract.ThrowIfTrue(this.AnalyzerResult.HasVariableToUseAsReturnValue); + return SyntaxFactory.ReturnStatement(callSignature); + } + + return SyntaxFactory.ExpressionStatement(callSignature); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.FormattingProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.FormattingProvider.cs new file mode 100644 index 0000000000..b0a2c77618 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.FormattingProvider.cs @@ -0,0 +1,66 @@ +// 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 Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Options; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor : MethodExtractor + { +// private class FormattingRule : AbstractFormattingRule +// { +// public FormattingRule() +// { +// } +// +// public override AdjustNewLinesOperation GetAdjustNewLinesOperation(SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation<AdjustNewLinesOperation> nextOperation) +// { +// // for extract method case, for a hybrid case, don't force rule, but preserve user style +// var operation = base.GetAdjustNewLinesOperation(previousToken, currentToken, optionSet, nextOperation); +// if (operation == null) +// { +// return null; +// } +// +// if (operation.Option == AdjustNewLinesOption.ForceLinesIfOnSingleLine) +// { +// return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); +// } +// +// if (operation.Option != AdjustNewLinesOption.ForceLines) +// { +// return operation; +// } +// +// if (previousToken.RawKind == (int)SyntaxKind.OpenBraceToken) +// { +// return operation; +// } +// +// if (previousToken.BetweenFieldAndNonFieldMember(currentToken)) +// { +// // make sure to have at least 2 line breaks between field and other members except field +// return FormattingOperations.CreateAdjustNewLinesOperation(2, AdjustNewLinesOption.PreserveLines); +// } +// +// if (previousToken.HasHybridTriviaBetween(currentToken)) +// { +// return FormattingOperations.CreateAdjustNewLinesOperation(operation.Line, AdjustNewLinesOption.PreserveLines); +// } +// +// return operation; +// } +// +// public override void AddAnchorIndentationOperations(List<AnchorIndentationOperation> list, SyntaxNode node, OptionSet optionSet, NextAction<AnchorIndentationOperation> nextOperation) +// { +// if (node.IsKind(SyntaxKind.SimpleLambdaExpression) || node.IsKind(SyntaxKind.ParenthesizedLambdaExpression) || node.IsKind(SyntaxKind.AnonymousMethodExpression)) +// { +// return; +// } +// +// nextOperation.Invoke(list); +// } +// } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.PostProcessor.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.PostProcessor.cs new file mode 100644 index 0000000000..0d77d21298 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.PostProcessor.cs @@ -0,0 +1,314 @@ +// 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.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private class PostProcessor + { + private readonly SemanticModel _semanticModel; + private readonly int _contextPosition; + + public PostProcessor(SemanticModel semanticModel, int contextPosition) + { + //Contract.ThrowIfNull(semanticModel); + + _semanticModel = semanticModel; + _contextPosition = contextPosition; + } + + public IEnumerable<StatementSyntax> RemoveRedundantBlock(IEnumerable<StatementSyntax> statements) + { + // it must have only one statement + if (statements.Count() != 1) + { + return statements; + } + + // that statement must be a block + var block = statements.Single() as BlockSyntax; + if (block == null) + { + return statements; + } + + // we have a block, remove them + return RemoveRedundantBlock(block); + } + + private IEnumerable<StatementSyntax> RemoveRedundantBlock(BlockSyntax block) + { + // if block doesn't have any statement + if (block.Statements.Count == 0) + { + // either remove the block if it doesn't have any trivia, or return as it is if + // there are trivia attached to block + return (block.OpenBraceToken.GetAllTrivia().IsEmpty() && block.CloseBraceToken.GetAllTrivia().IsEmpty()) ? + SpecializedCollections.EmptyEnumerable<StatementSyntax>() : SpecializedCollections.SingletonEnumerable<StatementSyntax>(block); + } + + // okay transfer asset attached to block to statements + var firstStatement = block.Statements.First(); + var firstToken = firstStatement.GetFirstToken(includeZeroWidth: true); + var firstTokenWithAsset = block.OpenBraceToken.CopyAnnotationsTo(firstToken).WithPrependedLeadingTrivia(block.OpenBraceToken.GetAllTrivia()); + + var lastStatement = block.Statements.Last(); + var lastToken = lastStatement.GetLastToken(includeZeroWidth: true); + var lastTokenWithAsset = block.CloseBraceToken.CopyAnnotationsTo(lastToken).WithAppendedTrailingTrivia(block.CloseBraceToken.GetAllTrivia()); + + // create new block with new tokens + block = block.ReplaceTokens(new[] { firstToken, lastToken }, (o, c) => (o == firstToken) ? firstTokenWithAsset : lastTokenWithAsset); + + // return only statements without the wrapping block + return block.Statements; + } + + public IEnumerable<StatementSyntax> MergeDeclarationStatements(IEnumerable<StatementSyntax> statements) + { + if (statements.FirstOrDefault() == null) + { + return statements; + } + + return MergeDeclarationStatementsWorker(statements); + } + + private IEnumerable<StatementSyntax> MergeDeclarationStatementsWorker(IEnumerable<StatementSyntax> statements) + { + var map = new Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>>(); + foreach (var statement in statements) + { + if (!IsDeclarationMergable(statement)) + { + foreach (var declStatement in GetMergedDeclarationStatements(map)) + { + yield return declStatement; + } + + yield return statement; + continue; + } + + AppendDeclarationStatementToMap(statement as LocalDeclarationStatementSyntax, map); + } + + // merge leftover + if (map.Count <= 0) + { + yield break; + } + + foreach (var declStatement in GetMergedDeclarationStatements(map)) + { + yield return declStatement; + } + } + + private void AppendDeclarationStatementToMap( + LocalDeclarationStatementSyntax statement, + Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>> map) + { + // Contract.ThrowIfNull(statement); + + var type = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, statement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + //Contract.ThrowIfNull(type); + + map.GetOrAdd(type, _ => new List<LocalDeclarationStatementSyntax>()).Add(statement); + } + + private IEnumerable<LocalDeclarationStatementSyntax> GetMergedDeclarationStatements( + Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>> map) + { + foreach (var keyValuePair in map) + { + //Contract.ThrowIfFalse(keyValuePair.Value.Count > 0); + + // merge all variable decl for current type + var variables = new List<VariableDeclaratorSyntax>(); + foreach (var statement in keyValuePair.Value) + { + foreach (var variable in statement.Declaration.Variables) + { + variables.Add(variable); + } + } + + // and create one decl statement + // use type name from the first decl statement + yield return + SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration(keyValuePair.Value.First().Declaration.Type, SyntaxFactory.SeparatedList(variables))); + } + + map.Clear(); + } + + private bool IsDeclarationMergable(StatementSyntax statement) + { + //Contract.ThrowIfNull(statement); + + // to be mergable, statement must be + // 1. decl statement without any extra info + // 2. no initialization on any of its decls + // 3. no trivia except whitespace + // 4. type must be known + + var declarationStatement = statement as LocalDeclarationStatementSyntax; + if (declarationStatement == null) + { + return false; + } + + if (declarationStatement.Modifiers.Count > 0 || + declarationStatement.IsConst || + declarationStatement.IsMissing) + { + return false; + } + + if (ContainsAnyInitialization(declarationStatement)) + { + return false; + } + + if (!ContainsOnlyWhitespaceTrivia(declarationStatement)) + { + return false; + } + + var semanticInfo = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, declarationStatement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + if (semanticInfo == null || + semanticInfo.TypeKind == TypeKind.Error || + semanticInfo.TypeKind == TypeKind.Unknown) + { + return false; + } + + return true; + } + + private bool ContainsAnyInitialization(LocalDeclarationStatementSyntax statement) + { + foreach (var variable in statement.Declaration.Variables) + { + if (variable.Initializer != null) + { + return true; + } + } + + return false; + } + + private static bool ContainsOnlyWhitespaceTrivia(StatementSyntax statement) + { + foreach (var token in statement.DescendantTokens()) + { + foreach (var trivia in token.LeadingTrivia.Concat(token.TrailingTrivia)) + { + if (trivia.Kind() != SyntaxKind.WhitespaceTrivia && + trivia.Kind() != SyntaxKind.EndOfLineTrivia) + { + return false; + } + } + } + + return true; + } + + public IEnumerable<StatementSyntax> RemoveInitializedDeclarationAndReturnPattern(IEnumerable<StatementSyntax> statements) + { + // if we have inline temp variable as service, we could just use that service here. + // since it is not a service right now, do very simple clean up + if (statements.ElementAtOrDefault(2) != null) + { + return statements; + } + + var declaration = statements.ElementAtOrDefault(0) as LocalDeclarationStatementSyntax; + var returnStatement = statements.ElementAtOrDefault(1) as ReturnStatementSyntax; + if (declaration == null || returnStatement == null) + { + return statements; + } + + if (declaration.Declaration == null || + declaration.Declaration.Variables.Count != 1 || + declaration.Declaration.Variables[0].Initializer == null || + declaration.Declaration.Variables[0].Initializer.Value == null || + declaration.Declaration.Variables[0].Initializer.Value is StackAllocArrayCreationExpressionSyntax || + returnStatement.Expression == null) + { + return statements; + } + + if (!ContainsOnlyWhitespaceTrivia(declaration) || + !ContainsOnlyWhitespaceTrivia(returnStatement)) + { + return statements; + } + + var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); + if (returnStatement.Expression.ToString() != variableName) + { + return statements; + } + + return SpecializedCollections.SingletonEnumerable<StatementSyntax>(SyntaxFactory.ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)); + } + + public IEnumerable<StatementSyntax> RemoveDeclarationAssignmentPattern(IEnumerable<StatementSyntax> statements) + { + // if we have inline temp variable as service, we could just use that service here. + // since it is not a service right now, do very simple clean up + var declaration = statements.ElementAtOrDefault(0) as LocalDeclarationStatementSyntax; + var assignment = statements.ElementAtOrDefault(1) as ExpressionStatementSyntax; + if (declaration == null || assignment == null) + { + return statements; + } + + if (ContainsAnyInitialization(declaration) || + declaration.Declaration == null || + declaration.Declaration.Variables.Count != 1 || + assignment.Expression == null || + assignment.Expression.Kind() != SyntaxKind.SimpleAssignmentExpression) + { + return statements; + } + + if (!ContainsOnlyWhitespaceTrivia(declaration) || + !ContainsOnlyWhitespaceTrivia(assignment)) + { + return statements; + } + + var variableName = declaration.Declaration.Variables[0].Identifier.ToString(); + + var assignmentExpression = assignment.Expression as AssignmentExpressionSyntax; + if (assignmentExpression.Left == null || + assignmentExpression.Right == null || + assignmentExpression.Left.ToString() != variableName) + { + return statements; + } + + var variable = declaration.Declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(assignmentExpression.Right)); + return SpecializedCollections.SingletonEnumerable<StatementSyntax>( + declaration.WithDeclaration( + declaration.Declaration.WithVariables( + SyntaxFactory.SingletonSeparatedList(variable)))).Concat(statements.Skip(2)); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.TriviaResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.TriviaResult.cs new file mode 100644 index 0000000000..05012b1e51 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.TriviaResult.cs @@ -0,0 +1,161 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.LanguageServices; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor + { + private class CSharpTriviaResult : TriviaResult + { + public static async Task<CSharpTriviaResult> ProcessAsync(SelectionResult selectionResult, CancellationToken cancellationToken) + { + var preservationService = new CSharpSyntaxTriviaService (); + var root = selectionResult.SemanticDocument.Root; + var result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan); + return new CSharpTriviaResult( + await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(false), + result); + } + + private CSharpTriviaResult(SemanticDocument document, ITriviaSavedResult result) : + base(document, result, (int)SyntaxKind.EndOfLineTrivia, (int)SyntaxKind.WhitespaceTrivia) + { + } + + protected override AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode method) + { + var methodDefinition = method as MethodDeclarationSyntax; + if (callsite == null || methodDefinition == null) + { + return null; + } + + return (node, location, annotation) => AnnotationResolver(node, location, annotation, callsite, methodDefinition); + } + + protected override TriviaResolver GetTriviaResolver(SyntaxNode method) + { + var methodDefinition = method as MethodDeclarationSyntax; + if (methodDefinition == null) + { + return null; + } + + return (location, tokenPair, triviaMap) => TriviaResolver(location, tokenPair, triviaMap, methodDefinition); + } + + private SyntaxToken AnnotationResolver( + SyntaxNode node, + TriviaLocation location, + SyntaxAnnotation annotation, + SyntaxNode callsite, + MethodDeclarationSyntax method) + { + var token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken(); + if (token.RawKind != 0) + { + return token; + } + + switch (location) + { + case TriviaLocation.BeforeBeginningOfSpan: + return callsite.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true); + case TriviaLocation.AfterEndOfSpan: + return callsite.GetLastToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true); + case TriviaLocation.AfterBeginningOfSpan: + return method.Body.OpenBraceToken.GetNextToken(includeZeroWidth: true); + case TriviaLocation.BeforeEndOfSpan: + return method.Body.CloseBraceToken.GetPreviousToken(includeZeroWidth: true); + } + + return token; //Contract.FailWithReturn<SyntaxToken>("can't happen"); + } + + private IEnumerable<SyntaxTrivia> TriviaResolver( + TriviaLocation location, + PreviousNextTokenPair tokenPair, + Dictionary<SyntaxToken, LeadingTrailingTriviaPair> triviaMap, + MethodDeclarationSyntax method) + { + // Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where + // elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style + // but not others can be dealt with here. + + // method has no statement in them. so basically two trivia list now pointing to same thing. "{" and "}" + if (tokenPair.PreviousToken == method.Body.OpenBraceToken && + tokenPair.NextToken == method.Body.CloseBraceToken) + { + return (location == TriviaLocation.AfterBeginningOfSpan) ? + SpecializedCollections.SingletonEnumerable<SyntaxTrivia>(SyntaxFactory.ElasticMarker) : + SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + } + + var previousTriviaPair = triviaMap.ContainsKey(tokenPair.PreviousToken) ? triviaMap[tokenPair.PreviousToken] : default(LeadingTrailingTriviaPair); + var nextTriviaPair = triviaMap.ContainsKey(tokenPair.NextToken) ? triviaMap[tokenPair.NextToken] : default(LeadingTrailingTriviaPair); + + var trailingTrivia = previousTriviaPair.TrailingTrivia ?? SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + var leadingTrivia = nextTriviaPair.LeadingTrivia ?? SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + + var list = trailingTrivia.Concat(leadingTrivia); + + switch (location) + { + case TriviaLocation.BeforeBeginningOfSpan: + return FilterBeforeBeginningOfSpan(tokenPair, list); + case TriviaLocation.AfterEndOfSpan: + return FilterTriviaList(list.Concat(tokenPair.NextToken.LeadingTrivia)); + case TriviaLocation.AfterBeginningOfSpan: + return FilterTriviaList(AppendTrailingTrivia(tokenPair).Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)); + case TriviaLocation.BeforeEndOfSpan: + return FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(tokenPair.NextToken.LeadingTrivia)); + } + + return null;//Contract.FailWithReturn<IEnumerable<SyntaxTrivia>>("Shouldn't reach here"); + } + + private IEnumerable<SyntaxTrivia> FilterBeforeBeginningOfSpan(PreviousNextTokenPair tokenPair, IEnumerable<SyntaxTrivia> list) + { + var allList = FilterTriviaList(tokenPair.PreviousToken.TrailingTrivia.Concat(list).Concat(AppendLeadingTrivia(tokenPair))); + + if (tokenPair.PreviousToken.RawKind == (int)SyntaxKind.OpenBraceToken) + { + return RemoveBlankLines(allList); + } + + return allList; + } + + private IEnumerable<SyntaxTrivia> AppendLeadingTrivia(PreviousNextTokenPair tokenPair) + { + if (tokenPair.PreviousToken.RawKind == (int)SyntaxKind.OpenBraceToken || + tokenPair.PreviousToken.RawKind == (int)SyntaxKind.SemicolonToken) + { + return tokenPair.NextToken.LeadingTrivia; + } + + return SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + } + + private IEnumerable<SyntaxTrivia> AppendTrailingTrivia(PreviousNextTokenPair tokenPair) + { + if (tokenPair.PreviousToken.RawKind == (int)SyntaxKind.OpenBraceToken || + tokenPair.PreviousToken.RawKind == (int)SyntaxKind.SemicolonToken) + { + return tokenPair.PreviousToken.TrailingTrivia; + } + + return SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.cs new file mode 100644 index 0000000000..f4ff3c45c0 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpMethodExtractor.cs @@ -0,0 +1,128 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpMethodExtractor : MethodExtractor + { + public CSharpMethodExtractor(CSharpSelectionResult result) : + base(result) + { + } + + protected override Task<AnalyzerResult> AnalyzeAsync(SelectionResult selectionResult, CancellationToken cancellationToken) + { + return CSharpAnalyzer.AnalyzeAsync(selectionResult, cancellationToken); + } + + protected override async Task<InsertionPoint> GetInsertionPointAsync(SemanticDocument document, int position, CancellationToken cancellationToken) + { + //Contract.ThrowIfFalse(position >= 0); + + var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var basePosition = root.FindToken(position); + + var memberNode = basePosition.GetAncestor<MemberDeclarationSyntax>(); +// Contract.ThrowIfNull(memberNode); +// Contract.ThrowIfTrue(memberNode.Kind() == SyntaxKind.NamespaceDeclaration); + + var globalStatement = memberNode as GlobalStatementSyntax; + if (globalStatement != null) + { + // check whether we are extracting whole global statement out + if (this.OriginalSelectionResult.FinalSpan.Contains(memberNode.Span)) + { + return await InsertionPoint.CreateAsync(document, globalStatement.Parent, cancellationToken).ConfigureAwait(false); + } + + return await InsertionPoint.CreateAsync(document, globalStatement.Statement, cancellationToken).ConfigureAwait(false); + } + + return await InsertionPoint.CreateAsync(document, memberNode, cancellationToken).ConfigureAwait(false); + } + + protected override async Task<TriviaResult> PreserveTriviaAsync(SelectionResult selectionResult, CancellationToken cancellationToken) + { + return await CSharpTriviaResult.ProcessAsync(selectionResult, cancellationToken).ConfigureAwait(false); + } + + protected override async Task<SemanticDocument> ExpandAsync(SelectionResult selection, CancellationToken cancellationToken) + { + var lastExpression = selection.GetFirstTokenInSelection().GetCommonRoot(selection.GetLastTokenInSelection()).GetAncestors<ExpressionSyntax>().LastOrDefault(); + if (lastExpression == null) + { + return selection.SemanticDocument; + } + + var newExpression = await Simplifier.ExpandAsync(lastExpression, selection.SemanticDocument.Document, n => n != selection.GetContainingScope(), expandParameter: false, cancellationToken: cancellationToken).ConfigureAwait(false); + return await selection.SemanticDocument.WithSyntaxRootAsync(selection.SemanticDocument.Root.ReplaceNode(lastExpression, newExpression), cancellationToken).ConfigureAwait(false); + } + + protected override Task<MethodExtractor.GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, CancellationToken cancellationToken) + { + return CSharpCodeGenerator.GenerateAsync(insertionPoint, selectionResult, analyzeResult, cancellationToken); + } + +// protected override IEnumerable<IFormattingRule> GetFormattingRules(Document document) +// { +// return SpecializedCollections.SingletonEnumerable(new FormattingRule()).Concat(Formatter.GetDefaultFormattingRules(document)); +// } + + protected override SyntaxToken GetMethodNameAtInvocation(IEnumerable<SyntaxNodeOrToken> methodNames) + { + return (SyntaxToken)methodNames.FirstOrDefault(t => !t.Parent.IsKind(SyntaxKind.MethodDeclaration)); + } + + protected override async Task<OperationStatus> CheckTypeAsync( + Document document, + SyntaxNode contextNode, + Location location, + ITypeSymbol type, + CancellationToken cancellationToken) + { + //Contract.ThrowIfNull(type); + + // this happens when there is no return type + if (type.SpecialType == SpecialType.System_Void) + { + return OperationStatus.Succeeded; + } + + if (type.TypeKind == TypeKind.Error || + type.TypeKind == TypeKind.Unknown) + { + return OperationStatus.ErrorOrUnknownType; + } + + // if it is type parameter, make sure we are getting same type parameter + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + foreach (var typeParameter in TypeParameterCollector.Collect(type)) + { + var typeName = SyntaxFactory.ParseTypeName(typeParameter.Name); + var currentType = semanticModel.GetSpeculativeTypeInfo(contextNode.SpanStart, typeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + if (currentType == null || !currentType.Equals(typeParameter)) + { + return new OperationStatus(OperationStatusFlag.BestEffort, + string.Format("FeaturesResources.TypeParameterIsHiddenByAnother", + typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + currentType == null ? string.Empty : currentType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + } + } + + return OperationStatus.Succeeded; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.ExpressionResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.ExpressionResult.cs new file mode 100644 index 0000000000..62917ffccc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.ExpressionResult.cs @@ -0,0 +1,140 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpSelectionResult + { + private class ExpressionResult : CSharpSelectionResult + { + public ExpressionResult( + OperationStatus status, + TextSpan originalSpan, + TextSpan finalSpan, + OptionSet options, + bool selectionInExpression, + SemanticDocument document, + SyntaxAnnotation firstTokenAnnotation, + SyntaxAnnotation lastTokenAnnotation) : + base(status, originalSpan, finalSpan, options, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation) + { + } + + public override bool ContainingScopeHasAsyncKeyword() + { + return false; + } + + public override SyntaxNode GetContainingScope() + { +// Contract.ThrowIfNull(this.SemanticDocument); +// Contract.ThrowIfFalse(this.SelectionInExpression); + + var firstToken = this.GetFirstTokenInSelection(); + var lastToken = this.GetLastTokenInSelection(); + return firstToken.GetCommonRoot(lastToken).GetAncestorOrThis<ExpressionSyntax>(); + } + + public override ITypeSymbol GetContainingScopeType() + { + var node = this.GetContainingScope(); + var model = this.SemanticDocument.SemanticModel; + + if (!node.IsExpression()) + { + // Contract.Fail("this shouldn't happen"); + return null; + } + + // special case for array initializer and explict cast + if (node.IsArrayInitializer()) + { + var variableDeclExpression = node.GetAncestorOrThis<VariableDeclarationSyntax>(); + if (variableDeclExpression != null) + { + return model.GetTypeInfo(variableDeclExpression.Type).Type; + } + } + + if (node.IsExpressionInCast()) + { + // bug # 12774 and # 4780 + // if the expression is under cast, we use the heuristic below + // 1. if regular binding returns a meaningful type, we use it as it is + // 2. if it doesn't, even if the cast itself wasn't included in the selection, we will treat it + // as it was in the selection + var regularType = GetRegularExpressionType(model, node); + if (regularType != null && !regularType.IsObjectType()) + { + return regularType; + } + + var castExpression = node.Parent as CastExpressionSyntax; + if (castExpression != null) + { + return model.GetTypeInfo(castExpression.Type).Type; + } + } + + return GetRegularExpressionType(model, node); + } + + private static ITypeSymbol GetRegularExpressionType(SemanticModel semanticModel, SyntaxNode node) + { + // regular case. always use ConvertedType to get implicit conversion right. + var expression = node.GetUnparenthesizedExpression(); + + var info = semanticModel.GetTypeInfo(expression); + var conv = semanticModel.GetConversion(expression); + + if (info.ConvertedType == null || info.ConvertedType.IsErrorType()) + { + // there is no implicit conversion involved. no need to go further + return info.Type; + } + + // always use converted type if method group + if ((!node.IsKind(SyntaxKind.ObjectCreationExpression) && semanticModel.GetMemberGroup(expression).Length > 0) || + IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType())) + { + return info.ConvertedType; + } + + // check implicit conversion + if (conv.IsImplicit && (conv.IsConstantExpression || conv.IsEnumeration)) + { + return info.ConvertedType; + } + + // always try to use type that is more specific than object type if possible. + return !info.Type.IsObjectType() ? info.Type : info.ConvertedType; + } + } + + private static bool IsCoClassImplicitConversion(TypeInfo info, Conversion conversion, ISymbol coclassSymbol) + { + if (!conversion.IsImplicit || + info.ConvertedType == null || + info.ConvertedType.TypeKind != TypeKind.Interface) + { + return false; + } + + // let's see whether this interface has coclass attribute + return info.ConvertedType.GetAttributes().Any(c => c.AttributeClass.Equals(coclassSymbol)); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.StatementResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.StatementResult.cs new file mode 100644 index 0000000000..5d6565e056 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.StatementResult.cs @@ -0,0 +1,88 @@ +// 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.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpSelectionResult + { + private class StatementResult : CSharpSelectionResult + { + public StatementResult( + OperationStatus status, + TextSpan originalSpan, + TextSpan finalSpan, + OptionSet options, + bool selectionInExpression, + SemanticDocument document, + SyntaxAnnotation firstTokenAnnotation, + SyntaxAnnotation lastTokenAnnotation) : + base(status, originalSpan, finalSpan, options, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation) + { + } + + public override bool ContainingScopeHasAsyncKeyword() + { + var node = this.GetContainingScope(); + var semanticModel = this.SemanticDocument.SemanticModel; + + return node.TypeSwitch( + (AccessorDeclarationSyntax access) => false, + (MethodDeclarationSyntax method) => method.Modifiers.Any(SyntaxKind.AsyncKeyword), + (ParenthesizedLambdaExpressionSyntax lambda) => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, + (SimpleLambdaExpressionSyntax lambda) => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword, + (AnonymousMethodExpressionSyntax anonymous) => anonymous.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword); + } + + public override SyntaxNode GetContainingScope() + { +// Contract.ThrowIfNull(this.SemanticDocument); +// Contract.ThrowIfTrue(this.SelectionInExpression); + + // it contains statements + var firstToken = this.GetFirstTokenInSelection(); + return firstToken.GetAncestors<SyntaxNode>().FirstOrDefault(n => + { + return n is BaseMethodDeclarationSyntax || + n is AccessorDeclarationSyntax || + n is ParenthesizedLambdaExpressionSyntax || + n is SimpleLambdaExpressionSyntax || + n is AnonymousMethodExpressionSyntax || + n is CompilationUnitSyntax; + }); + } + + public override ITypeSymbol GetContainingScopeType() + { + //Contract.ThrowIfTrue(this.SelectionInExpression); + + var node = this.GetContainingScope(); + var semanticModel = this.SemanticDocument.SemanticModel; + + return node.TypeSwitch( + (AccessorDeclarationSyntax access) => + { + // property case + if (access.Parent == null || access.Parent.Parent == null) + { + return null; + } + + return ((IPropertySymbol)semanticModel.GetDeclaredSymbol(access.Parent.Parent)).Type; + }, + (MethodDeclarationSyntax method) => ((IMethodSymbol)semanticModel.GetDeclaredSymbol(method)).ReturnType, + (ParenthesizedLambdaExpressionSyntax lambda) => semanticModel.GetLambdaOrAnonymousMethodReturnType(lambda), + (SimpleLambdaExpressionSyntax lambda) => semanticModel.GetLambdaOrAnonymousMethodReturnType(lambda), + (AnonymousMethodExpressionSyntax anonymous) => semanticModel.GetLambdaOrAnonymousMethodReturnType(anonymous)); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.cs new file mode 100644 index 0000000000..80f343c09a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionResult.cs @@ -0,0 +1,207 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class CSharpSelectionResult : SelectionResult + { + public static async Task<CSharpSelectionResult> CreateAsync( + OperationStatus status, + TextSpan originalSpan, + TextSpan finalSpan, + OptionSet options, + bool selectionInExpression, + SemanticDocument document, + SyntaxToken firstToken, + SyntaxToken lastToken, + CancellationToken cancellationToken) + { +// Contract.ThrowIfNull(status); +// Contract.ThrowIfNull(document); + + var firstTokenAnnotation = new SyntaxAnnotation(); + var lastTokenAnnotation = new SyntaxAnnotation(); + + var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newDocument = await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(root.AddAnnotations( + new[] + { + Tuple.Create<SyntaxToken, SyntaxAnnotation>(firstToken, firstTokenAnnotation), + Tuple.Create<SyntaxToken, SyntaxAnnotation>(lastToken, lastTokenAnnotation) + })), cancellationToken).ConfigureAwait(false); + + if (selectionInExpression) + { + return new ExpressionResult( + status, originalSpan, finalSpan, options, selectionInExpression, + newDocument, firstTokenAnnotation, lastTokenAnnotation); + } + else + { + return new StatementResult( + status, originalSpan, finalSpan, options, selectionInExpression, + newDocument, firstTokenAnnotation, lastTokenAnnotation); + } + } + + protected CSharpSelectionResult( + OperationStatus status, + TextSpan originalSpan, + TextSpan finalSpan, + OptionSet options, + bool selectionInExpression, + SemanticDocument document, + SyntaxAnnotation firstTokenAnnotation, + SyntaxAnnotation lastTokenAnnotation) : + base(status, originalSpan, finalSpan, options, selectionInExpression, + document, firstTokenAnnotation, lastTokenAnnotation) + { + } + + protected override bool UnderAsyncAnonymousMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) + { + var current = token.Parent; + for (; current != null; current = current.Parent) + { + if (current is MemberDeclarationSyntax || + current is SimpleLambdaExpressionSyntax || + current is ParenthesizedLambdaExpressionSyntax || + current is AnonymousMethodExpressionSyntax) + { + break; + } + } + + if (current == null || current is MemberDeclarationSyntax) + { + return false; + } + + // make sure the selection contains the lambda + return firstToken.SpanStart <= current.GetFirstToken().SpanStart && + current.GetLastToken().Span.End <= lastToken.Span.End; + } + + public StatementSyntax GetFirstStatement() + { + return GetFirstStatement<StatementSyntax>(); + } + + public StatementSyntax GetLastStatement() + { + return GetLastStatement<StatementSyntax>(); + } + + public StatementSyntax GetFirstStatementUnderContainer() + { + //Contract.ThrowIfTrue(this.SelectionInExpression); + + var firstToken = this.GetFirstTokenInSelection(); + var statement = firstToken.Parent.GetStatementUnderContainer(); + //Contract.ThrowIfNull(statement); + + return statement; + } + + public StatementSyntax GetLastStatementUnderContainer() + { + //Contract.ThrowIfTrue(this.SelectionInExpression); + + var lastToken = this.GetLastTokenInSelection(); + var statement = lastToken.Parent.GetStatementUnderContainer(); + + //Contract.ThrowIfNull(statement); + var firstStatementUnderContainer = this.GetFirstStatementUnderContainer(); + //Contract.ThrowIfFalse(statement.Parent == firstStatementUnderContainer.Parent); + + return statement; + } + + public SyntaxNode GetInnermostStatementContainer() + { + //Contract.ThrowIfFalse(this.SelectionInExpression); + var containingScope = this.GetContainingScope(); + var statements = containingScope.GetAncestorsOrThis<StatementSyntax>(); + StatementSyntax last = null; + + foreach (var statement in statements) + { + if (statement.IsStatementContainerNode()) + { + return statement; + } + + last = statement; + } + + // constructor initializer case + var constructorInitializer = this.GetContainingScopeOf<ConstructorInitializerSyntax>(); + if (constructorInitializer != null) + { + return constructorInitializer.Parent; + } + + // field initializer case + var field = this.GetContainingScopeOf<FieldDeclarationSyntax>(); + if (field != null) + { + return field.Parent; + } + +// Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement)); +// Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit)); + return last.Parent.Parent; + } + + public bool ShouldPutUnsafeModifier() + { + var token = this.GetFirstTokenInSelection(); + var ancestors = token.GetAncestors<SyntaxNode>(); + + // if enclosing type contains unsafe keyword, we don't need to put it again + if (ancestors.Where(a => SyntaxFacts.IsTypeDeclaration(a.Kind())) + .Cast<MemberDeclarationSyntax>() + .Any(m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword))) + { + return false; + } + + return token.Parent.IsUnsafeContext(); + } + + public SyntaxKind UnderCheckedExpressionContext() + { + return UnderCheckedContext<CheckedExpressionSyntax>(); + } + + public SyntaxKind UnderCheckedStatementContext() + { + return UnderCheckedContext<CheckedStatementSyntax>(); + } + + private SyntaxKind UnderCheckedContext<T>() where T : SyntaxNode + { + var token = this.GetFirstTokenInSelection(); + var contextNode = token.Parent.GetAncestor<T>(); + if (contextNode == null) + { + return SyntaxKind.None; + } + + return contextNode.Kind(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.Validator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.Validator.cs new file mode 100644 index 0000000000..578998b402 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.Validator.cs @@ -0,0 +1,86 @@ +// 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.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpSelectionValidator + { + public bool Check(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + return node.TypeSwitch( + (ExpressionSyntax expression) => CheckExpression(semanticModel, expression, cancellationToken), + (BlockSyntax block) => CheckBlock(semanticModel, block, cancellationToken), + (StatementSyntax statement) => CheckStatement(semanticModel, statement, cancellationToken), + (GlobalStatementSyntax globalStatement) => CheckGlobalStatement(semanticModel, globalStatement, cancellationToken)); + } + + private bool CheckGlobalStatement(SemanticModel semanticModel, GlobalStatementSyntax globalStatement, CancellationToken cancellationToken) + { + return true; + } + + private bool CheckBlock(SemanticModel semanticModel, BlockSyntax block, CancellationToken cancellationToken) + { + // TODO(cyrusn): Is it intentional that fixed statement is not in this list? + if (block.Parent is BlockSyntax || + block.Parent is DoStatementSyntax || + block.Parent is ElseClauseSyntax || + block.Parent is ForEachStatementSyntax || + block.Parent is ForStatementSyntax || + block.Parent is IfStatementSyntax || + block.Parent is LockStatementSyntax || + block.Parent is UsingStatementSyntax || + block.Parent is WhileStatementSyntax) + { + return true; + } + + return false; + } + + private bool CheckExpression(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // TODO(cyrusn): This is probably unnecessary. What we should be doing is binding + // the type of the expression and seeing if it contains an anonymous type. + if (expression is AnonymousObjectCreationExpressionSyntax) + { + return false; + } + + return expression.CanReplaceWithRValue(semanticModel, cancellationToken); + } + + private bool CheckStatement(SemanticModel semanticModel, StatementSyntax statement, CancellationToken cancellationToken) + { + if (statement is CheckedStatementSyntax || + statement is DoStatementSyntax || + statement is EmptyStatementSyntax || + statement is ExpressionStatementSyntax || + statement is FixedStatementSyntax || + statement is ForEachStatementSyntax || + statement is ForStatementSyntax || + statement is IfStatementSyntax || + statement is LocalDeclarationStatementSyntax || + statement is LockStatementSyntax || + statement is ReturnStatementSyntax || + statement is SwitchStatementSyntax || + statement is ThrowStatementSyntax || + statement is TryStatementSyntax || + statement is UnsafeStatementSyntax || + statement is UsingStatementSyntax || + statement is WhileStatementSyntax) + { + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.cs new file mode 100644 index 0000000000..5d67b0e6eb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSelectionValidator.cs @@ -0,0 +1,485 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class CSharpSelectionValidator : SelectionValidator + { + public CSharpSelectionValidator( + SemanticDocument document, + TextSpan textSpan, + OptionSet options) : + base(document, textSpan, options) + { + } + + public override async Task<SelectionResult> GetValidSelectionAsync(CancellationToken cancellationToken) + { + if (!this.ContainsValidSelection) + { + return NullSelection; + } + + var text = this.SemanticDocument.Text; + var root = this.SemanticDocument.Root; + var model = this.SemanticDocument.SemanticModel; + + // go through pipe line and calculate information about the user selection + var selectionInfo = GetInitialSelectionInfo(root, text, cancellationToken); + selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken); + selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken); + selectionInfo = AssignFinalSpan(selectionInfo, text, cancellationToken); + selectionInfo = ApplySpecialCases(selectionInfo, text, cancellationToken); + selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, root, cancellationToken); + + // there was a fatal error that we couldn't even do negative preview, return error result + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion()) + { + return new ErrorSelectionResult(selectionInfo.Status); + } + + var controlFlowSpan = GetControlFlowSpan(selectionInfo); + if (!selectionInfo.SelectionInExpression) + { + var statementRange = GetStatementRangeContainedInSpan<StatementSyntax>(root, controlFlowSpan, cancellationToken); + if (statementRange == null) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, "CSharpFeaturesResources.CantDetermineValidRangeOfStatements")); + return new ErrorSelectionResult(selectionInfo.Status); + } + + var isFinalSpanSemanticallyValid = IsFinalSpanSemanticallyValidSpan(model, controlFlowSpan, statementRange, cancellationToken); + if (!isFinalSpanSemanticallyValid) + { + // check control flow only if we are extracting statement level, not expression + // level. you can not have goto that moves control out of scope in expression level + // (even in lambda) + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.BestEffort, "CSharpFeaturesResources.NotAllCodePathReturns")); + } + } + + return await CSharpSelectionResult.CreateAsync( + selectionInfo.Status, + selectionInfo.OriginalSpan, + selectionInfo.FinalSpan, + this.Options, + selectionInfo.SelectionInExpression, + this.SemanticDocument, + selectionInfo.FirstTokenInFinalSpan, + selectionInfo.LastTokenInFinalSpan, + cancellationToken).ConfigureAwait(false); + } + + private SelectionInfo ApplySpecialCases(SelectionInfo selectionInfo, SourceText text, CancellationToken cancellationToken) + { + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion() || !selectionInfo.SelectionInExpression) + { + return selectionInfo; + } + + var expressionNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); + if (!expressionNode.IsAnyAssignExpression()) + { + return selectionInfo; + } + + var assign = (AssignmentExpressionSyntax)expressionNode; + + // make sure there is a visible token at right side expression + if (assign.Right.GetLastToken().Kind() == SyntaxKind.None) + { + return selectionInfo; + } + + return AssignFinalSpan(selectionInfo.With(s => s.FirstTokenInFinalSpan = assign.Right.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = assign.Right.GetLastToken(includeZeroWidth: true)), + text, cancellationToken); + } + + private TextSpan GetControlFlowSpan(SelectionInfo selectionInfo) + { + return TextSpan.FromBounds(selectionInfo.FirstTokenInFinalSpan.SpanStart, selectionInfo.LastTokenInFinalSpan.Span.End); + } + + private SelectionInfo AdjustFinalTokensBasedOnContext( + SelectionInfo selectionInfo, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion()) + { + return selectionInfo; + } + + // don't need to adjust anything if it is multi-statements case + if (!selectionInfo.SelectionInExpression && !selectionInfo.SelectionInSingleStatement) + { + return selectionInfo; + } + + // get the node that covers the selection + var node = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); + + var validNode = Check(semanticModel, node, cancellationToken); + if (validNode) + { + return selectionInfo; + } + + var firstValidNode = node.GetAncestors<SyntaxNode>().FirstOrDefault(n => Check(semanticModel, n, cancellationToken)); + if (firstValidNode == null) + { + // couldn't find any valid node + return selectionInfo.WithStatus(s => new OperationStatus(OperationStatusFlag.None, "CSharpFeaturesResources.SelectionDoesNotContainAValidNode")) + .With(s => s.FirstTokenInFinalSpan = default(SyntaxToken)) + .With(s => s.LastTokenInFinalSpan = default(SyntaxToken)); + } + + firstValidNode = (firstValidNode.Parent is ExpressionStatementSyntax) ? firstValidNode.Parent : firstValidNode; + + return selectionInfo.With(s => s.SelectionInExpression = firstValidNode is ExpressionSyntax) + .With(s => s.SelectionInSingleStatement = firstValidNode is StatementSyntax) + .With(s => s.FirstTokenInFinalSpan = firstValidNode.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = firstValidNode.GetLastToken(includeZeroWidth: true)); + } + + private SelectionInfo GetInitialSelectionInfo(SyntaxNode root, SourceText text, CancellationToken cancellationToken) + { + var adjustedSpan = GetAdjustedSpan(text, this.OriginalSpan); + + var firstTokenInSelection = root.FindTokenOnRightOfPosition(adjustedSpan.Start, includeSkipped: false); + var lastTokenInSelection = root.FindTokenOnLeftOfPosition(adjustedSpan.End, includeSkipped: false); + + if (firstTokenInSelection.Kind() == SyntaxKind.None || lastTokenInSelection.Kind() == SyntaxKind.None) + { + return new SelectionInfo { Status = new OperationStatus(OperationStatusFlag.None, "CSharpFeaturesResources.InvalidSelection"), OriginalSpan = adjustedSpan }; + } + + if (!adjustedSpan.Contains(firstTokenInSelection.Span) && !adjustedSpan.Contains(lastTokenInSelection.Span)) + { + return new SelectionInfo + { + Status = new OperationStatus(OperationStatusFlag.None, "CSharpFeaturesResources.SelectionDoesNotContainAValidToken"), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + if (!firstTokenInSelection.UnderValidContext() || !lastTokenInSelection.UnderValidContext()) + { + return new SelectionInfo + { + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + var commonRoot = firstTokenInSelection.GetCommonRoot(lastTokenInSelection); + if (commonRoot == null) + { + return new SelectionInfo + { + Status = new OperationStatus(OperationStatusFlag.None, "CSharpFeaturesResources.NoCommonRootNodeForExtraction"), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + var selectionInExpression = commonRoot is ExpressionSyntax; + if (!selectionInExpression && !commonRoot.UnderValidContext()) + { + return new SelectionInfo + { + Status = new OperationStatus(OperationStatusFlag.None, "CSharpFeaturesResources.NoValidSelectionToPerformExtraction"), + OriginalSpan = adjustedSpan, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + return new SelectionInfo + { + Status = OperationStatus.Succeeded, + OriginalSpan = adjustedSpan, + CommonRootFromOriginalSpan = commonRoot, + SelectionInExpression = selectionInExpression, + FirstTokenInOriginalSpan = firstTokenInSelection, + LastTokenInOriginalSpan = lastTokenInSelection + }; + } + + private SelectionInfo CheckErrorCasesAndAppendDescriptions(SelectionInfo selectionInfo, SyntaxNode root, CancellationToken cancellationToken) + { + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion()) + { + return selectionInfo; + } + + if (selectionInfo.FirstTokenInFinalSpan.IsMissing || selectionInfo.LastTokenInFinalSpan.IsMissing) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, "CSharpFeaturesResources.ContainsInvalidSelection")); + } + + // get the node that covers the selection + var commonNode = selectionInfo.FirstTokenInFinalSpan.GetCommonRoot(selectionInfo.LastTokenInFinalSpan); + + if ((selectionInfo.SelectionInExpression || selectionInfo.SelectionInSingleStatement) && commonNode.HasDiagnostics()) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, "CSharpFeaturesResources.TheSelectionContainsSyntacticErrors")); + } + + var tokens = root.DescendantTokens(selectionInfo.FinalSpan); + if (tokens.ContainPreprocessorCrossOver(selectionInfo.FinalSpan)) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.BestEffort, "CSharpFeaturesResources.SelectionCanNotCrossOverPreprocessorDirectives")); + } + + // TODO : check whether this can be handled by control flow analysis engine + if (tokens.Any(t => t.Kind() == SyntaxKind.YieldKeyword)) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.BestEffort, "CSharpFeaturesResources.SelectionCanNotContainAYieldStatement")); + } + + // TODO : check behavior of control flow analysis engine around exception and exception handling. + if (tokens.ContainArgumentlessThrowWithoutEnclosingCatch(selectionInfo.FinalSpan)) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.BestEffort, "CSharpFeaturesResources.SelectionCanNotContainThrowStatement")); + } + + if (selectionInfo.SelectionInExpression && commonNode.PartOfConstantInitializerExpression()) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, "CSharpFeaturesResources.SelectionCanNotBePartOfConstInitializerExpr")); + } + + if (commonNode.IsUnsafeContext()) + { + selectionInfo = selectionInfo.WithStatus(s => s.With(s.Flag, "CSharpFeaturesResources.TheSelectedCodeIsInsideAnUnsafeContext")); + } + + var selectionChanged = selectionInfo.FirstTokenInOriginalSpan != selectionInfo.FirstTokenInFinalSpan || selectionInfo.LastTokenInOriginalSpan != selectionInfo.LastTokenInFinalSpan; + if (selectionChanged) + { + selectionInfo = selectionInfo.WithStatus(s => s.MarkSuggestion()); + } + + return selectionInfo; + } + + private SelectionInfo AssignInitialFinalTokens(SelectionInfo selectionInfo, SyntaxNode root, CancellationToken cancellationToken) + { + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion()) + { + return selectionInfo; + } + + if (selectionInfo.SelectionInExpression) + { + // simple expression case + return selectionInfo.With(s => s.FirstTokenInFinalSpan = s.CommonRootFromOriginalSpan.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = s.CommonRootFromOriginalSpan.GetLastToken(includeZeroWidth: true)); + } + + var range = GetStatementRangeContainingSpan<StatementSyntax>( + root, TextSpan.FromBounds(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.LastTokenInOriginalSpan.Span.End), + cancellationToken); + + if (range == null) + { + return selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, "CSharpFeaturesResources.NoValidStatementRangeToExtractOut")); + } + + var statement1 = (StatementSyntax)range.Item1; + var statement2 = (StatementSyntax)range.Item2; + + if (statement1 == statement2) + { + // check one more time to see whether it is an expression case + var expression = selectionInfo.CommonRootFromOriginalSpan.GetAncestor<ExpressionSyntax>(); + if (expression != null && statement1.Span.Contains(expression.Span)) + { + return selectionInfo.With(s => s.SelectionInExpression = true) + .With(s => s.FirstTokenInFinalSpan = expression.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = expression.GetLastToken(includeZeroWidth: true)); + } + + // single statement case + return selectionInfo.With(s => s.SelectionInSingleStatement = true) + .With(s => s.FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = statement1.GetLastToken(includeZeroWidth: true)); + } + + // move only statements inside of the block + return selectionInfo.With(s => s.FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth: true)) + .With(s => s.LastTokenInFinalSpan = statement2.GetLastToken(includeZeroWidth: true)); + } + + private SelectionInfo AssignFinalSpan(SelectionInfo selectionInfo, SourceText text, CancellationToken cancellationToken) + { + if (selectionInfo.Status.FailedWithNoBestEffortSuggestion()) + { + return selectionInfo; + } + + // set final span + var start = (selectionInfo.FirstTokenInOriginalSpan == selectionInfo.FirstTokenInFinalSpan) ? + Math.Min(selectionInfo.FirstTokenInOriginalSpan.SpanStart, selectionInfo.OriginalSpan.Start) : + selectionInfo.FirstTokenInFinalSpan.FullSpan.Start; + + var end = (selectionInfo.LastTokenInOriginalSpan == selectionInfo.LastTokenInFinalSpan) ? + Math.Max(selectionInfo.LastTokenInOriginalSpan.Span.End, selectionInfo.OriginalSpan.End) : + selectionInfo.LastTokenInFinalSpan.FullSpan.End; + + return selectionInfo.With(s => s.FinalSpan = GetAdjustedSpan(text, TextSpan.FromBounds(start, end))); + } + + public override bool ContainsNonReturnExitPointsStatements(IEnumerable<SyntaxNode> jumpsOutOfRegion) + { + return jumpsOutOfRegion.Where(n => !(n is ReturnStatementSyntax)).Any(); + } + + public override IEnumerable<SyntaxNode> GetOuterReturnStatements(SyntaxNode commonRoot, IEnumerable<SyntaxNode> jumpsOutOfRegion) + { + var returnStatements = jumpsOutOfRegion.Where(s => s is ReturnStatementSyntax); + + var container = commonRoot.GetAncestorsOrThis<SyntaxNode>().Where(a => a.IsReturnableConstruct()).FirstOrDefault(); + if (container == null) + { + return SpecializedCollections.EmptyEnumerable<SyntaxNode>(); + } + + var returnableConstructPairs = returnStatements.Select(r => Tuple.Create(r, r.GetAncestors<SyntaxNode>().Where(a => a.IsReturnableConstruct()).FirstOrDefault())) + .Where(p => p.Item2 != null); + + // now filter return statements to only include the one under outmost container + return returnableConstructPairs.Where(p => p.Item2 == container).Select(p => p.Item1); + } + + public override bool IsFinalSpanSemanticallyValidSpan( + SyntaxNode root, TextSpan textSpan, + IEnumerable<SyntaxNode> returnStatements, CancellationToken cancellationToken) + { + // return statement shouldn't contain any return value + if (returnStatements.Cast<ReturnStatementSyntax>().Any(r => r.Expression != null)) + { + return false; + } + + var lastToken = (SyntaxToken)root.FindToken(textSpan.End); + if (lastToken.Kind() == SyntaxKind.None) + { + return false; + } + + var container = lastToken.GetAncestors<SyntaxNode>().FirstOrDefault(n => n.IsReturnableConstruct()); + if (container == null) + { + return false; + } + + var body = container.GetBlockBody(); + if (body == null) + { + return false; + } + + // make sure that next token of the last token in the selection is the close braces of containing block + if (body.CloseBraceToken != lastToken.GetNextToken(includeZeroWidth: true)) + { + return false; + } + + // alright, for these construcuts, it must be okay to be extracted + switch (container.Kind()) + { + case SyntaxKind.AnonymousMethodExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.ParenthesizedLambdaExpression: + return true; + } + + // now, only method is okay to be extracted out + var method = body.Parent as MethodDeclarationSyntax; + if (method == null) + { + return false; + } + + // make sure this method doesn't have return type. + return method.ReturnType.TypeSwitch((PredefinedTypeSyntax p) => p.Keyword.Kind() == SyntaxKind.VoidKeyword); + } + + private static TextSpan GetAdjustedSpan(SourceText text, TextSpan textSpan) + { + // beginning of a file + if (textSpan.IsEmpty || textSpan.End == 0) + { + return textSpan; + } + + // if it is a start of new line, make it belong to previous line + var line = text.Lines.GetLineFromPosition(textSpan.End); + if (line.Start != textSpan.End) + { + return textSpan; + } + + // get previous line + //Contract.ThrowIfFalse(line.LineNumber > 0); + var previousLine = text.Lines[line.LineNumber - 1]; + return TextSpan.FromBounds(textSpan.Start, previousLine.End); + } + + private class SelectionInfo + { + public OperationStatus Status { get; set; } + + public TextSpan OriginalSpan { get; set; } + public TextSpan FinalSpan { get; set; } + + public SyntaxNode CommonRootFromOriginalSpan { get; set; } + + public SyntaxToken FirstTokenInOriginalSpan { get; set; } + public SyntaxToken LastTokenInOriginalSpan { get; set; } + + public SyntaxToken FirstTokenInFinalSpan { get; set; } + public SyntaxToken LastTokenInFinalSpan { get; set; } + + public bool SelectionInExpression { get; set; } + public bool SelectionInSingleStatement { get; set; } + + public SelectionInfo WithStatus(Func<OperationStatus, OperationStatus> statusGetter) + { + return With(s => s.Status = statusGetter(s.Status)); + } + + public SelectionInfo With(Action<SelectionInfo> valueSetter) + { + var newInfo = this.Clone(); + valueSetter(newInfo); + return newInfo; + } + + public SelectionInfo Clone() + { + return (SelectionInfo)this.MemberwiseClone(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaService.cs new file mode 100644 index 0000000000..1cabb19475 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaService.cs @@ -0,0 +1,16 @@ +// 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; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class CSharpSyntaxTriviaService : AbstractSyntaxTriviaService + { + public CSharpSyntaxTriviaService() + : base((int)SyntaxKind.EndOfLineTrivia) + { + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaServiceFactory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaServiceFactory.cs new file mode 100644 index 0000000000..d640d6066c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/CSharpSyntaxTriviaServiceFactory.cs @@ -0,0 +1,20 @@ +// 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; +using Microsoft.CodeAnalysis.Host.Mef; + +using Microsoft.CodeAnalysis; + + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ +// [ExportLanguageServiceFactory(typeof(ISyntaxTriviaService), LanguageNames.CSharp), Shared] +// public class CSharpSyntaxTriviaServiceFactory : ILanguageServiceFactory +// { +// public ILanguageService CreateLanguageService(HostLanguageServices provider) +// { +// return new CSharpSyntaxTriviaService(provider); +// } +// } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/Extensions.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/Extensions.cs new file mode 100644 index 0000000000..61825c5c53 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/CSharp/Extensions.cs @@ -0,0 +1,285 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + static partial class Extensions + { + public static ExpressionSyntax GetUnparenthesizedExpression(this SyntaxNode node) + { + var parenthesizedExpression = node as ParenthesizedExpressionSyntax; + if (parenthesizedExpression == null) + { + return node as ExpressionSyntax; + } + + return GetUnparenthesizedExpression(parenthesizedExpression.Expression); + } + + public static StatementSyntax GetStatementUnderContainer(this SyntaxNode node) + { + //Contract.ThrowIfNull(node); + + while (node != null) + { + if (node.Parent != null && + node.Parent.IsStatementContainerNode()) + { + return node as StatementSyntax; + } + + node = node.Parent; + } + + return null; + } + + public static StatementSyntax GetParentLabeledStatementIfPossible(this SyntaxNode node) + { + return (StatementSyntax)((node.Parent is LabeledStatementSyntax) ? node.Parent : node); + } + + public static bool IsStatementContainerNode(this SyntaxNode node) + { + return node is BlockSyntax || node is SwitchSectionSyntax; + } + + public static BlockSyntax GetBlockBody(this SyntaxNode node) + { + return node.TypeSwitch( + (BaseMethodDeclarationSyntax m) => m.Body, + (AccessorDeclarationSyntax a) => a.Body, + (SimpleLambdaExpressionSyntax s) => s.Body as BlockSyntax, + (ParenthesizedLambdaExpressionSyntax p) => p.Body as BlockSyntax, + (AnonymousMethodExpressionSyntax a) => a.Block); + } + + public static bool UnderValidContext(this SyntaxNode node) + { + //Contract.ThrowIfNull(node); + + Func<SyntaxNode, bool> predicate = n => + { + if (n is BaseMethodDeclarationSyntax || + n is AccessorDeclarationSyntax || + n is BlockSyntax || + n is GlobalStatementSyntax) + { + return true; + } + + var constructorInitializer = n as ConstructorInitializerSyntax; + if (constructorInitializer != null) + { + return constructorInitializer.ContainsInArgument(node.Span); + } + + return false; + }; + + if (!node.GetAncestorsOrThis<SyntaxNode>().Any(predicate)) + { + return false; + } + + if (node.FromScript() || node.GetAncestor<TypeDeclarationSyntax>() != null) + { + return true; + } + + return false; + } + + public static bool UnderValidContext(this SyntaxToken token) + { + return token.GetAncestors<SyntaxNode>().Any(n => n.CheckTopLevel(token.Span)); + } + + public static bool PartOfConstantInitializerExpression(this SyntaxNode node) + { + return node.PartOfConstantInitializerExpression<FieldDeclarationSyntax>(n => n.Modifiers) || + node.PartOfConstantInitializerExpression<LocalDeclarationStatementSyntax>(n => n.Modifiers); + } + + private static bool PartOfConstantInitializerExpression<T>(this SyntaxNode node, Func<T, SyntaxTokenList> modifiersGetter) where T : SyntaxNode + { + var decl = node.GetAncestor<T>(); + if (decl == null) + { + return false; + } + + if (!modifiersGetter(decl).Any(t => t.Kind() == SyntaxKind.ConstKeyword)) + { + return false; + } + + // we are under decl with const modifier, check we are part of initializer expression + var equal = node.GetAncestor<EqualsValueClauseSyntax>(); + if (equal == null) + { + return false; + } + + return equal.Value != null && equal.Value.Span.Contains(node.Span); + } + + public static bool ContainArgumentlessThrowWithoutEnclosingCatch(this IEnumerable<SyntaxToken> tokens, TextSpan textSpan) + { + foreach (var token in tokens) + { + if (token.Kind() != SyntaxKind.ThrowKeyword) + { + continue; + } + + var throwStatement = token.Parent as ThrowStatementSyntax; + if (throwStatement == null || throwStatement.Expression != null) + { + continue; + } + + var catchClause = token.GetAncestor<CatchClauseSyntax>(); + if (catchClause == null || !textSpan.Contains(catchClause.Span)) + { + return true; + } + } + + return false; + } + + public static bool ContainPreprocessorCrossOver(this IEnumerable<SyntaxToken> tokens, TextSpan textSpan) + { + int activeRegions = 0; + int activeIfs = 0; + + foreach (var trivia in tokens.GetAllTrivia()) + { + if (!textSpan.Contains(trivia.Span)) + { + continue; + } + + switch (trivia.Kind()) + { + case SyntaxKind.RegionDirectiveTrivia: + activeRegions++; + break; + case SyntaxKind.EndRegionDirectiveTrivia: + if (activeRegions <= 0) + { + return true; + } + + activeRegions--; + break; + case SyntaxKind.IfDirectiveTrivia: + activeIfs++; + break; + case SyntaxKind.EndIfDirectiveTrivia: + if (activeIfs <= 0) + { + return true; + } + + activeIfs--; + break; + case SyntaxKind.ElseDirectiveTrivia: + case SyntaxKind.ElifDirectiveTrivia: + if (activeIfs <= 0) + { + return true; + } + + break; + } + } + + return activeIfs != 0 || activeRegions != 0; + } + + public static IEnumerable<SyntaxTrivia> GetAllTrivia(this IEnumerable<SyntaxToken> tokens) + { + foreach (var token in tokens) + { + foreach (var trivia in token.LeadingTrivia) + { + yield return trivia; + } + + foreach (var trivia in token.TrailingTrivia) + { + yield return trivia; + } + } + } + + public static bool HasSyntaxAnnotation(this HashSet<SyntaxAnnotation> set, SyntaxNode node) + { + return set.Any(a => node.GetAnnotatedNodesAndTokens(a).Any()); + } + + public static bool HasHybridTriviaBetween(this SyntaxToken token1, SyntaxToken token2) + { + if (token1.TrailingTrivia.Any(t => !t.IsElastic())) + { + return true; + } + + if (token2.LeadingTrivia.Any(t => !t.IsElastic())) + { + return true; + } + + return false; + } + + public static bool IsArrayInitializer(this SyntaxNode node) + { + return node is InitializerExpressionSyntax && node.Parent is EqualsValueClauseSyntax; + } + + public static bool IsExpressionInCast(this SyntaxNode node) + { + return node is ExpressionSyntax && node.Parent is CastExpressionSyntax; + } + + public static bool IsExpression(this SyntaxNode node) + { + return node is ExpressionSyntax; + } + + public static bool IsErrorType(this ITypeSymbol type) + { + return type == null || type.Kind == SymbolKind.ErrorType; + } + + public static bool IsObjectType(this ITypeSymbol type) + { + return type == null || type.SpecialType == SpecialType.System_Object; + } + + public static bool BetweenFieldAndNonFieldMember(this SyntaxToken token1, SyntaxToken token2) + { + if (token1.RawKind != (int)SyntaxKind.SemicolonToken || !(token1.Parent is FieldDeclarationSyntax)) + { + return false; + } + + var field = token2.GetAncestor<FieldDeclarationSyntax>(); + return field == null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Enums.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Enums.cs new file mode 100644 index 0000000000..36b85e5578 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Enums.cs @@ -0,0 +1,55 @@ +// 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 ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public enum DeclarationBehavior + { + None, + Delete, + MoveIn, + MoveOut, + SplitIn, + SplitOut + } + + public enum ReturnBehavior + { + None, + Initialization, + Assignment + } + + public enum ParameterBehavior + { + None, + Input, + Out, + Ref + } + + /// <summary> + /// status code for extract method operations + /// </summary> + [Flags] + public enum OperationStatusFlag + { + None = 0x0, + + /// <summary> + /// operation has succeeded + /// </summary> + Succeeded = 0x1, + + /// <summary> + /// operation has succeeded with a span that is different than original span + /// </summary> + Suggestion = 0x2, + + /// <summary> + /// operation has failed but can provide some best effort result + /// </summary> + BestEffort = 0x4, + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Extensions.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Extensions.cs new file mode 100644 index 0000000000..5a4f85d696 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/Extensions.cs @@ -0,0 +1,121 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + static partial class Extensions + { + public static bool Succeeded(this OperationStatus status) + { + return status.Flag.Succeeded(); + } + + public static bool FailedWithNoBestEffortSuggestion(this OperationStatus status) + { + return status.Flag.Failed() && !status.Flag.HasBestEffort(); + } + + public static bool Failed(this OperationStatus status) + { + return status.Flag.Failed(); + } + + public static bool Succeeded(this OperationStatusFlag flag) + { + return (flag & OperationStatusFlag.Succeeded) != 0; + } + + public static bool Failed(this OperationStatusFlag flag) + { + return !flag.Succeeded(); + } + + public static bool HasBestEffort(this OperationStatusFlag flag) + { + return (flag & OperationStatusFlag.BestEffort) != 0; + } + + public static bool HasSuggestion(this OperationStatusFlag flag) + { + return (flag & OperationStatusFlag.Suggestion) != 0; + } + + public static bool HasMask(this OperationStatusFlag flag, OperationStatusFlag mask) + { + return (flag & mask) != 0x0; + } + + public static OperationStatusFlag RemoveFlag(this OperationStatusFlag baseFlag, OperationStatusFlag flagToRemove) + { + return baseFlag & ~flagToRemove; + } + + public static ITypeSymbol GetLambdaOrAnonymousMethodReturnType(this SemanticModel binding, SyntaxNode node) + { + var info = binding.GetSymbolInfo(node); + if (info.Symbol == null) + { + return null; + } + + var methodSymbol = info.Symbol as IMethodSymbol; + if (methodSymbol.MethodKind != MethodKind.AnonymousFunction) + { + return null; + } + + return methodSymbol.ReturnType; + } + + public static Task<SemanticDocument> WithSyntaxRootAsync(this SemanticDocument semanticDocument, SyntaxNode root, CancellationToken cancellationToken) + { + return SemanticDocument.CreateAsync(semanticDocument.Document.WithSyntaxRoot(root), cancellationToken); + } + + /// <summary> + /// get tokens with given annotation in current document + /// </summary> + public static SyntaxToken GetTokenWithAnnotaton(this SemanticDocument document, SyntaxAnnotation annotation) + { + return document.Root.GetAnnotatedNodesAndTokens(annotation).Single().AsToken(); + } + + /// <summary> + /// resolve the given symbol against compilation this snapshot has + /// </summary> + public static T ResolveType<T>(this SemanticModel semanticModel, T symbol) where T : class, ITypeSymbol + { + return (T)symbol.GetSymbolKey().Resolve(semanticModel.Compilation).GetAnySymbol(); + } + + /// <summary> + /// check whether node contains error for itself but not from its child node + /// </summary> + public static bool HasDiagnostics(this SyntaxNode node) + { + var set = new HashSet<Diagnostic>(node.GetDiagnostics()); + + foreach (var child in node.ChildNodes()) + { + set.ExceptWith(child.GetDiagnostics()); + } + + return set.Count > 0; + } + + public static bool FromScript(this SyntaxNode node) + { + if (node.SyntaxTree == null) + { + return false; + } + + return node.SyntaxTree.Options.Kind != SourceCodeKind.Regular; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodMatrix.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodMatrix.cs new file mode 100644 index 0000000000..9762ca2cc2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodMatrix.cs @@ -0,0 +1,229 @@ +// 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 Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class ExtractMethodMatrix + { + private static readonly Dictionary<Key, VariableStyle> s_matrix; + + static ExtractMethodMatrix() + { + s_matrix = new Dictionary<Key, VariableStyle>(); + BuildMatrix(); + } + + public static VariableStyle GetVariableStyle( + bool captured, + bool dataFlowIn, + bool dataFlowOut, + bool alwaysAssigned, + bool variableDeclared, + bool readInside, + bool writtenInside, + bool readOutside, + bool writtenOutside, + bool unsafeAddressTaken) + { +#if false + // decide not to treat capture variable special + if (captured) + { + // if a variable is captured, it can only be passed as ref parameter. + return VariableStyle.OnlyAsRefParam; + } +#endif + // bug # 12258, 12114 + // use "out" if "&" is taken for the variable + if (unsafeAddressTaken) + { + return VariableStyle.Out; + } + + var key = new Key( + dataFlowIn, + dataFlowOut, + alwaysAssigned, + variableDeclared, + readInside, + writtenInside, + readOutside, + writtenOutside); + + // special cases + if (!s_matrix.ContainsKey(key)) + { + // Interesting case. Due to things like constant analysis there can be regions that + // the compiler considers data not to flow in (because analysis proves that that + // path will never be taken). However, the variable can still be read/written inside + // the region. For purposes of extract method, we check for this case, and we + // pretend it's as if data flowed into the region. + if (!dataFlowIn && (readInside || writtenInside)) + { + key = new Key(true, dataFlowOut, alwaysAssigned, variableDeclared, readInside, writtenInside, readOutside, writtenOutside); + } + + // another interesting case (bug # 10875) + // basically, it can happen in malformed code where a variable is not properly assigned but used outside of the selection + unreachable code region + // for such cases, treat it like "MoveOut" + if (!dataFlowOut && !alwaysAssigned && variableDeclared && !writtenInside && readOutside) + { + key = new Key(dataFlowIn, /*dataFlowOut*/ true, alwaysAssigned, variableDeclared, readInside, writtenInside, readOutside, writtenOutside); + } + } + + // Contract.ThrowIfFalse(s_matrix.ContainsKey(key)); + + return s_matrix[key]; + } + + private static void BuildMatrix() + { + // meaning of each boolean values (total of 69 different cases) + // data flowin/data flow out/always assigned/variable declared/ read inside/written inside/read outside/written outside + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.MoveIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: false, readOutside: false, writtenOutside: true), VariableStyle.MoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: false, readOutside: true, writtenOutside: true), VariableStyle.MoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.None); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: false, readOutside: false, writtenOutside: false), VariableStyle.None); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: false, readOutside: false, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: false, readOutside: true, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.None); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.MoveIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.MoveIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitIn); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: false, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.None); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: false, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.None); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: false, alwaysAssigned: true, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.SplitOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.Ref); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Ref); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.Ref); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Ref); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: false, readOutside: true, writtenOutside: false), VariableStyle.NotUsed); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: false, readOutside: true, writtenOutside: true), VariableStyle.NotUsed); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: false, readOutside: true, writtenOutside: false), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: false, readOutside: true, writtenOutside: true), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: false, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.Out); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Out); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.Out); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Out); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: true, readInside: false, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: false, dataFlowOut: true, alwaysAssigned: true, variableDeclared: true, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.OutWithMoveOut); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: false, readOutside: false, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: false, readOutside: false, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: false, readOutside: true, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: false, readOutside: true, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: false, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: false, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.InputOnly); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithErrorInput); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: true, alwaysAssigned: false, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Ref); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: false), VariableStyle.OutWithErrorInput); + s_matrix.Add(new Key(dataFlowIn: true, dataFlowOut: true, alwaysAssigned: true, variableDeclared: false, readInside: true, writtenInside: true, readOutside: true, writtenOutside: true), VariableStyle.Ref); + } + + private struct Key : IEquatable<Key> + { + public bool DataFlowIn { get; } + public bool DataFlowOut { get; } + public bool AlwaysAssigned { get; } + public bool VariableDeclared { get; } + public bool ReadInside { get; } + public bool WrittenInside { get; } + public bool ReadOutside { get; } + public bool WrittenOutside { get; } + + public Key( + bool dataFlowIn, + bool dataFlowOut, + bool alwaysAssigned, + bool variableDeclared, + bool readInside, + bool writtenInside, + bool readOutside, + bool writtenOutside) : + this() + { + this.DataFlowIn = dataFlowIn; + this.DataFlowOut = dataFlowOut; + this.AlwaysAssigned = alwaysAssigned; + this.VariableDeclared = variableDeclared; + this.ReadInside = readInside; + this.WrittenInside = writtenInside; + this.ReadOutside = readOutside; + this.WrittenOutside = writtenOutside; + } + + public bool Equals(Key key) + { + return this.DataFlowIn == key.DataFlowIn && + this.DataFlowOut == key.DataFlowOut && + this.AlwaysAssigned == key.AlwaysAssigned && + this.VariableDeclared == key.VariableDeclared && + this.ReadInside == key.ReadInside && + this.WrittenInside == key.WrittenInside && + this.ReadOutside == key.ReadOutside && + this.WrittenOutside == key.WrittenOutside; + } + + public override bool Equals(object obj) + { + if (obj is Key) + { + return Equals((Key)obj); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = 0; + + hashCode = this.DataFlowIn ? 1 << 7 | hashCode : hashCode; + hashCode = this.DataFlowOut ? 1 << 6 | hashCode : hashCode; + hashCode = this.AlwaysAssigned ? 1 << 5 | hashCode : hashCode; + hashCode = this.VariableDeclared ? 1 << 4 | hashCode : hashCode; + hashCode = this.ReadInside ? 1 << 3 | hashCode : hashCode; + hashCode = this.WrittenInside ? 1 << 2 | hashCode : hashCode; + hashCode = this.ReadOutside ? 1 << 1 | hashCode : hashCode; + hashCode = this.WrittenOutside ? 1 << 0 | hashCode : hashCode; + + return hashCode; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodOptions.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodOptions.cs new file mode 100644 index 0000000000..dccabba2d9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodOptions.cs @@ -0,0 +1,17 @@ +// 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.Options; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public static class ExtractMethodOptions + { + public const string FeatureName = "ExtractMethod"; + + public static readonly PerLanguageOption<bool> AllowBestEffort = new PerLanguageOption<bool>(FeatureName, "Allow Best Effort", defaultValue: false); + + public static readonly PerLanguageOption<bool> DontPutOutOrRefOnStruct = new PerLanguageOption<bool>(FeatureName, "Don't Put Out Or Ref On Strcut", defaultValue: true); + + public static readonly PerLanguageOption<bool> AllowMovingDeclaration = new PerLanguageOption<bool>(FeatureName, "Allow Moving Declaration", defaultValue: false); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodResult.cs new file mode 100644 index 0000000000..7aaf0fad80 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodResult.cs @@ -0,0 +1,66 @@ +// 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 Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class ExtractMethodResult + { + /// <summary> + /// True if the extract method operation succeeded. + /// </summary> + public bool Succeeded { get; } + + /// <summary> + /// True if the extract method operation is possible if the original span is adjusted. + /// </summary> + public bool SucceededWithSuggestion { get; } + + /// <summary> + /// The transformed document that was produced as a result of the extract method operation. + /// </summary> + public Document Document { get; } + + /// <summary> + /// The reasons why the extract method operation did not succeed. + /// </summary> + public IEnumerable<string> Reasons { get; } + + /// <summary> + /// the generated method node that contains the extracted code. + /// </summary> + public SyntaxNode MethodDeclarationNode { get; } + + /// <summary> + /// The name token for the invocation node that replaces the extracted code. + /// </summary> + public SyntaxToken InvocationNameToken { get; } + + public ExtractMethodResult( + OperationStatusFlag status, + IEnumerable<string> reasons, + Document document, + SyntaxToken invocationNameToken, + SyntaxNode methodDeclarationNode) + { + this.Status = status; + + this.Succeeded = status.Succeeded() && !status.HasSuggestion(); + this.SucceededWithSuggestion = status.Succeeded() && status.HasSuggestion(); + + this.Reasons = (reasons ?? SpecializedCollections.EmptyEnumerable<string>()).ToReadOnlyCollection(); + + this.Document = document; + this.InvocationNameToken = invocationNameToken; + this.MethodDeclarationNode = methodDeclarationNode; + } + + /// <summary> + /// public status of result. more fine grained reason why it is failed. + /// </summary> + public OperationStatusFlag Status { get; } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodService.cs new file mode 100644 index 0000000000..3ac1794b1c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ExtractMethodService.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public static class ExtractMethodService + { + readonly static CSharpExtractMethodService service = new CSharpExtractMethodService (); + + public static Task<ExtractMethodResult> ExtractMethodAsync(Document document, TextSpan textSpan, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)) + { + return service.ExtractMethodAsync(document, textSpan, options, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/FailedExtractMethodResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/FailedExtractMethodResult.cs new file mode 100644 index 0000000000..62391a1b66 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/FailedExtractMethodResult.cs @@ -0,0 +1,13 @@ +// 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; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class FailedExtractMethodResult : ExtractMethodResult + { + public FailedExtractMethodResult(OperationStatus status) + : base(status.Flag, status.Reasons, null, default(SyntaxToken), default(SyntaxNode)) + { + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/IExtractMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/IExtractMethodService.cs new file mode 100644 index 0000000000..e5c2aa786c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/IExtractMethodService.cs @@ -0,0 +1,16 @@ +// 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.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public interface IExtractMethodService : ILanguageService + { + Task<ExtractMethodResult> ExtractMethodAsync(Document document, TextSpan textSpan, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ISyntaxTriviaService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ISyntaxTriviaService.cs new file mode 100644 index 0000000000..f75f66ce20 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ISyntaxTriviaService.cs @@ -0,0 +1,66 @@ +// 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 Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public enum TriviaLocation + { + BeforeBeginningOfSpan = 0, + AfterBeginningOfSpan, + BeforeEndOfSpan, + AfterEndOfSpan + } + + public struct PreviousNextTokenPair + { + public SyntaxToken PreviousToken { get; set; } + public SyntaxToken NextToken { get; set; } + } + + public struct LeadingTrailingTriviaPair + { + public IEnumerable<SyntaxTrivia> LeadingTrivia { get; set; } + public IEnumerable<SyntaxTrivia> TrailingTrivia { get; set; } + } + + public delegate SyntaxToken AnnotationResolver(SyntaxNode root, TriviaLocation location, SyntaxAnnotation annotation); + public delegate IEnumerable<SyntaxTrivia> TriviaResolver(TriviaLocation location, PreviousNextTokenPair tokenPair, Dictionary<SyntaxToken, LeadingTrailingTriviaPair> triviaMap); + + /// <summary> + /// contains information to restore trivia later on to the annotated tree + /// </summary> + public interface ITriviaSavedResult + { + /// <summary> + /// root node of the annotated tree. + /// </summary> + SyntaxNode Root { get; } + + /// <summary> + /// restore saved trivia to given tree + /// </summary> + /// <param name="root">root node to the annotated tree</param> + /// <param name="annotationResolver">it provides a custom way of resolving annotations to retrieve right tokens to attach trivia</param> + /// <param name="triviaResolver">it provides a custom way of creating trivia list between two tokens</param> + /// <returns>root node to a trivia restored tree</returns> + SyntaxNode RestoreTrivia(SyntaxNode root, AnnotationResolver annotationResolver = null, TriviaResolver triviaResolver = null); + } + + /// <summary> + /// syntax trivia related services + /// </summary> + public interface ISyntaxTriviaService : ILanguageService + { + /// <summary> + /// save trivia around span and let user restore trivia later + /// </summary> + /// <param name="root">root node of a tree</param> + /// <param name="textSpan">selection whose trivia around its edges will be saved</param> + /// <returns>object that holds onto enough information to restore trivia later</returns> + ITriviaSavedResult SaveTriviaAroundSelection(SyntaxNode root, TextSpan textSpan); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/InsertionPoint.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/InsertionPoint.cs new file mode 100644 index 0000000000..d0d74659fd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/InsertionPoint.cs @@ -0,0 +1,64 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class InsertionPoint + { + private readonly SyntaxAnnotation _annotation; + private readonly Lazy<SyntaxNode> _context; + + public static async Task<InsertionPoint> CreateAsync(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken) + { + var root = document.Root; + var annotation = new SyntaxAnnotation(); + var newRoot = root.AddAnnotations(SpecializedCollections.SingletonEnumerable(Tuple.Create(node, annotation))); + return new InsertionPoint(await document.WithSyntaxRootAsync(newRoot, cancellationToken).ConfigureAwait(false), annotation); + } + + private InsertionPoint(SemanticDocument document, SyntaxAnnotation annotation) + { + //Contract.ThrowIfNull(document); + //Contract.ThrowIfNull(annotation); + + this.SemanticDocument = document; + _annotation = annotation; + _context = CreateLazyContextNode(); + } + + public SemanticDocument SemanticDocument { get; } + + public SyntaxNode GetRoot() + { + return this.SemanticDocument.Root; + } + + public SyntaxNode GetContext() + { + return _context.Value; + } + + public InsertionPoint With(SemanticDocument document) + { + return new InsertionPoint(document, _annotation); + } + + private Lazy<SyntaxNode> CreateLazyContextNode() + { + return new Lazy<SyntaxNode>(ComputeContextNode, isThreadSafe: true); + } + + private SyntaxNode ComputeContextNode() + { + var root = this.SemanticDocument.Root; + return root.GetAnnotatedNodesAndTokens(_annotation).Single().AsNode(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs new file mode 100644 index 0000000000..82ce82f155 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.SymbolMapBuilder.cs @@ -0,0 +1,79 @@ +// 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; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected abstract partial class Analyzer + { + private class SymbolMapBuilder : SyntaxWalker + { + private readonly SemanticModel _semanticModel; + //private readonly ISyntaxFactsService _service; + private readonly TextSpan _span; + private readonly Dictionary<ISymbol, List<SyntaxToken>> _symbolMap; + private readonly CancellationToken _cancellationToken; + + public static Dictionary<ISymbol, List<SyntaxToken>> Build( + // ISyntaxFactsService service, + SemanticModel semanticModel, + SyntaxNode root, + TextSpan span, + CancellationToken cancellationToken) + { + //Contract.ThrowIfNull(semanticModel); +// Contract.ThrowIfNull(service); + //Contract.ThrowIfNull(root); + + var builder = new SymbolMapBuilder(/*service, */semanticModel, span, cancellationToken); + builder.Visit(root); + + return builder._symbolMap; + } + + private SymbolMapBuilder( + // ISyntaxFactsService service, + SemanticModel semanticModel, + TextSpan span, + CancellationToken cancellationToken) + : base(SyntaxWalkerDepth.Token) + { + _semanticModel = semanticModel; + // _service = service; + _span = span; + _symbolMap = new Dictionary<ISymbol, List<SyntaxToken>>(); + _cancellationToken = cancellationToken; + } + + protected override void VisitToken(SyntaxToken token) + { + if (token.IsMissing || + token.Width() <= 0 || + !token.IsIdentifier() || + !_span.Contains(token.Span) || + token.Parent.IsNamedParameter()) + { + return; + } + + var symbolInfo = _semanticModel.GetSymbolInfo(token, _cancellationToken); + foreach (var sym in symbolInfo.GetAllSymbols()) + { + // add binding result to map + var list = _symbolMap.GetOrAdd(sym, _ => new List<SyntaxToken>()); + list.Add(token); + } + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.cs new file mode 100644 index 0000000000..300773b942 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.Analyzer.cs @@ -0,0 +1,957 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected abstract partial class Analyzer + { + private readonly SemanticDocument _semanticDocument; + + protected readonly CancellationToken CancellationToken; + protected readonly SelectionResult SelectionResult; + + protected Analyzer(SelectionResult selectionResult, CancellationToken cancellationToken) + { + //Contract.ThrowIfNull(selectionResult); + + this.SelectionResult = selectionResult; + _semanticDocument = selectionResult.SemanticDocument; + this.CancellationToken = cancellationToken; + } + + /// <summary> + /// convert text span to node range for the flow analysis API + /// </summary> + protected abstract Tuple<SyntaxNode, SyntaxNode> GetFlowAnalysisNodeRange(); + + /// <summary> + /// check whether selection contains return statement or not + /// </summary> + protected abstract bool ContainsReturnStatementInSelectedCode(IEnumerable<SyntaxNode> jumpOutOfRegionStatements); + + /// <summary> + /// create VariableInfo type + /// </summary> + protected abstract VariableInfo CreateFromSymbol(Compilation compilation, ISymbol symbol, ITypeSymbol type, VariableStyle variableStyle, bool variableDeclared); + + /// <summary> + /// among variables that will be used as parameters at the extracted method, check whether one of the parameter can be used as return + /// </summary> + protected abstract int GetIndexOfVariableInfoToUseAsReturnValue(IList<VariableInfo> variableInfo); + + /// <summary> + /// get type of the range variable symbol + /// </summary> + protected abstract ITypeSymbol GetRangeVariableType(SemanticModel model, IRangeVariableSymbol symbol); + + /// <summary> + /// check whether the selection is at the placed where read-only field is allowed to be extracted out + /// </summary> + /// <returns></returns> + protected abstract bool ReadOnlyFieldAllowed(); + + public async Task<AnalyzerResult> AnalyzeAsync() + { + // do data flow analysis + var model = _semanticDocument.SemanticModel; + var dataFlowAnalysisData = GetDataFlowAnalysisData(model); + + // build symbol map for the identifiers used inside of the selection + var symbolMap = GetSymbolMap(model); + + // gather initial local or parameter variable info + var variableInfoMap = GenerateVariableInfoMap(model, dataFlowAnalysisData, symbolMap); + + // check whether instance member is used inside of the selection + var instanceMemberIsUsed = IsInstanceMemberUsedInSelectedCode(dataFlowAnalysisData); + + // check whether end of selection is reachable + var endOfSelectionReachable = IsEndOfSelectionReachable(model); + + // collects various variable informations + // extracted code contains return value + var isInExpressionOrHasReturnStatement = IsInExpressionOrHasReturnStatement(model); + var signatureTuple = GetSignatureInformation(model, dataFlowAnalysisData, variableInfoMap, isInExpressionOrHasReturnStatement); + + var parameters = signatureTuple.Item1; + var returnType = signatureTuple.Item2; + var variableToUseAsReturnValue = signatureTuple.Item3; + var unsafeAddressTakenUsed = signatureTuple.Item4; + + var returnTypeTuple = AdjustReturnType(model, returnType); + + returnType = returnTypeTuple.Item1; + bool returnTypeHasAnonymousType = returnTypeTuple.Item2; + bool awaitTaskReturn = returnTypeTuple.Item3; + + // create new document + var newDocument = await CreateDocumentWithAnnotationsAsync(_semanticDocument, parameters, CancellationToken).ConfigureAwait(false); + + // collect method type variable used in selected code + var sortedMap = new SortedDictionary<int, ITypeParameterSymbol>(); + var typeParametersInConstraintList = GetMethodTypeParametersInConstraintList(model, variableInfoMap, symbolMap, sortedMap); + var typeParametersInDeclaration = GetMethodTypeParametersInDeclaration(returnType, sortedMap); + + // check various error cases + var operationStatus = GetOperationStatus(model, symbolMap, parameters, unsafeAddressTakenUsed, returnTypeHasAnonymousType); + + return new AnalyzerResult( + newDocument, + typeParametersInDeclaration, typeParametersInConstraintList, + parameters, variableToUseAsReturnValue, returnType, awaitTaskReturn, + instanceMemberIsUsed, endOfSelectionReachable, operationStatus); + } + + private Tuple<ITypeSymbol, bool, bool> AdjustReturnType(SemanticModel model, ITypeSymbol returnType) + { + // check whether return type contains anonymous type and if it does, fix it up by making it object + var returnTypeHasAnonymousType = returnType.ContainsAnonymousType(); + returnType = returnTypeHasAnonymousType ? returnType.RemoveAnonymousTypes(model.Compilation) : returnType; + + // if selection contains await which is not under async lambda or anonymous delegate, + // change return type to be wrapped in Task + var shouldPutAsyncModifier = this.SelectionResult.ShouldPutAsyncModifier(); + if (shouldPutAsyncModifier) + { + bool awaitTaskReturn; + WrapReturnTypeInTask(model, ref returnType, out awaitTaskReturn); + + return Tuple.Create(returnType, returnTypeHasAnonymousType, awaitTaskReturn); + } + + // unwrap task if needed + UnwrapTaskIfNeeded(model, ref returnType); + return Tuple.Create(returnType, returnTypeHasAnonymousType, false); + } + + private void UnwrapTaskIfNeeded(SemanticModel model, ref ITypeSymbol returnType) + { + // nothing to unwrap + if (!this.SelectionResult.ContainingScopeHasAsyncKeyword() || + !this.ContainsReturnStatementInSelectedCode(model)) + { + return; + } + + var originalDefinition = returnType.OriginalDefinition; + + // see whether it needs to be unwrapped + var taskType = model.Compilation.TaskType(); + if (originalDefinition.Equals(taskType)) + { + returnType = model.Compilation.GetSpecialType(SpecialType.System_Void); + return; + } + + var genericTaskType = model.Compilation.TaskOfTType(); + if (originalDefinition.Equals(genericTaskType)) + { + returnType = ((INamedTypeSymbol)returnType).TypeArguments[0]; + return; + } + + // nothing to unwrap + return; + } + + private void WrapReturnTypeInTask(SemanticModel model, ref ITypeSymbol returnType, out bool awaitTaskReturn) + { + awaitTaskReturn = false; + + var genericTaskType = model.Compilation.TaskOfTType(); + var taskType = model.Compilation.TaskType(); + + if (returnType.Equals(model.Compilation.GetSpecialType(SpecialType.System_Void))) + { + // convert void to Task type + awaitTaskReturn = true; + returnType = taskType; + return; + } + + if (this.SelectionResult.SelectionInExpression) + { + returnType = genericTaskType.Construct(returnType); + return; + } + + if (ContainsReturnStatementInSelectedCode(model)) + { + // check whether we will use return type as it is or not. + awaitTaskReturn = returnType.Equals(taskType); + return; + } + + // okay, wrap the return type in Task<T> + returnType = genericTaskType.Construct(returnType); + } + + private Tuple<IList<VariableInfo>, ITypeSymbol, VariableInfo, bool> GetSignatureInformation( + SemanticModel model, + DataFlowAnalysis dataFlowAnalysisData, + IDictionary<ISymbol, VariableInfo> variableInfoMap, + bool isInExpressionOrHasReturnStatement) + { + if (isInExpressionOrHasReturnStatement) + { + // check whether current selection contains return statement + var parameters = GetMethodParameters(variableInfoMap.Values); + var returnType = this.SelectionResult.GetContainingScopeType(); + if (returnType == null) + { + returnType = model.Compilation.GetSpecialType(SpecialType.System_Object); + } + + var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); + return Tuple.Create(parameters, returnType, default(VariableInfo), unsafeAddressTakenUsed); + } + else + { + // no return statement + var parameters = MarkVariableInfoToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap.Values)); + var variableToUseAsReturnValue = parameters.FirstOrDefault(v => v.UseAsReturnValue); + var returnType = default(ITypeSymbol); + if (variableToUseAsReturnValue != null) + { + returnType = variableToUseAsReturnValue.GetVariableType(_semanticDocument); + } + else + { + returnType = model.Compilation.GetSpecialType(SpecialType.System_Void); + } + + var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); + return Tuple.Create(parameters, returnType, variableToUseAsReturnValue, unsafeAddressTakenUsed); + } + } + + private bool IsInExpressionOrHasReturnStatement(SemanticModel model) + { + var isInExpressionOrHasReturnStatement = this.SelectionResult.SelectionInExpression; + if (!isInExpressionOrHasReturnStatement) + { + var containsReturnStatement = ContainsReturnStatementInSelectedCode(model); + isInExpressionOrHasReturnStatement |= containsReturnStatement; + } + + return isInExpressionOrHasReturnStatement; + } + + private OperationStatus GetOperationStatus( + SemanticModel model, Dictionary<ISymbol, List<SyntaxToken>> symbolMap, IList<VariableInfo> parameters, + bool unsafeAddressTakenUsed, bool returnTypeHasAnonymousType) + { + var readonlyFieldStatus = CheckReadOnlyFields(model, symbolMap); + + var namesWithAnonymousTypes = parameters.Where(v => v.OriginalTypeHadAnonymousTypeOrDelegate).Select(v => v.Name ?? string.Empty); + if (returnTypeHasAnonymousType) + { + namesWithAnonymousTypes = namesWithAnonymousTypes.Concat("return type"); + } + + var anonymousTypeStatus = namesWithAnonymousTypes.Any() ? + new OperationStatus(OperationStatusFlag.BestEffort, string.Format("FeaturesResources.ContainsAnonymousType", string.Join(", ", namesWithAnonymousTypes))) : + OperationStatus.Succeeded; + + var unsafeAddressStatus = unsafeAddressTakenUsed ? OperationStatus.UnsafeAddressTaken : OperationStatus.Succeeded; + + var asyncRefOutParameterStatue = CheckAsyncMethodRefOutParameters(parameters); + + return readonlyFieldStatus.With(anonymousTypeStatus).With(unsafeAddressStatus).With(asyncRefOutParameterStatue); + } + + private OperationStatus CheckAsyncMethodRefOutParameters(IList<VariableInfo> parameters) + { + if (this.SelectionResult.ShouldPutAsyncModifier()) + { + var names = parameters.Where(v => !v.UseAsReturnValue && (v.ParameterModifier == ParameterBehavior.Out || v.ParameterModifier == ParameterBehavior.Ref)) + .Select(p => p.Name ?? string.Empty); + + if (names.Any()) + { + return new OperationStatus(OperationStatusFlag.BestEffort, string.Format("FeaturesResources.AsyncMethodWithRefOutParameters", string.Join(", ", names))); + } + } + + return OperationStatus.Succeeded; + } + + private Task<SemanticDocument> CreateDocumentWithAnnotationsAsync(SemanticDocument document, IList<VariableInfo> variables, CancellationToken cancellationToken) + { + var annotations = new List<Tuple<SyntaxToken, SyntaxAnnotation>>(variables.Count); + variables.Do(v => v.AddIdentifierTokenAnnotationPair(annotations, cancellationToken)); + + if (annotations.Count == 0) + { + return Task.FromResult(document); + } + + return document.WithSyntaxRootAsync(document.Root.AddAnnotations(annotations), cancellationToken); + } + + private Dictionary<ISymbol, List<SyntaxToken>> GetSymbolMap(SemanticModel model) + { + var context = this.SelectionResult.GetContainingScope(); + var symbolMap = SymbolMapBuilder.Build(model, context, this.SelectionResult.FinalSpan, CancellationToken); + + return symbolMap; + } + + private bool ContainsVariableUnsafeAddressTaken(DataFlowAnalysis dataFlowAnalysisData, IEnumerable<ISymbol> symbols) + { + // check whether the selection contains "&" over a symbol exist + var map = new HashSet<ISymbol>(dataFlowAnalysisData.UnsafeAddressTaken); + return symbols.Any(s => map.Contains(s)); + } + + private DataFlowAnalysis GetDataFlowAnalysisData(SemanticModel model) + { + if (this.SelectionResult.SelectionInExpression) + { + return model.AnalyzeDataFlow(this.SelectionResult.GetContainingScope()); + } + + var pair = GetFlowAnalysisNodeRange(); + return model.AnalyzeDataFlow(pair.Item1, pair.Item2); + } + + private bool IsEndOfSelectionReachable(SemanticModel model) + { + if (this.SelectionResult.SelectionInExpression) + { + return true; + } + + var pair = GetFlowAnalysisNodeRange(); + var analysis = model.AnalyzeControlFlow(pair.Item1, pair.Item2); + return analysis.EndPointIsReachable; + } + + private IList<VariableInfo> MarkVariableInfoToUseAsReturnValueIfPossible(IList<VariableInfo> variableInfo) + { + var variableToUseAsReturnValueIndex = GetIndexOfVariableInfoToUseAsReturnValue(variableInfo); + if (variableToUseAsReturnValueIndex >= 0) + { + variableInfo[variableToUseAsReturnValueIndex] = VariableInfo.CreateReturnValue(variableInfo[variableToUseAsReturnValueIndex]); + } + + return variableInfo; + } + + private IList<VariableInfo> GetMethodParameters(ICollection<VariableInfo> variableInfo) + { + var list = new List<VariableInfo>(variableInfo); + + list.Sort(VariableInfo.Compare); + + return list; + } + + private IDictionary<ISymbol, VariableInfo> GenerateVariableInfoMap( + SemanticModel model, DataFlowAnalysis dataFlowAnalysisData, Dictionary<ISymbol, List<SyntaxToken>> symbolMap) + { +// Contract.ThrowIfNull(model); +// Contract.ThrowIfNull(dataFlowAnalysisData); + + var variableInfoMap = new Dictionary<ISymbol, VariableInfo>(); + + // create map of each data + var capturedMap = new HashSet<ISymbol>(dataFlowAnalysisData.Captured); + var dataFlowInMap = new HashSet<ISymbol>(dataFlowAnalysisData.DataFlowsIn); + var dataFlowOutMap = new HashSet<ISymbol>(dataFlowAnalysisData.DataFlowsOut); + var alwaysAssignedMap = new HashSet<ISymbol>(dataFlowAnalysisData.AlwaysAssigned); + var variableDeclaredMap = new HashSet<ISymbol>(dataFlowAnalysisData.VariablesDeclared); + var readInsideMap = new HashSet<ISymbol>(dataFlowAnalysisData.ReadInside); + var writtenInsideMap = new HashSet<ISymbol>(dataFlowAnalysisData.WrittenInside); + var readOutsideMap = new HashSet<ISymbol>(dataFlowAnalysisData.ReadOutside); + var writtenOutsideMap = new HashSet<ISymbol>(dataFlowAnalysisData.WrittenOutside); + var unsafeAddressTakenMap = new HashSet<ISymbol>(dataFlowAnalysisData.UnsafeAddressTaken); + + // gather all meaningful symbols for the span. + var candidates = new HashSet<ISymbol>(readInsideMap); + candidates.UnionWith(writtenInsideMap); + candidates.UnionWith(variableDeclaredMap); + + foreach (var symbol in candidates) + { + if (IsThisParameter(symbol) || + IsInteractiveSynthesizedParameter(symbol)) + { + continue; + } + + var captured = capturedMap.Contains(symbol); + var dataFlowIn = dataFlowInMap.Contains(symbol); + var dataFlowOut = dataFlowOutMap.Contains(symbol); + var alwaysAssigned = alwaysAssignedMap.Contains(symbol); + var variableDeclared = variableDeclaredMap.Contains(symbol); + var readInside = readInsideMap.Contains(symbol); + var writtenInside = writtenInsideMap.Contains(symbol); + var readOutside = readOutsideMap.Contains(symbol); + var writtenOutside = writtenOutsideMap.Contains(symbol); + var unsafeAddressTaken = unsafeAddressTakenMap.Contains(symbol); + + // if it is static local, make sure it is not defined inside + if (symbol.IsStatic) + { + dataFlowIn = dataFlowIn && !variableDeclared; + } + + // make sure readoutside is true when dataflowout is true (bug #3790) + // when a variable is only used inside of loop, a situation where dataflowout == true and readOutside == false + // can happen. but for extract method's point of view, this is not an information that would affect output. + // so, here we adjust flags to follow predefined assumption. + readOutside = readOutside || dataFlowOut; + + // make sure data flow out is true when declared inside/written inside/read outside/not written outside are true (bug #6277) + dataFlowOut = dataFlowOut || (variableDeclared && writtenInside && readOutside && !writtenOutside); + + // variable that is declared inside but never referenced outside. just ignore it and move to next one. + if (variableDeclared && !dataFlowOut && !readOutside && !writtenOutside) + { + continue; + } + + // parameter defined inside of the selection (such as lambda parameter) will be ignored (bug # 10964) + if (symbol is IParameterSymbol && variableDeclared) + { + continue; + } + + var type = GetSymbolType(model, symbol); + if (type == null) + { + continue; + } + + var variableStyle = GetVariableStyle(symbolMap, symbol, model, type, + captured, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, + readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken); + + AddVariableToMap(variableInfoMap, symbol, CreateFromSymbol(model.Compilation, symbol, type, variableStyle, variableDeclared)); + } + + return variableInfoMap; + } + + private void AddVariableToMap(IDictionary<ISymbol, VariableInfo> variableInfoMap, ISymbol localOrParameter, VariableInfo variableInfo) + { + variableInfoMap.Add(localOrParameter, variableInfo); + } + + private VariableStyle GetVariableStyle( + Dictionary<ISymbol, List<SyntaxToken>> symbolMap, + ISymbol symbol, + SemanticModel model, + ITypeSymbol type, + bool captured, + bool dataFlowIn, + bool dataFlowOut, + bool alwaysAssigned, + bool variableDeclared, + bool readInside, + bool writtenInside, + bool readOutside, + bool writtenOutside, + bool unsafeAddressTaken) + { +// Contract.ThrowIfNull(model); +// Contract.ThrowIfNull(type); + + var style = ExtractMethodMatrix.GetVariableStyle(captured, dataFlowIn, dataFlowOut, alwaysAssigned, variableDeclared, + readInside, writtenInside, readOutside, writtenOutside, unsafeAddressTaken); + + if (SelectionContainsOnlyIdentifierWithSameType(type)) + { + return style; + } + + if (UserDefinedValueType(model.Compilation, type) && !this.SelectionResult.DontPutOutOrRefOnStruct) + { + return AlwaysReturn(style); + } + + // for captured variable, never try to move the decl into extracted method + if (captured && (style == VariableStyle.MoveIn)) + { + return VariableStyle.Out; + } + + // check special value type cases + if (type.IsValueType && !IsWrittenInsideForFrameworkValueType(symbolMap, model, symbol, writtenInside)) + { + return style; + } + + // don't blindly always return. make sure there is a write inside of the selection + if (this.SelectionResult.AllowMovingDeclaration || !writtenInside) + { + return style; + } + + return AlwaysReturn(style); + } + + private bool IsWrittenInsideForFrameworkValueType( + Dictionary<ISymbol, List<SyntaxToken>> symbolMap, SemanticModel model, ISymbol symbol, bool writtenInside) + { + List<SyntaxToken> tokens; + if (!symbolMap.TryGetValue(symbol, out tokens)) + { + return writtenInside; + } + + // this relies on the fact that our IsWrittenTo only cares about syntax to figure out whether + // something is written to or not. but not semantic. + // we probably need to move the API to syntaxFact service not semanticFact. + // + // if one wants to get result that also considers semantic, he should use data control flow analysis API. + return tokens.Any(t => t.Parent is ExpressionSyntax && ((ExpressionSyntax)t.Parent).IsWrittenTo()); + } + + private bool SelectionContainsOnlyIdentifierWithSameType(ITypeSymbol type) + { + if (!this.SelectionResult.SelectionInExpression) + { + return false; + } + + var firstToken = this.SelectionResult.GetFirstTokenInSelection(); + var lastToken = this.SelectionResult.GetLastTokenInSelection(); + + if (!firstToken.Equals(lastToken)) + { + return false; + } + + return type.Equals(this.SelectionResult.GetContainingScopeType()); + } + + private bool UserDefinedValueType(Compilation compilation, ITypeSymbol type) + { + if (!type.IsValueType || type.IsPointerType() || type.IsEnumType()) + { + return false; + } + + return type.OriginalDefinition.SpecialType == SpecialType.None && !WellKnownFrameworkValueType(compilation, type); + } + + private bool WellKnownFrameworkValueType(Compilation compilation, ITypeSymbol type) + { + if (!type.IsValueType) + { + return false; + } + + var cancellationTokenType = compilation.GetTypeByMetadataName("System.Threading.CancellationToken"); + if (cancellationTokenType != null && cancellationTokenType.Equals(type)) + { + return true; + } + + return false; + } + + private ITypeSymbol GetSymbolType(SemanticModel model, ISymbol symbol) + { + var local = symbol as ILocalSymbol; + if (local != null) + { + return local.Type; + } + + var parameter = symbol as IParameterSymbol; + if (parameter != null) + { + return parameter.Type; + } + + var rangeVariable = symbol as IRangeVariableSymbol; + if (rangeVariable != null) + { + return GetRangeVariableType(model, rangeVariable); + } + + return null; + } + + protected VariableStyle AlwaysReturn(VariableStyle style) + { + if (style == VariableStyle.InputOnly) + { + return VariableStyle.Ref; + } + + if (style == VariableStyle.MoveIn) + { + return VariableStyle.Out; + } + + if (style == VariableStyle.SplitIn) + { + return VariableStyle.Out; + } + + if (style == VariableStyle.SplitOut) + { + return VariableStyle.OutWithMoveOut; + } + + return style; + } + + private bool IsParameterUsedOutside(ISymbol localOrParameter) + { + var parameter = localOrParameter as IParameterSymbol; + if (parameter == null) + { + return false; + } + + return parameter.RefKind != RefKind.None; + } + + private bool IsParameterAssigned(ISymbol localOrParameter) + { + // hack for now. + var parameter = localOrParameter as IParameterSymbol; + if (parameter == null) + { + return false; + } + + return parameter.RefKind != RefKind.Out; + } + + private bool IsThisParameter(ISymbol localOrParameter) + { + var parameter = localOrParameter as IParameterSymbol; + if (parameter == null) + { + return false; + } + + return parameter.IsThis; + } + + private bool IsInteractiveSynthesizedParameter(ISymbol localOrParameter) + { + var parameter = localOrParameter as IParameterSymbol; + if (parameter == null) + { + return false; + } + + return parameter.IsImplicitlyDeclared && + parameter.ContainingAssembly.IsInteractive && + parameter.ContainingSymbol != null && + parameter.ContainingSymbol.ContainingType != null && + parameter.ContainingSymbol.ContainingType.IsScriptClass; + } + + private bool ContainsReturnStatementInSelectedCode(SemanticModel model) + { + //Contract.ThrowIfTrue(this.SelectionResult.SelectionInExpression); + + var pair = GetFlowAnalysisNodeRange(); + var controlFlowAnalysisData = model.AnalyzeControlFlow(pair.Item1, pair.Item2); + + return ContainsReturnStatementInSelectedCode(controlFlowAnalysisData.ExitPoints); + } + + private void AddTypeParametersToMap(IEnumerable<ITypeParameterSymbol> typeParameters, IDictionary<int, ITypeParameterSymbol> sortedMap) + { + foreach (var typeParameter in typeParameters) + { + AddTypeParameterToMap(typeParameter, sortedMap); + } + } + + private void AddTypeParameterToMap(ITypeParameterSymbol typeParameter, IDictionary<int, ITypeParameterSymbol> sortedMap) + { + if (typeParameter == null || + typeParameter.DeclaringMethod == null || + sortedMap.ContainsKey(typeParameter.Ordinal)) + { + return; + } + + sortedMap[typeParameter.Ordinal] = typeParameter; + } + + private void AppendMethodTypeVariableFromDataFlowAnalysis( + SemanticModel model, + IDictionary<ISymbol, VariableInfo> variableInfoMap, + IDictionary<int, ITypeParameterSymbol> sortedMap) + { + foreach (var symbol in variableInfoMap.Keys) + { + var parameter = symbol as IParameterSymbol; + if (parameter != null) + { + AddTypeParametersToMap(TypeParameterCollector.Collect(parameter.Type), sortedMap); + continue; + } + + var local = symbol as ILocalSymbol; + if (local != null) + { + AddTypeParametersToMap(TypeParameterCollector.Collect(local.Type), sortedMap); + continue; + } + + var rangeVariable = symbol as IRangeVariableSymbol; + if (rangeVariable != null) + { + var type = GetRangeVariableType(model, rangeVariable); + AddTypeParametersToMap(TypeParameterCollector.Collect(type), sortedMap); + continue; + } + + //Contract.Fail(FeaturesResources.UnknownSymbolKind); + } + } + + private void AppendMethodTypeParameterFromConstraint(SortedDictionary<int, ITypeParameterSymbol> sortedMap) + { + var typeParametersInConstraint = new List<ITypeParameterSymbol>(); + + // collect all type parameter appears in constraint + foreach (var typeParameter in sortedMap.Values) + { + var constraintTypes = typeParameter.ConstraintTypes; + if (constraintTypes.IsDefaultOrEmpty) + { + continue; + } + + foreach (var type in constraintTypes) + { + // constraint itself is type parameter + typeParametersInConstraint.AddRange(TypeParameterCollector.Collect(type)); + } + } + + // pick up only valid type parameter and add them to the map + foreach (var typeParameter in typeParametersInConstraint) + { + AddTypeParameterToMap(typeParameter, sortedMap); + } + } + + private void AppendMethodTypeParameterUsedDirectly(IDictionary<ISymbol, List<SyntaxToken>> symbolMap, IDictionary<int, ITypeParameterSymbol> sortedMap) + { + foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.TypeParameter)) + { + var typeParameter = pair.Key as ITypeParameterSymbol; + if (typeParameter.DeclaringMethod == null || + sortedMap.ContainsKey(typeParameter.Ordinal)) + { + continue; + } + + sortedMap[typeParameter.Ordinal] = typeParameter; + } + } + + private IEnumerable<ITypeParameterSymbol> GetMethodTypeParametersInConstraintList( + SemanticModel model, + IDictionary<ISymbol, VariableInfo> variableInfoMap, + IDictionary<ISymbol, List<SyntaxToken>> symbolMap, + SortedDictionary<int, ITypeParameterSymbol> sortedMap) + { + // find starting points + AppendMethodTypeVariableFromDataFlowAnalysis(model, variableInfoMap, sortedMap); + AppendMethodTypeParameterUsedDirectly(symbolMap, sortedMap); + + // recursively dive into constraints to find all constraints needed + AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(sortedMap); + + return sortedMap.Values.ToList(); + } + + private void AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(SortedDictionary<int, ITypeParameterSymbol> sortedMap) + { + var visited = new HashSet<ITypeSymbol>(); + var candidates = SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol>(); + + // collect all type parameter appears in constraint + foreach (var typeParameter in sortedMap.Values) + { + var constraintTypes = typeParameter.ConstraintTypes; + if (constraintTypes.IsDefaultOrEmpty) + { + continue; + } + + foreach (var type in constraintTypes) + { + candidates = candidates.Concat(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(type, visited)); + } + } + + // pick up only valid type parameter and add them to the map + foreach (var typeParameter in candidates) + { + AddTypeParameterToMap(typeParameter, sortedMap); + } + } + + private IEnumerable<ITypeParameterSymbol> AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints( + ITypeSymbol type, HashSet<ITypeSymbol> visited) + { + if (visited.Contains(type)) + { + return SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol>(); + } + + visited.Add(type); + + if (type.OriginalDefinition.Equals(type)) + { + return SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol>(); + } + + var constructedType = type as INamedTypeSymbol; + if (constructedType == null) + { + return SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol>(); + } + + var parameters = constructedType.GetAllTypeParameters().ToList(); + var arguments = constructedType.GetAllTypeArguments().ToList(); + + //Contract.ThrowIfFalse(parameters.Count == arguments.Count); + + var typeParameters = new List<ITypeParameterSymbol>(); + for (int i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + var argument = arguments[i] as ITypeParameterSymbol; + if (argument != null) + { + // no constraint, nothing to do + if (!parameter.HasConstructorConstraint && + !parameter.HasReferenceTypeConstraint && + !parameter.HasValueTypeConstraint && + parameter.ConstraintTypes.IsDefaultOrEmpty) + { + continue; + } + + typeParameters.Add(argument); + continue; + } + + var candidate = arguments[i] as INamedTypeSymbol; + if (candidate == null) + { + continue; + } + + typeParameters.AddRange(AppendTypeParametersInConstraintsUsedByConstructedTypeWithItsOwnConstraints(candidate, visited)); + } + + return typeParameters; + } + + private IEnumerable<ITypeParameterSymbol> GetMethodTypeParametersInDeclaration(ITypeSymbol returnType, SortedDictionary<int, ITypeParameterSymbol> sortedMap) + { + // add return type to the map + AddTypeParametersToMap(TypeParameterCollector.Collect(returnType), sortedMap); + + AppendMethodTypeParameterFromConstraint(sortedMap); + + return sortedMap.Values.ToList(); + } + + private OperationStatus CheckReadOnlyFields(SemanticModel semanticModel, Dictionary<ISymbol, List<SyntaxToken>> symbolMap) + { + if (ReadOnlyFieldAllowed()) + { + return OperationStatus.Succeeded; + } + + List<string> names = null; + + foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.Field)) + { + var field = (IFieldSymbol)pair.Key; + if (!field.IsReadOnly) + { + continue; + } + + var tokens = pair.Value; + if (tokens.All(t => !((ExpressionSyntax)t.Parent).IsWrittenTo())) + { + continue; + } + + names = names ?? new List<string>(); + names.Add(field.Name ?? string.Empty); + } + + if (names != null) + { + return new OperationStatus(OperationStatusFlag.BestEffort, string.Format("FeaturesResources.AssingingToReadonlyFields", string.Join(", ", names))); + } + + return OperationStatus.Succeeded; + } + + private bool IsInstanceMemberUsedInSelectedCode(DataFlowAnalysis dataFlowAnalysisData) + { + //Contract.ThrowIfNull(dataFlowAnalysisData); + + // "this" can be used as a lvalue in a struct, check WrittenInside as well + return dataFlowAnalysisData.ReadInside.Any(s => IsThisParameter(s)) || + dataFlowAnalysisData.WrittenInside.Any(s => IsThisParameter(s)); + } + + protected VariableInfo CreateFromSymbolCommon<T>( + Compilation compilation, + ISymbol symbol, + ITypeSymbol type, + VariableStyle style, + HashSet<int> nonNoisySyntaxKindSet) where T : SyntaxNode + { + var local = symbol as ILocalSymbol; + if (local != null) + { + return new VariableInfo( + new LocalVariableSymbol<T>(compilation, local, type, nonNoisySyntaxKindSet), + style); + } + + var parameter = symbol as IParameterSymbol; + if (parameter != null) + { + return new VariableInfo(new ParameterVariableSymbol(compilation, parameter, type), style); + } + + var rangeVariable = symbol as IRangeVariableSymbol; + if (rangeVariable != null) + { + return new VariableInfo(new QueryVariableSymbol(compilation, rangeVariable, type), style); + } + + return null;//Contract.FailWithReturn<VariableInfo>(FeaturesResources.Unknown); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.AnalyzerResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.AnalyzerResult.cs new file mode 100644 index 0000000000..12cee3b59a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.AnalyzerResult.cs @@ -0,0 +1,176 @@ +// 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.ObjectModel; +using System.Linq; +using System.Threading; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected class AnalyzerResult + { + private readonly IList<ITypeParameterSymbol> _typeParametersInDeclaration; + private readonly IList<ITypeParameterSymbol> _typeParametersInConstraintList; + private readonly IList<VariableInfo> _variables; + private readonly VariableInfo _variableToUseAsReturnValue; + + public AnalyzerResult( + SemanticDocument document, + IEnumerable<ITypeParameterSymbol> typeParametersInDeclaration, + IEnumerable<ITypeParameterSymbol> typeParametersInConstraintList, + IList<VariableInfo> variables, + VariableInfo variableToUseAsReturnValue, + ITypeSymbol returnType, + bool awaitTaskReturn, + bool instanceMemberIsUsed, + bool endOfSelectionReachable, + OperationStatus status) + { + var semanticModel = document.SemanticModel; + + this.UseInstanceMember = instanceMemberIsUsed; + this.EndOfSelectionReachable = endOfSelectionReachable; + this.AwaitTaskReturn = awaitTaskReturn; + this.SemanticDocument = document; + _typeParametersInDeclaration = typeParametersInDeclaration.Select(s => semanticModel.ResolveType(s)).ToList(); + _typeParametersInConstraintList = typeParametersInConstraintList.Select(s => semanticModel.ResolveType(s)).ToList(); + _variables = variables; + this.ReturnType = semanticModel.ResolveType(returnType); + _variableToUseAsReturnValue = variableToUseAsReturnValue; + this.Status = status; + } + + public AnalyzerResult With(SemanticDocument document) + { + if (this.SemanticDocument == document) + { + return this; + } + + return new AnalyzerResult( + document, + _typeParametersInDeclaration, + _typeParametersInConstraintList, + _variables, + _variableToUseAsReturnValue, + this.ReturnType, + this.AwaitTaskReturn, + this.UseInstanceMember, + this.EndOfSelectionReachable, + this.Status); + } + + /// <summary> + /// used to determine whether static can be used + /// </summary> + public bool UseInstanceMember { get; } + + /// <summary> + /// used to determine whether "return" statement needs to be inserted + /// </summary> + public bool EndOfSelectionReachable { get; } + + /// <summary> + /// document this result is based on + /// </summary> + public SemanticDocument SemanticDocument { get; } + + /// <summary> + /// flag to show whether task return type is due to await + /// </summary> + public bool AwaitTaskReturn { get; } + + /// <summary> + /// return type + /// </summary> + public ITypeSymbol ReturnType { get; } + + /// <summary> + /// analyzer result operation status + /// </summary> + public OperationStatus Status { get; } + + public ReadOnlyCollection<ITypeParameterSymbol> MethodTypeParametersInDeclaration + { + get + { + return new ReadOnlyCollection<ITypeParameterSymbol>(_typeParametersInDeclaration); + } + } + + public ReadOnlyCollection<ITypeParameterSymbol> MethodTypeParametersInConstraintList + { + get + { + return new ReadOnlyCollection<ITypeParameterSymbol>(_typeParametersInConstraintList); + } + } + + public bool HasVariableToUseAsReturnValue + { + get + { + return _variableToUseAsReturnValue != null; + } + } + + public VariableInfo VariableToUseAsReturnValue + { + get + { + //Contract.ThrowIfNull(_variableToUseAsReturnValue); + return _variableToUseAsReturnValue; + } + } + + public bool HasReturnType + { + get + { + return this.ReturnType.SpecialType != SpecialType.System_Void && !this.AwaitTaskReturn; + } + } + + public IEnumerable<VariableInfo> MethodParameters + { + get + { + return _variables.Where(v => v.UseAsParameter); + } + } + + public IEnumerable<VariableInfo> GetVariablesToSplitOrMoveIntoMethodDefinition(CancellationToken cancellationToken) + { + return _variables + .Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.SplitIn || + v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveIn); + } + + public IEnumerable<VariableInfo> GetVariablesToMoveIntoMethodDefinition(CancellationToken cancellationToken) + { + return _variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveIn); + } + + public IEnumerable<VariableInfo> GetVariablesToMoveOutToCallSite(CancellationToken cancellationToken) + { + return _variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut); + } + + public IEnumerable<VariableInfo> GetVariablesToMoveOutToCallSiteOrDelete(CancellationToken cancellationToken) + { + return _variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut || + v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.Delete); + } + + public IEnumerable<VariableInfo> GetVariablesToSplitOrMoveOutToCallSite(CancellationToken cancellationToken) + { + return _variables.Where(v => v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.SplitOut || + v.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.CodeGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.CodeGenerator.cs new file mode 100644 index 0000000000..87d59c70a8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -0,0 +1,316 @@ +// 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; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected abstract partial class CodeGenerator<TStatement, TExpression, TNodeUnderContainer> + where TStatement : SyntaxNode + where TExpression : SyntaxNode + where TNodeUnderContainer : SyntaxNode + { + protected readonly SyntaxAnnotation MethodNameAnnotation; + protected readonly SyntaxAnnotation MethodDefinitionAnnotation; + protected readonly SyntaxAnnotation CallSiteAnnotation; + + protected readonly InsertionPoint InsertionPoint; + protected readonly SemanticDocument SemanticDocument; + protected readonly SelectionResult SelectionResult; + protected readonly AnalyzerResult AnalyzerResult; + + protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzerResult) + { + //Contract.ThrowIfFalse(insertionPoint.SemanticDocument == analyzerResult.SemanticDocument); + + this.InsertionPoint = insertionPoint; + this.SemanticDocument = insertionPoint.SemanticDocument; + + this.SelectionResult = selectionResult; + this.AnalyzerResult = analyzerResult; + + this.MethodNameAnnotation = new SyntaxAnnotation(); + this.CallSiteAnnotation = new SyntaxAnnotation(); + this.MethodDefinitionAnnotation = new SyntaxAnnotation(); + } + + #region method to be implemented in sub classes + + protected abstract SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken); + protected abstract Task<SyntaxNode> GenerateBodyForCallSiteContainerAsync(CancellationToken cancellationToken); + protected abstract SyntaxNode GetPreviousMember(SemanticDocument document); + protected abstract OperationStatus<IMethodSymbol> GenerateMethodDefinition(CancellationToken cancellationToken); + + protected abstract SyntaxToken CreateIdentifier(string name); + protected abstract SyntaxToken CreateMethodName(); + protected abstract bool LastStatementOrHasReturnStatementInReturnableConstruct(); + + protected abstract TNodeUnderContainer GetFirstStatementOrInitializerSelectedAtCallSite(); + protected abstract TNodeUnderContainer GetLastStatementOrInitializerSelectedAtCallSite(); + protected abstract Task<TNodeUnderContainer> GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(SyntaxAnnotation callsiteAnnotation, CancellationToken cancellationToken); + + protected abstract TExpression CreateCallSignature(); + protected abstract TStatement CreateDeclarationStatement(VariableInfo variable, CancellationToken cancellationToken, TExpression initialValue = null); + protected abstract TStatement CreateAssignmentExpressionStatement(SyntaxToken identifier, TExpression rvalue); + protected abstract TStatement CreateReturnStatement(string identifierName = null); + + protected abstract IEnumerable<TStatement> GetInitialStatementsForMethodDefinitions(); + #endregion + + public async Task<GeneratedCode> GenerateAsync(CancellationToken cancellationToken) + { + var root = this.SemanticDocument.Root; + + // should I check venus hidden position check here as well? + root = root.ReplaceNode(this.GetOutermostCallSiteContainerToProcess(cancellationToken), await this.GenerateBodyForCallSiteContainerAsync(cancellationToken).ConfigureAwait(false)); + var callSiteDocument = await this.SemanticDocument.WithSyntaxRootAsync(root, cancellationToken).ConfigureAwait(false); + + var newCallSiteRoot = callSiteDocument.Root; + var previousMemberNode = GetPreviousMember(callSiteDocument); + + // it is possible in a script file case where there is no previous member. in that case, insert new text into top level script + var destination = (previousMemberNode.Parent == null) ? previousMemberNode : previousMemberNode.Parent; + + var codeGenerationService = new CSharpCodeGenerationService (this.SemanticDocument.Document.Project.Solution.Workspace.Services.GetLanguageServices (LanguageNames.CSharp)); + var result = this.GenerateMethodDefinition(cancellationToken); + var newContainer = codeGenerationService.AddMethod( + destination, result.Data, + new CodeGenerationOptions(afterThisLocation: previousMemberNode.GetLocation(), generateDefaultAccessibility: false, generateMethodBodies: true), + cancellationToken); + + var newDocument = callSiteDocument.Document.WithSyntaxRoot(newCallSiteRoot.ReplaceNode(destination, newContainer)); + newDocument = await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, null, cancellationToken).ConfigureAwait(false); + + var finalDocument = await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); + var finalRoot = finalDocument.Root; + + var methodDefinition = finalRoot.GetAnnotatedNodesAndTokens(this.MethodDefinitionAnnotation).FirstOrDefault(); + if (!methodDefinition.IsNode || methodDefinition.AsNode() == null) + { + return await CreateGeneratedCodeAsync( + result.Status.With(OperationStatus.FailedWithUnknownReason), finalDocument, cancellationToken).ConfigureAwait(false); + } + + if (methodDefinition.SyntaxTree.IsHiddenPosition(methodDefinition.AsNode().SpanStart, cancellationToken) || + methodDefinition.SyntaxTree.IsHiddenPosition(methodDefinition.AsNode().Span.End, cancellationToken)) + { + return await CreateGeneratedCodeAsync( + result.Status.With(OperationStatus.OverlapsHiddenPosition), finalDocument, cancellationToken).ConfigureAwait(false); + } + + return await CreateGeneratedCodeAsync(result.Status, finalDocument, cancellationToken).ConfigureAwait(false); + } + + protected virtual Task<GeneratedCode> CreateGeneratedCodeAsync(OperationStatus status, SemanticDocument newDocument, CancellationToken cancellationToken) + { + return Task.FromResult(new GeneratedCode( + status, + newDocument, + this.MethodNameAnnotation, + this.CallSiteAnnotation, + this.MethodDefinitionAnnotation)); + } + + protected VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(CancellationToken cancellationToken) + { + var variables = new List<VariableInfo>(this.AnalyzerResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken)); + if (variables.Count <= 0) + { + return null; + } + + variables.Sort(VariableInfo.Compare); + return variables[0]; + } + + protected IEnumerable<TStatement> AddReturnIfUnreachable( + IEnumerable<TStatement> statements, CancellationToken cancellationToken) + { + if (this.AnalyzerResult.EndOfSelectionReachable) + { + return statements; + } + + var type = this.SelectionResult.GetContainingScopeType(); + if (type != null && type.SpecialType != SpecialType.System_Void) + { + return statements; + } + + // no return type + end of selection not reachable + if (LastStatementOrHasReturnStatementInReturnableConstruct()) + { + return statements; + } + + return statements.Concat(CreateReturnStatement()); + } + + protected async Task<IEnumerable<TStatement>> AddInvocationAtCallSiteAsync( + IEnumerable<TStatement> statements, CancellationToken cancellationToken) + { + if (this.AnalyzerResult.HasVariableToUseAsReturnValue) + { + return statements; + } + + //Contract.ThrowIfTrue(this.AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken).Any(v => v.UseAsReturnValue)); + + // add invocation expression + return statements.Concat( + (TStatement)(SyntaxNode)await GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(this.CallSiteAnnotation, cancellationToken).ConfigureAwait(false)); + } + + protected IEnumerable<TStatement> AddAssignmentStatementToCallSite( + IEnumerable<TStatement> statements, + CancellationToken cancellationToken) + { + if (!this.AnalyzerResult.HasVariableToUseAsReturnValue) + { + return statements; + } + + var variable = this.AnalyzerResult.VariableToUseAsReturnValue; + if (variable.ReturnBehavior == ReturnBehavior.Initialization) + { + // there must be one decl behavior when there is "return value and initialize" variable + //Contract.ThrowIfFalse(this.AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken).Single(v => v.ReturnBehavior == ReturnBehavior.Initialization) != null); + + return statements.Concat( + CreateDeclarationStatement(variable, cancellationToken, CreateCallSignature()).WithAdditionalAnnotations(this.CallSiteAnnotation)); + } + + //Contract.ThrowIfFalse(variable.ReturnBehavior == ReturnBehavior.Assignment); + return statements.Concat( + CreateAssignmentExpressionStatement(CreateIdentifier(variable.Name), CreateCallSignature()).WithAdditionalAnnotations(this.CallSiteAnnotation)); + } + + protected IEnumerable<TStatement> CreateDeclarationStatements(IEnumerable<VariableInfo> variables, CancellationToken cancellationToken) + { + var list = new List<TStatement>(); + + foreach (var variable in variables) + { + list.Add(CreateDeclarationStatement(variable, cancellationToken)); + } + + return list; + } + + protected IEnumerable<TStatement> AddSplitOrMoveDeclarationOutStatementsToCallSite(IEnumerable<TStatement> statements, CancellationToken cancellationToken) + { + var list = new List<TStatement>(); + + foreach (var variable in this.AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken)) + { + if (variable.UseAsReturnValue) + { + continue; + } + + list.Add(CreateDeclarationStatement(variable, cancellationToken)); + } + + return list; + } + + protected IEnumerable<TStatement> AppendReturnStatementIfNeeded(IEnumerable<TStatement> statements) + { + if (!this.AnalyzerResult.HasVariableToUseAsReturnValue) + { + return statements; + } + + var variableToUseAsReturnValue = this.AnalyzerResult.VariableToUseAsReturnValue; + + //Contract.ThrowIfFalse(variableToUseAsReturnValue.ReturnBehavior == ReturnBehavior.Assignment || + // variableToUseAsReturnValue.ReturnBehavior == ReturnBehavior.Initialization); + + return statements.Concat(CreateReturnStatement(this.AnalyzerResult.VariableToUseAsReturnValue.Name)); + } + + protected HashSet<SyntaxAnnotation> CreateVariableDeclarationToRemoveMap( + IEnumerable<VariableInfo> variables, CancellationToken cancellationToken) + { + var annotations = new List<Tuple<SyntaxToken, SyntaxAnnotation>>(); + + foreach (var variable in variables) + { +// Contract.ThrowIfFalse(variable.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveOut || +// variable.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.MoveIn || +// variable.GetDeclarationBehavior(cancellationToken) == DeclarationBehavior.Delete); + + variable.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + } + + return new HashSet<SyntaxAnnotation>(annotations.Select(t => t.Item2)); + } + + protected IList<ITypeParameterSymbol> CreateMethodTypeParameters(CancellationToken cancellationToken) + { + if (this.AnalyzerResult.MethodTypeParametersInDeclaration.Count == 0) + { + return SpecializedCollections.EmptyList<ITypeParameterSymbol>(); + } + + var set = new HashSet<ITypeParameterSymbol>(this.AnalyzerResult.MethodTypeParametersInConstraintList); + + var typeParameters = new List<ITypeParameterSymbol>(); + foreach (var parameter in this.AnalyzerResult.MethodTypeParametersInDeclaration) + { + if (parameter != null && set.Contains(parameter)) + { + typeParameters.Add(parameter); + continue; + } + + typeParameters.Add(CodeGenerationSymbolFactory.CreateTypeParameter( + parameter.GetAttributes(), parameter.Variance, parameter.Name, ImmutableArray.Create<ITypeSymbol>(), + parameter.HasConstructorConstraint, parameter.HasReferenceTypeConstraint, parameter.HasValueTypeConstraint, parameter.Ordinal)); + } + + return typeParameters; + } + + protected IList<IParameterSymbol> CreateMethodParameters() + { + var parameters = new List<IParameterSymbol>(); + + foreach (var parameter in this.AnalyzerResult.MethodParameters) + { + var refKind = GetRefKind(parameter.ParameterModifier); + var type = parameter.GetVariableType(this.SemanticDocument); + + parameters.Add( + CodeGenerationSymbolFactory.CreateParameterSymbol( + attributes: SpecializedCollections.EmptyList<AttributeData>(), + refKind: refKind, + isParams: false, + type: type, + name: parameter.Name)); + } + + return parameters; + } + + private static RefKind GetRefKind(ParameterBehavior parameterBehavior) + { + return parameterBehavior == ParameterBehavior.Ref ? RefKind.Ref : + parameterBehavior == ParameterBehavior.Out ? RefKind.Out : RefKind.None; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.GeneratedCode.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.GeneratedCode.cs new file mode 100644 index 0000000000..2948cb05e1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.GeneratedCode.cs @@ -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. + +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + public class GeneratedCode + { + public GeneratedCode( + OperationStatus status, + SemanticDocument document, + SyntaxAnnotation methodNameAnnotation, + SyntaxAnnotation callsiteAnnotation, + SyntaxAnnotation methodDefinitionAnnotation) + { + //Contract.ThrowIfNull(document); + //Contract.ThrowIfNull(methodNameAnnotation); + //Contract.ThrowIfNull(callsiteAnnotation); + //Contract.ThrowIfNull(methodDefinitionAnnotation); + + this.Status = status; + this.SemanticDocument = document; + this.MethodNameAnnotation = methodNameAnnotation; + this.CallSiteAnnotation = callsiteAnnotation; + this.MethodDefinitionAnnotation = methodDefinitionAnnotation; + } + + public OperationStatus Status { get; } + public SemanticDocument SemanticDocument { get; } + + public SyntaxAnnotation MethodNameAnnotation { get; } + public SyntaxAnnotation CallSiteAnnotation { get; } + public SyntaxAnnotation MethodDefinitionAnnotation { get; } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TriviaResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TriviaResult.cs new file mode 100644 index 0000000000..6182d45d7c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TriviaResult.cs @@ -0,0 +1,181 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected abstract class TriviaResult + { + private readonly int _endOfLineKind; + private readonly int _whitespaceKind; + + private readonly ITriviaSavedResult _result; + + public TriviaResult(SemanticDocument document, ITriviaSavedResult result, int endOfLineKind, int whitespaceKind) + { + this.SemanticDocument = document; + + _result = result; + _endOfLineKind = endOfLineKind; + _whitespaceKind = whitespaceKind; + } + + protected abstract AnnotationResolver GetAnnotationResolver(SyntaxNode callsite, SyntaxNode methodDefinition); + protected abstract TriviaResolver GetTriviaResolver(SyntaxNode methodDefinition); + + public SemanticDocument SemanticDocument { get; } + + public async Task<OperationStatus<SemanticDocument>> ApplyAsync(GeneratedCode generatedCode, CancellationToken cancellationToken) + { + var document = generatedCode.SemanticDocument; + var root = document.Root; + + var callsiteAnnotation = generatedCode.CallSiteAnnotation; + var methodDefinitionAnnotation = generatedCode.MethodDefinitionAnnotation; + + var callsite = root.GetAnnotatedNodesAndTokens(callsiteAnnotation).SingleOrDefault().AsNode(); + var method = root.GetAnnotatedNodesAndTokens(methodDefinitionAnnotation).SingleOrDefault().AsNode(); + + var annotationResolver = GetAnnotationResolver(callsite, method); + var triviaResolver = GetTriviaResolver(method); + if (annotationResolver == null || triviaResolver == null) + { + // bug # 6644 + // this could happen in malformed code. return as it was. + var status = new OperationStatus(OperationStatusFlag.None, "FeaturesResources.CantNotConstructFinalTree"); + return status.With(document); + } + + return OperationStatus.Succeeded.With( + await document.WithSyntaxRootAsync(_result.RestoreTrivia(root, annotationResolver, triviaResolver), cancellationToken).ConfigureAwait(false)); + } + + protected IEnumerable<SyntaxTrivia> FilterTriviaList(IEnumerable<SyntaxTrivia> list) + { + // has noisy token + if (list.Any(t => t.RawKind != _endOfLineKind && t.RawKind != _whitespaceKind)) + { + return RemoveLeadingElasticBeforeEndOfLine(list); + } + + // whitespace only + return MergeLineBreaks(list); + } + + protected IEnumerable<SyntaxTrivia> RemoveBlankLines(IEnumerable<SyntaxTrivia> list) + { + // remove any blank line at the beginging + var currentLine = new List<SyntaxTrivia>(); + var result = new List<SyntaxTrivia>(); + + var seenFirstEndOfLine = false; + int i = 0; + + foreach (var trivia in list) + { + i++; + + if (trivia.RawKind == _endOfLineKind) + { + if (seenFirstEndOfLine) + { + // empty line. remove it + if (currentLine.All(t => t.RawKind == _endOfLineKind || t.RawKind == _whitespaceKind)) + { + continue; + } + + // non empty line after the first end of line. + // return now + return result.Concat(currentLine).Concat(list.Skip(i - 1)); + } + else + { + seenFirstEndOfLine = true; + + result.AddRange(currentLine); + result.Add(trivia); + currentLine.Clear(); + + continue; + } + } + + currentLine.Add(trivia); + } + + return result.Concat(currentLine); + } + + protected IEnumerable<SyntaxTrivia> RemoveLeadingElasticBeforeEndOfLine(IEnumerable<SyntaxTrivia> list) + { + var trivia = list.FirstOrDefault(); + if (!trivia.IsElastic()) + { + return list; + } + + var listWithoutHead = list.Skip(1); + trivia = listWithoutHead.FirstOrDefault(); + if (trivia.RawKind == _endOfLineKind) + { + return listWithoutHead; + } + + if (trivia.IsElastic()) + { + return RemoveLeadingElasticBeforeEndOfLine(listWithoutHead); + } + + return list; + } + + protected IEnumerable<SyntaxTrivia> MergeLineBreaks(IEnumerable<SyntaxTrivia> list) + { + // this will make sure that it doesn't have more than two subsequent end of line + // trivia without any noisy trivia + var stack = new Stack<SyntaxTrivia>(); + int numberOfEndOfLinesWithoutAnyNoisyTrivia = 0; + + foreach (var trivia in list) + { + if (trivia.IsElastic()) + { + stack.Push(trivia); + continue; + } + + if (trivia.RawKind == _endOfLineKind) + { + numberOfEndOfLinesWithoutAnyNoisyTrivia++; + + if (numberOfEndOfLinesWithoutAnyNoisyTrivia > 2) + { + // get rid of any whitespace trivia from stack + var top = stack.Peek(); + while (!top.IsElastic() && top.RawKind == _whitespaceKind) + { + stack.Pop(); + top = stack.Peek(); + } + + continue; + } + } + + stack.Push(trivia); + } + + return stack.Reverse(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TypeParameterCollector.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TypeParameterCollector.cs new file mode 100644 index 0000000000..9b19db0f03 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.TypeParameterCollector.cs @@ -0,0 +1,57 @@ +// 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 Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected class TypeParameterCollector : SymbolVisitor + { + private readonly List<ITypeParameterSymbol> _typeParameters = new List<ITypeParameterSymbol>(); + + public static IEnumerable<ITypeParameterSymbol> Collect(ITypeSymbol typeSymbol) + { + var collector = new TypeParameterCollector(); + typeSymbol.Accept(collector); + + return collector._typeParameters; + } + + public override void DefaultVisit(ISymbol node) + { + throw new NotImplementedException(); + } + + public override void VisitDynamicType(IDynamicTypeSymbol dynamicTypeSymbol) + { + } + + public override void VisitArrayType(IArrayTypeSymbol arrayTypeSymbol) + { + arrayTypeSymbol.ElementType.Accept(this); + } + + public override void VisitPointerType(IPointerTypeSymbol pointerTypeSymbol) + { + pointerTypeSymbol.PointedAtType.Accept(this); + } + + public override void VisitNamedType(INamedTypeSymbol namedTypeSymbol) + { + foreach (var argument in namedTypeSymbol.GetAllTypeArguments()) + { + argument.Accept(this); + } + } + + public override void VisitTypeParameter(ITypeParameterSymbol typeParameterTypeSymbol) + { + _typeParameters.Add(typeParameterTypeSymbol); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableInfo.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableInfo.cs new file mode 100644 index 0000000000..d7a4b47b3f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableInfo.cs @@ -0,0 +1,138 @@ +// 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.Linq; +using System.Threading; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected class VariableInfo + { + private readonly VariableSymbol _variableSymbol; + private readonly VariableStyle _variableStyle; + private readonly bool _useAsReturnValue; + + public VariableInfo( + VariableSymbol variableSymbol, + VariableStyle variableStyle, + bool useAsReturnValue = false) + { + _variableSymbol = variableSymbol; + _variableStyle = variableStyle; + _useAsReturnValue = useAsReturnValue; + } + + public bool UseAsReturnValue + { + get + { + //Contract.ThrowIfFalse(!_useAsReturnValue || _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None); + return _useAsReturnValue; + } + } + + public bool CanBeUsedAsReturnValue + { + get + { + return _variableStyle.ReturnStyle.ReturnBehavior != ReturnBehavior.None; + } + } + + public bool UseAsParameter + { + get + { + return (!_useAsReturnValue && _variableStyle.ParameterStyle.ParameterBehavior != ParameterBehavior.None) || + (_useAsReturnValue && _variableStyle.ReturnStyle.ParameterBehavior != ParameterBehavior.None); + } + } + + public ParameterBehavior ParameterModifier + { + get + { + return _useAsReturnValue ? _variableStyle.ReturnStyle.ParameterBehavior : _variableStyle.ParameterStyle.ParameterBehavior; + } + } + + public DeclarationBehavior GetDeclarationBehavior(CancellationToken cancellationToken) + { + if (_useAsReturnValue) + { + return _variableStyle.ReturnStyle.DeclarationBehavior; + } + + if (_variableSymbol.GetUseSaferDeclarationBehavior(cancellationToken)) + { + return _variableStyle.ParameterStyle.SaferDeclarationBehavior; + } + + return _variableStyle.ParameterStyle.DeclarationBehavior; + } + + public ReturnBehavior ReturnBehavior + { + get + { + if (_useAsReturnValue) + { + return _variableStyle.ReturnStyle.ReturnBehavior; + } + + return ReturnBehavior.None; + } + } + + public static VariableInfo CreateReturnValue(VariableInfo variable) + { + //Contract.ThrowIfNull(variable); + //Contract.ThrowIfFalse(variable.CanBeUsedAsReturnValue); + //Contract.ThrowIfFalse(variable.ParameterModifier == ParameterBehavior.Out || variable.ParameterModifier == ParameterBehavior.Ref); + + return new VariableInfo(variable._variableSymbol, variable._variableStyle, useAsReturnValue: true); + } + + public void AddIdentifierTokenAnnotationPair( + List<Tuple<SyntaxToken, SyntaxAnnotation>> annotations, CancellationToken cancellationToken) + { + _variableSymbol.AddIdentifierTokenAnnotationPair(annotations, cancellationToken); + } + + public string Name + { + get { return _variableSymbol.Name; } + } + + public bool OriginalTypeHadAnonymousTypeOrDelegate + { + get { return _variableSymbol.OriginalTypeHadAnonymousTypeOrDelegate; } + } + + public ITypeSymbol GetVariableType(SemanticDocument document) + { + return document.SemanticModel.ResolveType(_variableSymbol.OriginalType); + } + + public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) + { + return document.GetTokenWithAnnotaton(_variableSymbol.IdentifierTokenAnnotation); + } + + public SyntaxToken GetIdentifierTokenAtDeclaration(SyntaxNode node) + { + return node.GetAnnotatedTokens(_variableSymbol.IdentifierTokenAnnotation).SingleOrDefault(); + } + + public static int Compare(VariableInfo left, VariableInfo right) + { + return VariableSymbol.Compare(left._variableSymbol, right._variableSymbol); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableSymbol.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableSymbol.cs new file mode 100644 index 0000000000..4dc5b590c1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.VariableSymbol.cs @@ -0,0 +1,357 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + /// <summary> + /// temporary symbol until we have a symbol that can hold onto both local and parameter symbol + /// </summary> + protected abstract class VariableSymbol + { + protected VariableSymbol(Compilation compilation, ITypeSymbol type) + { + this.OriginalTypeHadAnonymousTypeOrDelegate = type.ContainsAnonymousType(); + this.OriginalType = this.OriginalTypeHadAnonymousTypeOrDelegate ? type.RemoveAnonymousTypes(compilation) : type; + } + + public abstract int DisplayOrder { get; } + public abstract string Name { get; } + + public abstract bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken); + public abstract SyntaxAnnotation IdentifierTokenAnnotation { get; } + public abstract SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken); + + public abstract void AddIdentifierTokenAnnotationPair( + List<Tuple<SyntaxToken, SyntaxAnnotation>> annotations, CancellationToken cancellationToken); + + protected abstract int CompareTo(VariableSymbol right); + + /// <summary> + /// return true if original type had anonymous type or delegate somewhere in the type + /// </summary> + public bool OriginalTypeHadAnonymousTypeOrDelegate { get; } + + /// <summary> + /// get the original type with anonymous type removed + /// </summary> + public ITypeSymbol OriginalType { get; } + + public static int Compare(VariableSymbol left, VariableSymbol right) + { + if (left.DisplayOrder == right.DisplayOrder) + { + return left.CompareTo(right); + } + + return left.DisplayOrder - right.DisplayOrder; + } + } + + protected abstract class NotMovableVariableSymbol : VariableSymbol + { + public NotMovableVariableSymbol(Compilation compilation, ITypeSymbol type) : + base(compilation, type) + { + } + + public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) + { + // decl never get moved + return false; + } + + [ExcludeFromCodeCoverage] + public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) + { + throw new InvalidOperationException(); + } + + [ExcludeFromCodeCoverage] + public override SyntaxAnnotation IdentifierTokenAnnotation + { + get { throw new InvalidOperationException(); } + } + + public override void AddIdentifierTokenAnnotationPair( + List<Tuple<SyntaxToken, SyntaxAnnotation>> annotations, CancellationToken cancellationToken) + { + // do nothing for parameter + } + } + + protected class ParameterVariableSymbol : NotMovableVariableSymbol, IComparable<ParameterVariableSymbol> + { + private readonly IParameterSymbol _parameterSymbol; + + public ParameterVariableSymbol(Compilation compilation, IParameterSymbol parameterSymbol, ITypeSymbol type) : + base(compilation, type) + { + //Contract.ThrowIfNull(parameterSymbol); + _parameterSymbol = parameterSymbol; + } + + public override int DisplayOrder + { + get { return 0; } + } + + protected override int CompareTo(VariableSymbol right) + { + return this.CompareTo((ParameterVariableSymbol)right); + } + + public int CompareTo(ParameterVariableSymbol other) + { + //Contract.ThrowIfNull(other); + + if (this == other) + { + return 0; + } + + var compare = CompareTo((IMethodSymbol)_parameterSymbol.ContainingSymbol, (IMethodSymbol)other._parameterSymbol.ContainingSymbol); + if (compare != 0) + { + return compare; + } + + // Contract.ThrowIfFalse(_parameterSymbol.Ordinal != other._parameterSymbol.Ordinal); + return (_parameterSymbol.Ordinal > other._parameterSymbol.Ordinal) ? 1 : -1; + } + + private int CompareTo(IMethodSymbol left, IMethodSymbol right) + { + if (left == null && right == null) + { + return 0; + } + + if (left.Equals(right)) + { + return 0; + } + + if (left.MethodKind == MethodKind.AnonymousFunction && + right.MethodKind != MethodKind.AnonymousFunction) + { + return 1; + } + + if (left.MethodKind != MethodKind.AnonymousFunction && + right.MethodKind == MethodKind.AnonymousFunction) + { + return -1; + } + + if (left.MethodKind == MethodKind.AnonymousFunction && + right.MethodKind == MethodKind.AnonymousFunction) + { + //Contract.ThrowIfFalse(left.Locations.Length == 1); + //Contract.ThrowIfFalse(right.Locations.Length == 1); + + return left.Locations[0].SourceSpan.Start - right.Locations[0].SourceSpan.Start; + } + + return 0;//Contract.FailWithReturn<int>("Shouldn't reach here"); + } + + public override string Name + { + get + { + return _parameterSymbol.ToDisplayString( + new SymbolDisplayFormat( + parameterOptions: SymbolDisplayParameterOptions.IncludeName, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } + } + } + + protected class LocalVariableSymbol<T> : VariableSymbol, IComparable<LocalVariableSymbol<T>> where T : SyntaxNode + { + private readonly SyntaxAnnotation _annotation; + private readonly ILocalSymbol _localSymbol; + private readonly HashSet<int> _nonNoisySet; + + public LocalVariableSymbol(Compilation compilation, ILocalSymbol localSymbol, ITypeSymbol type, HashSet<int> nonNoisySet) : + base(compilation, type) + { +// Contract.ThrowIfNull(localSymbol); +// Contract.ThrowIfNull(nonNoisySet); + + _annotation = new SyntaxAnnotation(); + _localSymbol = localSymbol; + _nonNoisySet = nonNoisySet; + } + + public override int DisplayOrder + { + get { return 1; } + } + + protected override int CompareTo(VariableSymbol right) + { + return this.CompareTo((LocalVariableSymbol<T>)right); + } + + public int CompareTo(LocalVariableSymbol<T> other) + { + //Contract.ThrowIfNull(other); + + if (this == other) + { + return 0; + } + + //Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); + //Contract.ThrowIfFalse(other._localSymbol.Locations.Length == 1); + //Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); + //Contract.ThrowIfFalse(other._localSymbol.Locations[0].IsInSource); + //Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceTree == other._localSymbol.Locations[0].SourceTree); + //Contract.ThrowIfFalse(_localSymbol.Locations[0].SourceSpan.Start != other._localSymbol.Locations[0].SourceSpan.Start); + + return _localSymbol.Locations[0].SourceSpan.Start - other._localSymbol.Locations[0].SourceSpan.Start; + } + + public override string Name + { + get + { + return _localSymbol.ToDisplayString( + new SymbolDisplayFormat( + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } + } + + public override SyntaxToken GetOriginalIdentifierToken(CancellationToken cancellationToken) + { +// Contract.ThrowIfFalse(_localSymbol.Locations.Length == 1); +// Contract.ThrowIfFalse(_localSymbol.Locations[0].IsInSource); +// Contract.ThrowIfNull(_localSymbol.Locations[0].SourceTree); + + var tree = _localSymbol.Locations[0].SourceTree; + var span = _localSymbol.Locations[0].SourceSpan; + + var token = tree.GetRoot(cancellationToken).FindToken(span.Start); + //Contract.ThrowIfFalse(token.Span.Equals(span)); + + return token; + } + + public override SyntaxAnnotation IdentifierTokenAnnotation + { + get { return _annotation; } + } + + public override void AddIdentifierTokenAnnotationPair( + List<Tuple<SyntaxToken, SyntaxAnnotation>> annotations, CancellationToken cancellationToken) + { + annotations.Add(Tuple.Create(this.GetOriginalIdentifierToken(cancellationToken), _annotation)); + } + + public override bool GetUseSaferDeclarationBehavior(CancellationToken cancellationToken) + { + var identifier = this.GetOriginalIdentifierToken(cancellationToken); + + // check whether there is a noisy trivia around the token. + if (ContainsNoisyTrivia(identifier.LeadingTrivia)) + { + return true; + } + + if (ContainsNoisyTrivia(identifier.TrailingTrivia)) + { + return true; + } + + var declStatement = identifier.Parent.FirstAncestorOrSelf<T>((n) => true); + if (declStatement == null) + { + return true; + } + + foreach (var token in declStatement.DescendantTokens()) + { + if (ContainsNoisyTrivia(token.LeadingTrivia)) + { + return true; + } + + if (ContainsNoisyTrivia(token.TrailingTrivia)) + { + return true; + } + } + + return false; + } + + private bool ContainsNoisyTrivia(SyntaxTriviaList list) + { + return list.Any(t => !_nonNoisySet.Contains(t.RawKind)); + } + } + + protected class QueryVariableSymbol : NotMovableVariableSymbol, IComparable<QueryVariableSymbol> + { + private readonly IRangeVariableSymbol _symbol; + + public QueryVariableSymbol(Compilation compilation, IRangeVariableSymbol symbol, ITypeSymbol type) : + base(compilation, type) + { + //Contract.ThrowIfNull(symbol); + _symbol = symbol; + } + + public override int DisplayOrder + { + get { return 2; } + } + + protected override int CompareTo(VariableSymbol right) + { + return this.CompareTo((QueryVariableSymbol)right); + } + + public int CompareTo(QueryVariableSymbol other) + { + //Contract.ThrowIfNull(other); + + if (this == other) + { + return 0; + } + + var locationLeft = _symbol.Locations.First(); + var locationRight = other._symbol.Locations.First(); + +// Contract.ThrowIfFalse(locationLeft.IsInSource); +// Contract.ThrowIfFalse(locationRight.IsInSource); +// Contract.ThrowIfFalse(locationLeft.SourceTree == locationRight.SourceTree); +// Contract.ThrowIfFalse(locationLeft.SourceSpan.Start != locationRight.SourceSpan.Start); + + return locationLeft.SourceSpan.Start - locationRight.SourceSpan.Start; + } + + public override string Name + { + get + { + return _symbol.ToDisplayString( + new SymbolDisplayFormat( + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)); + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.cs new file mode 100644 index 0000000000..7bff9c7fd9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/MethodExtractor.cs @@ -0,0 +1,171 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class MethodExtractor + { + protected readonly SelectionResult OriginalSelectionResult; + + public MethodExtractor(SelectionResult selectionResult) + { + //Contract.ThrowIfNull(selectionResult); + this.OriginalSelectionResult = selectionResult; + } + + protected abstract Task<AnalyzerResult> AnalyzeAsync(SelectionResult selectionResult, CancellationToken cancellationToken); + protected abstract Task<InsertionPoint> GetInsertionPointAsync(SemanticDocument document, int position, CancellationToken cancellationToken); + protected abstract Task<TriviaResult> PreserveTriviaAsync(SelectionResult selectionResult, CancellationToken cancellationToken); + protected abstract Task<SemanticDocument> ExpandAsync(SelectionResult selection, CancellationToken cancellationToken); + + protected abstract Task<GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, CancellationToken cancellationToken); + + protected abstract SyntaxToken GetMethodNameAtInvocation(IEnumerable<SyntaxNodeOrToken> methodNames); + // protected abstract IEnumerable<IFormattingRule> GetFormattingRules(Document document); + + protected abstract Task<OperationStatus> CheckTypeAsync(Document document, SyntaxNode contextNode, Location location, ITypeSymbol type, CancellationToken cancellationToken); + + public async Task<ExtractMethodResult> ExtractMethodAsync(CancellationToken cancellationToken) + { + var operationStatus = this.OriginalSelectionResult.Status; + + var analyzeResult = await AnalyzeAsync(this.OriginalSelectionResult, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + operationStatus = await CheckVariableTypesAsync(analyzeResult.Status.With(operationStatus), analyzeResult, cancellationToken).ConfigureAwait(false); + if (operationStatus.FailedWithNoBestEffortSuggestion()) + { + return new FailedExtractMethodResult(operationStatus); + } + + var insertionPoint = await GetInsertionPointAsync(analyzeResult.SemanticDocument, this.OriginalSelectionResult.OriginalSpan.Start, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + var triviaResult = await PreserveTriviaAsync(this.OriginalSelectionResult.With(insertionPoint.SemanticDocument), cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + var expandedDocument = await ExpandAsync(this.OriginalSelectionResult.With(triviaResult.SemanticDocument), cancellationToken).ConfigureAwait(false); + + var generatedCode = await GenerateCodeAsync( + insertionPoint.With(expandedDocument), + this.OriginalSelectionResult.With(expandedDocument), + analyzeResult.With(expandedDocument), + cancellationToken).ConfigureAwait(false); + + var applied = await triviaResult.ApplyAsync(generatedCode, cancellationToken).ConfigureAwait(false); + var afterTriviaRestored = applied.With(operationStatus); + cancellationToken.ThrowIfCancellationRequested(); + + if (afterTriviaRestored.Status.FailedWithNoBestEffortSuggestion()) + { + return CreateExtractMethodResult( + operationStatus, generatedCode.SemanticDocument, generatedCode.MethodNameAnnotation, generatedCode.MethodDefinitionAnnotation); + } + + var finalDocument = afterTriviaRestored.Data.Document; + finalDocument = await Formatter.FormatAsync(finalDocument, Formatter.Annotation, options: null,/* rules: GetFormattingRules(finalDocument), */cancellationToken: cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + return CreateExtractMethodResult( + operationStatus.With(generatedCode.Status), + await SemanticDocument.CreateAsync(finalDocument, cancellationToken).ConfigureAwait(false), + generatedCode.MethodNameAnnotation, + generatedCode.MethodDefinitionAnnotation); + } + + private ExtractMethodResult CreateExtractMethodResult( + OperationStatus status, SemanticDocument semanticDocument, + SyntaxAnnotation invocationAnnotation, SyntaxAnnotation methodAnnotation) + { + var newRoot = semanticDocument.Root; + var annotatedTokens = newRoot.GetAnnotatedNodesAndTokens(invocationAnnotation); + var methodDefinition = newRoot.GetAnnotatedNodesAndTokens(methodAnnotation).FirstOrDefault().AsNode(); + + return new SimpleExtractMethodResult(status, semanticDocument.Document, GetMethodNameAtInvocation(annotatedTokens), methodDefinition); + } + + private async Task<OperationStatus> CheckVariableTypesAsync( + OperationStatus status, + AnalyzerResult analyzeResult, + CancellationToken cancellationToken) + { + var document = analyzeResult.SemanticDocument; + + // sync selection result to same semantic data as analyzeResult + var firstToken = this.OriginalSelectionResult.With(document).GetFirstTokenInSelection(); + var context = firstToken.Parent; + + var result = await TryCheckVariableTypeAsync(document, context, analyzeResult.GetVariablesToMoveIntoMethodDefinition(cancellationToken), status, cancellationToken).ConfigureAwait(false); + if (!result.Item1) + { + result = await TryCheckVariableTypeAsync(document, context, analyzeResult.GetVariablesToSplitOrMoveIntoMethodDefinition(cancellationToken), result.Item2, cancellationToken).ConfigureAwait(false); + if (!result.Item1) + { + result = await TryCheckVariableTypeAsync(document, context, analyzeResult.MethodParameters, result.Item2, cancellationToken).ConfigureAwait(false); + if (!result.Item1) + { + result = await TryCheckVariableTypeAsync(document, context, analyzeResult.GetVariablesToMoveOutToCallSite(cancellationToken), result.Item2, cancellationToken).ConfigureAwait(false); + if (!result.Item1) + { + result = await TryCheckVariableTypeAsync(document, context, analyzeResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken), result.Item2, cancellationToken).ConfigureAwait(false); + if (!result.Item1) + { + return result.Item2; + } + } + } + } + } + + status = result.Item2; + + var checkedStatus = await CheckTypeAsync(document.Document, context, context.GetLocation(), analyzeResult.ReturnType, cancellationToken).ConfigureAwait(false); + return checkedStatus.With(status); + } + + private async Task<Tuple<bool, OperationStatus>> TryCheckVariableTypeAsync( + SemanticDocument document, SyntaxNode contextNode, IEnumerable<VariableInfo> variables, + OperationStatus status, CancellationToken cancellationToken) + { + if (status.FailedWithNoBestEffortSuggestion()) + { + return Tuple.Create(false, status); + } + + var location = contextNode.GetLocation(); + + foreach (var variable in variables) + { + var originalType = variable.GetVariableType(document); + var result = await CheckTypeAsync(document.Document, contextNode, location, originalType, cancellationToken).ConfigureAwait(false); + if (result.FailedWithNoBestEffortSuggestion()) + { + status = status.With(result); + return Tuple.Create(false, status); + } + } + + return Tuple.Create(true, status); + } + + public static string MakeMethodName(string prefix, string originalName) + { + var startingWithLetter = originalName.SkipWhile(c => !char.IsLetter(c)).ToArray(); + var name = startingWithLetter.Length == 0 ? originalName : new string(startingWithLetter); + + return char.IsUpper(name[0]) ? + prefix + name : + prefix + char.ToUpper(name[0]).ToString() + name.Substring(1); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus.cs new file mode 100644 index 0000000000..1abdadffd7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus.cs @@ -0,0 +1,68 @@ +// 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 Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class OperationStatus + { + public OperationStatus(OperationStatusFlag flag, string reason) + { + //Contract.ThrowIfTrue(flag.Succeeded() && flag.HasBestEffort()); + + this.Flag = flag; + this.Reasons = reason == null ? SpecializedCollections.EmptyEnumerable<string>() : SpecializedCollections.SingletonEnumerable(reason); + } + + private OperationStatus(OperationStatusFlag flag, IEnumerable<string> reasons) + { + //Contract.ThrowIfNull(reasons); + //Contract.ThrowIfTrue(flag.Succeeded() && flag.HasBestEffort()); + + this.Flag = flag; + this.Reasons = reasons; + } + + public OperationStatus With(OperationStatusFlag flag, string reason) + { + var newFlag = this.Flag | flag; + + newFlag = (this.Failed() || flag.Failed()) ? newFlag.RemoveFlag(OperationStatusFlag.Succeeded) : newFlag; + newFlag = newFlag.Succeeded() ? newFlag.RemoveFlag(OperationStatusFlag.BestEffort) : newFlag; + + var reasons = reason == null ? this.Reasons : this.Reasons.Concat(reason); + return new OperationStatus(newFlag, reasons); + } + + public OperationStatus With(OperationStatus operationStatus) + { + var newFlag = this.Flag | operationStatus.Flag; + + newFlag = (this.Failed() || operationStatus.Failed()) ? newFlag.RemoveFlag(OperationStatusFlag.Succeeded) : newFlag; + newFlag = newFlag.Succeeded() ? newFlag.RemoveFlag(OperationStatusFlag.BestEffort) : newFlag; + + var reasons = this.Reasons.Concat(operationStatus.Reasons); + return new OperationStatus(newFlag, reasons); + } + + public OperationStatus MakeFail() + { + return new OperationStatus(OperationStatusFlag.None, this.Reasons); + } + + public OperationStatus MarkSuggestion() + { + return new OperationStatus(this.Flag | OperationStatusFlag.Suggestion, this.Reasons); + } + + public OperationStatus<T> With<T>(T data) + { + return Create(this, data); + } + + public OperationStatusFlag Flag { get; } + public IEnumerable<string> Reasons { get; } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus_Statics.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus_Statics.cs new file mode 100644 index 0000000000..d15dc7388b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus_Statics.cs @@ -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. + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class OperationStatus + { + public static readonly OperationStatus Succeeded = new OperationStatus(OperationStatusFlag.Succeeded, reason: null); + public static readonly OperationStatus FailedWithUnknownReason = new OperationStatus(OperationStatusFlag.None, reason: "FeaturesResources.ExtractMethodFailedWithUnknownReasons"); + public static readonly OperationStatus OverlapsHiddenPosition = new OperationStatus(OperationStatusFlag.None, "FeaturesResources.GeneratedCodeIsOverlapping"); + + public static readonly OperationStatus NoActiveStatement = new OperationStatus(OperationStatusFlag.BestEffort, "FeaturesResources.NoActiveStatement"); + public static readonly OperationStatus ErrorOrUnknownType = new OperationStatus(OperationStatusFlag.BestEffort, "FeaturesResources.ErrorOrUnknownType"); + public static readonly OperationStatus UnsafeAddressTaken = new OperationStatus(OperationStatusFlag.BestEffort, "FeaturesResources.TheAddressOfAVariableIsUsed"); + + /// <summary> + /// create operation status with the given data + /// </summary> + public static OperationStatus<T> Create<T>(OperationStatus status, T data) + { + return new OperationStatus<T>(status, data); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus`1.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus`1.cs new file mode 100644 index 0000000000..b339377cbe --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/OperationStatus`1.cs @@ -0,0 +1,29 @@ +// 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 ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + /// <summary> + /// operation status paired with data + /// </summary> + public class OperationStatus<T> + { + public OperationStatus(OperationStatus status, T data) + { + this.Status = status; + this.Data = data; + } + + public OperationStatus Status { get; } + public T Data { get; } + + public OperationStatus<T> With(OperationStatus status) + { + return new OperationStatus<T>(status, this.Data); + } + + public OperationStatus<TNew> With<TNew>(TNew data) + { + return new OperationStatus<TNew>(this.Status, data); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ParameterStyle.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ParameterStyle.cs new file mode 100644 index 0000000000..aed67dc170 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ParameterStyle.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. + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class ParameterStyle + { + public ParameterBehavior ParameterBehavior { get; private set; } + public DeclarationBehavior DeclarationBehavior { get; private set; } + public DeclarationBehavior SaferDeclarationBehavior { get; private set; } + + public static readonly ParameterStyle None = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ParameterStyle InputOnly = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.Input, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ParameterStyle Delete = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.Delete, SaferDeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ParameterStyle MoveOut = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.MoveOut, SaferDeclarationBehavior = DeclarationBehavior.SplitOut }; + + public static readonly ParameterStyle SplitOut = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.SplitOut, SaferDeclarationBehavior = DeclarationBehavior.SplitOut }; + + public static readonly ParameterStyle MoveIn = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.MoveIn, SaferDeclarationBehavior = DeclarationBehavior.SplitIn }; + + public static readonly ParameterStyle SplitIn = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.None, DeclarationBehavior = DeclarationBehavior.SplitIn, SaferDeclarationBehavior = DeclarationBehavior.SplitIn }; + + public static readonly ParameterStyle Out = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.Out, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ParameterStyle Ref = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.Ref, DeclarationBehavior = DeclarationBehavior.None, SaferDeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ParameterStyle OutWithMoveOut = + new ParameterStyle() { ParameterBehavior = ParameterBehavior.Out, DeclarationBehavior = DeclarationBehavior.MoveOut, SaferDeclarationBehavior = DeclarationBehavior.MoveOut }; + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ReturnStyle.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ReturnStyle.cs new file mode 100644 index 0000000000..d9b7c50fcc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/ReturnStyle.cs @@ -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. + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class ReturnStyle + { + public ParameterBehavior ParameterBehavior { get; private set; } + public ReturnBehavior ReturnBehavior { get; private set; } + public DeclarationBehavior DeclarationBehavior { get; private set; } + + public static readonly ReturnStyle None = + new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.None, DeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ReturnStyle AssignmentWithInput = + new ReturnStyle() { ParameterBehavior = ParameterBehavior.Input, ReturnBehavior = ReturnBehavior.Assignment, DeclarationBehavior = DeclarationBehavior.None }; + + public static readonly ReturnStyle AssignmentWithNoInput = + new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.Assignment, DeclarationBehavior = DeclarationBehavior.SplitIn }; + + public static readonly ReturnStyle Initialization = + new ReturnStyle() { ParameterBehavior = ParameterBehavior.None, ReturnBehavior = ReturnBehavior.Initialization, DeclarationBehavior = DeclarationBehavior.SplitOut }; + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionResult.cs new file mode 100644 index 0000000000..49ca208b8d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionResult.cs @@ -0,0 +1,158 @@ +// 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.LanguageServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + /// <summary> + /// clean up this code when we do selection validator work. + /// </summary> + public abstract class SelectionResult + { + protected SelectionResult(OperationStatus status) + { + // Contract.ThrowIfNull(status); + + this.Status = status; + } + + protected SelectionResult( + OperationStatus status, + TextSpan originalSpan, + TextSpan finalSpan, + OptionSet options, + bool selectionInExpression, + SemanticDocument document, + SyntaxAnnotation firstTokenAnnotation, + SyntaxAnnotation lastTokenAnnotation) + { + this.Status = status; + + this.OriginalSpan = originalSpan; + this.FinalSpan = finalSpan; + + this.SelectionInExpression = selectionInExpression; + this.Options = options; + + this.FirstTokenAnnotation = firstTokenAnnotation; + this.LastTokenAnnotation = lastTokenAnnotation; + + this.SemanticDocument = document; + } + + protected abstract bool UnderAsyncAnonymousMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken); + + public abstract bool ContainingScopeHasAsyncKeyword(); + + public abstract SyntaxNode GetContainingScope(); + public abstract ITypeSymbol GetContainingScopeType(); + + public OperationStatus Status { get; } + public TextSpan OriginalSpan { get; } + public TextSpan FinalSpan { get; } + public OptionSet Options { get; } + public bool SelectionInExpression { get; } + public SemanticDocument SemanticDocument { get; private set; } + public SyntaxAnnotation FirstTokenAnnotation { get; } + public SyntaxAnnotation LastTokenAnnotation { get; } + + public SelectionResult With(SemanticDocument document) + { + if (this.SemanticDocument == document) + { + return this; + } + + var clone = (SelectionResult)this.MemberwiseClone(); + clone.SemanticDocument = document; + + return clone; + } + + public bool ContainsValidContext + { + get + { + return this.SemanticDocument != null; + } + } + + public SyntaxToken GetFirstTokenInSelection() + { + return this.SemanticDocument.GetTokenWithAnnotaton(this.FirstTokenAnnotation); + } + + public SyntaxToken GetLastTokenInSelection() + { + return this.SemanticDocument.GetTokenWithAnnotaton(this.LastTokenAnnotation); + } + + public TNode GetContainingScopeOf<TNode>() where TNode : SyntaxNode + { + var containingScope = this.GetContainingScope(); + return containingScope.GetAncestorOrThis<TNode>(); + } + + protected T GetFirstStatement<T>() where T : SyntaxNode + { + //Contract.ThrowIfTrue(this.SelectionInExpression); + + var token = this.GetFirstTokenInSelection(); + return token.GetAncestor<T>(); + } + + protected T GetLastStatement<T>() where T : SyntaxNode + { + //Contract.ThrowIfTrue(this.SelectionInExpression); + + var token = this.GetLastTokenInSelection(); + return token.GetAncestor<T>(); + } + + public bool ShouldPutAsyncModifier() + { + var firstToken = this.GetFirstTokenInSelection(); + var lastToken = this.GetLastTokenInSelection(); + + for (var currentToken = firstToken; + currentToken.Span.End < lastToken.SpanStart; + currentToken = currentToken.GetNextToken()) + { + // [| + // async () => await .... + // |] + // + // for the case above, even if the selection contains "await", it doesn't belong to the enclosing block + // which extract method is applied to + if (currentToken.IsAwaitKeyword() + && !UnderAsyncAnonymousMethod(currentToken, firstToken, lastToken)) + { + return true; + } + } + + return false; + } + + public bool AllowMovingDeclaration + { + get + { + return this.Options.GetOption(ExtractMethodOptions.AllowMovingDeclaration, this.SemanticDocument.Project.Language); + } + } + + public bool DontPutOutOrRefOnStruct + { + get + { + return this.Options.GetOption(ExtractMethodOptions.DontPutOutOrRefOnStruct, this.SemanticDocument.Project.Language); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.NullSelectionResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.NullSelectionResult.cs new file mode 100644 index 0000000000..59f8ff9b70 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.NullSelectionResult.cs @@ -0,0 +1,52 @@ +// 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; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public partial class SelectionValidator + { + // null object + protected class NullSelectionResult : SelectionResult + { + public NullSelectionResult() : + this(OperationStatus.FailedWithUnknownReason) + { + } + + protected NullSelectionResult(OperationStatus status) : + base(status) + { + } + + protected override bool UnderAsyncAnonymousMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken) + { + throw new InvalidOperationException(); + } + + public override bool ContainingScopeHasAsyncKeyword() + { + throw new InvalidOperationException(); + } + + public override SyntaxNode GetContainingScope() + { + throw new InvalidOperationException(); + } + + public override ITypeSymbol GetContainingScopeType() + { + throw new InvalidOperationException(); + } + } + + protected class ErrorSelectionResult : NullSelectionResult + { + public ErrorSelectionResult(OperationStatus status) : + base(status.MakeFail()) + { + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.cs new file mode 100644 index 0000000000..440d7e9207 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SelectionValidator.cs @@ -0,0 +1,185 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public abstract partial class SelectionValidator + { + protected static readonly SelectionResult NullSelection = new NullSelectionResult(); + + protected readonly SemanticDocument SemanticDocument; + protected readonly TextSpan OriginalSpan; + protected readonly OptionSet Options; + + protected SelectionValidator( + SemanticDocument document, + TextSpan textSpan, + OptionSet options) + { + //Contract.ThrowIfNull(document); + + this.SemanticDocument = document; + this.OriginalSpan = textSpan; + this.Options = options; + } + + public bool ContainsValidSelection + { + get + { + return !this.OriginalSpan.IsEmpty; + } + } + + public abstract Task<SelectionResult> GetValidSelectionAsync(CancellationToken cancellationToken); + public abstract IEnumerable<SyntaxNode> GetOuterReturnStatements(SyntaxNode commonRoot, IEnumerable<SyntaxNode> jumpsOutOfRegion); + public abstract bool IsFinalSpanSemanticallyValidSpan(SyntaxNode node, TextSpan textSpan, IEnumerable<SyntaxNode> returnStatements, CancellationToken cancellationToken); + public abstract bool ContainsNonReturnExitPointsStatements(IEnumerable<SyntaxNode> jumpsOutOfRegion); + + protected bool IsFinalSpanSemanticallyValidSpan( + SemanticModel semanticModel, TextSpan textSpan, Tuple<SyntaxNode, SyntaxNode> range, CancellationToken cancellationToken) + { + //Contract.ThrowIfNull(range); + + var controlFlowAnalysisData = semanticModel.AnalyzeControlFlow(range.Item1, range.Item2); + + // there must be no control in and out of given span + if (controlFlowAnalysisData.EntryPoints.Any()) + { + return false; + } + + // check something like continue, break, yield break, yield return, and etc + if (ContainsNonReturnExitPointsStatements(controlFlowAnalysisData.ExitPoints)) + { + return false; + } + + // okay, there is no branch out, check whether next statement can be executed normally + var returnStatements = GetOuterReturnStatements(range.Item1.GetCommonRoot(range.Item2), controlFlowAnalysisData.ExitPoints); + if (!returnStatements.Any()) + { + if (!controlFlowAnalysisData.EndPointIsReachable) + { + // REVIEW: should we just do extract method regardless or show some warning to user? + // in dev10, looks like we went ahead and did the extract method even if selection contains + // unreachable code. + } + + return true; + } + + // okay, only branch was return. make sure we have all return in the selection. (?) + if (!controlFlowAnalysisData.EndPointIsReachable) + { + return true; + } + + // there is a return statement, and current position is reachable. let's check whether this is a case where that is okay + return IsFinalSpanSemanticallyValidSpan(semanticModel.SyntaxTree.GetRoot(cancellationToken), textSpan, returnStatements, cancellationToken); + } + + protected Tuple<SyntaxNode, SyntaxNode> GetStatementRangeContainingSpan<T>( + SyntaxNode root, TextSpan textSpan, CancellationToken cancellationToken) where T : SyntaxNode + { + // use top-down approach to find smallest statement range that contains given span. + // this approach is more expansive than bottom-up approach I used before but way simpler and easy to understand + var token1 = root.FindToken(textSpan.Start); + var token2 = root.FindTokenFromEnd(textSpan.End); + + var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis<T>() ?? root; + + var firstStatement = default(T); + var lastStatement = default(T); + + var spine = new List<T>(); + + foreach (var stmt in commonRoot.DescendantNodesAndSelf().OfType<T>()) + { + cancellationToken.ThrowIfCancellationRequested(); + + // quick skip check. + // - not containing at all + if (stmt.Span.End < textSpan.Start) + { + continue; + } + + // quick exit check + // - passed candidate statements + if (textSpan.End < stmt.SpanStart) + { + break; + } + + if (stmt.SpanStart <= textSpan.Start) + { + // keep track spine + spine.Add(stmt); + } + + if (textSpan.End <= stmt.Span.End && spine.Any(s => s.Parent == stmt.Parent)) + { + // malformed code or selection can make spine to have more than an elements + firstStatement = spine.First(s => s.Parent == stmt.Parent); + lastStatement = stmt; + + spine.Clear(); + } + } + + if (firstStatement == null || lastStatement == null) + { + return null; + } + + return new Tuple<SyntaxNode, SyntaxNode>(firstStatement, lastStatement); + } + + protected Tuple<SyntaxNode, SyntaxNode> GetStatementRangeContainedInSpan<T>( + SyntaxNode root, TextSpan textSpan, CancellationToken cancellationToken) where T : SyntaxNode + { + // use top-down approach to find largest statement range contained in the given span + // this method is a bit more expensive than bottom-up approach, but way more simpler than the other approach. + var token1 = root.FindToken(textSpan.Start); + var token2 = root.FindTokenFromEnd(textSpan.End); + + var commonRoot = token1.GetCommonRoot(token2).GetAncestorOrThis<T>() ?? root; + + T firstStatement = null; + T lastStatement = null; + + foreach (var stmt in commonRoot.DescendantNodesAndSelf().OfType<T>()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (firstStatement == null && stmt.SpanStart >= textSpan.Start) + { + firstStatement = stmt; + } + + if (firstStatement != null && stmt.Span.End <= textSpan.End && stmt.Parent == firstStatement.Parent) + { + lastStatement = stmt; + } + } + + if (firstStatement == null || lastStatement == null) + { + return null; + } + + return new Tuple<SyntaxNode, SyntaxNode>(firstStatement, lastStatement); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SimpleExtractMethodResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SimpleExtractMethodResult.cs new file mode 100644 index 0000000000..4be54b61b3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/SimpleExtractMethodResult.cs @@ -0,0 +1,17 @@ +// 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; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class SimpleExtractMethodResult : ExtractMethodResult + { + public SimpleExtractMethodResult( + OperationStatus status, + Document document, + SyntaxToken invocationNameToken, + SyntaxNode methodDefinition) + : base(status.Flag, status.Reasons, document, invocationNameToken, methodDefinition) + { + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/UniqueNameGenerator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/UniqueNameGenerator.cs new file mode 100644 index 0000000000..4ee4242b76 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/UniqueNameGenerator.cs @@ -0,0 +1,28 @@ +// 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.Shared.Utilities; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class UniqueNameGenerator + { + private readonly SemanticModel _semanticModel; + + public UniqueNameGenerator(SemanticModel semanticModel) + { + //Contract.ThrowIfNull(semanticModel); + _semanticModel = semanticModel; + } + + public string CreateUniqueMethodName(SyntaxNode contextNode, string baseName) + { + //Contract.ThrowIfNull(contextNode); + //Contract.ThrowIfNull(baseName); + + return NameGenerator.GenerateUniqueName(baseName, string.Empty, + n => _semanticModel.LookupSymbols(contextNode.SpanStart, /*container*/null, n).Length == 0); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/VariableStyle.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/VariableStyle.cs new file mode 100644 index 0000000000..f648a048b3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ExtractMethod/VariableStyle.cs @@ -0,0 +1,49 @@ +// 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 ICSharpCode.NRefactory6.CSharp.ExtractMethod +{ + public class VariableStyle + { + public ParameterStyle ParameterStyle { get; private set; } + public ReturnStyle ReturnStyle { get; private set; } + + public static readonly VariableStyle None = + new VariableStyle() { ParameterStyle = ParameterStyle.None, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle InputOnly = + new VariableStyle() { ParameterStyle = ParameterStyle.InputOnly, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle Delete = + new VariableStyle() { ParameterStyle = ParameterStyle.Delete, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle MoveOut = + new VariableStyle() { ParameterStyle = ParameterStyle.MoveOut, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle SplitOut = + new VariableStyle() { ParameterStyle = ParameterStyle.SplitOut, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle MoveIn = + new VariableStyle() { ParameterStyle = ParameterStyle.MoveIn, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle SplitIn = + new VariableStyle() { ParameterStyle = ParameterStyle.SplitIn, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle NotUsed = + new VariableStyle() { ParameterStyle = ParameterStyle.MoveOut, ReturnStyle = ReturnStyle.Initialization }; + + public static readonly VariableStyle Ref = + new VariableStyle() { ParameterStyle = ParameterStyle.Ref, ReturnStyle = ReturnStyle.AssignmentWithInput }; + + public static readonly VariableStyle OnlyAsRefParam = + new VariableStyle() { ParameterStyle = ParameterStyle.Ref, ReturnStyle = ReturnStyle.None }; + + public static readonly VariableStyle Out = + new VariableStyle() { ParameterStyle = ParameterStyle.Out, ReturnStyle = ReturnStyle.AssignmentWithNoInput }; + + public static readonly VariableStyle OutWithErrorInput = + new VariableStyle() { ParameterStyle = ParameterStyle.Out, ReturnStyle = ReturnStyle.AssignmentWithInput }; + + public static readonly VariableStyle OutWithMoveOut = + new VariableStyle() { ParameterStyle = ParameterStyle.OutWithMoveOut, ReturnStyle = ReturnStyle.Initialization }; + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CSharpEditorFormattingService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CSharpEditorFormattingService.cs new file mode 100644 index 0000000000..0507520c2d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CSharpEditorFormattingService.cs @@ -0,0 +1,292 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public partial class CSharpEditorFormattingService + { + private readonly ImmutableHashSet<char> _supportedChars; + private readonly ImmutableHashSet<char> _autoFormattingTriggerChars; + private readonly ImmutableDictionary<char, ImmutableHashSet<SyntaxKind>> _multiWordsMap; + + public CSharpEditorFormattingService() + { + _autoFormattingTriggerChars = ImmutableHashSet.CreateRange<char>(";}"); + + // add all auto formatting trigger to supported char + _supportedChars = _autoFormattingTriggerChars.Union("{}#nte:)"); + + // set up multi words map + _multiWordsMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair<char, ImmutableHashSet<SyntaxKind>> ('n', ImmutableHashSet.Create(SyntaxKind.RegionKeyword, SyntaxKind.EndRegionKeyword)), + new KeyValuePair<char, ImmutableHashSet<SyntaxKind>> ('t', ImmutableHashSet.Create(SyntaxKind.SelectKeyword)), + new KeyValuePair<char, ImmutableHashSet<SyntaxKind>> ('e', ImmutableHashSet.Create(SyntaxKind.WhereKeyword)), + }); + } + + public bool SupportsFormatDocument { get { return true; } } + + public bool SupportsFormatOnPaste { get { return true; } } + + public bool SupportsFormatSelection { get { return true; } } + + public bool SupportsFormatOnReturn { get { return true; } } + + public bool SupportsFormattingOnTypedCharacter(Document document, char ch) + { + var optionsService = document.Project.Solution.Workspace.Options; + // if ((ch == '}' && !optionsService.GetOption(FeatureOnOffOptions.AutoFormattingOnCloseBrace, document.Project.Language)) || + // (ch == ';' && !optionsService.GetOption(FeatureOnOffOptions.AutoFormattingOnSemicolon, document.Project.Language))) + // { + // return false; + // } + + return _supportedChars.Contains(ch); + } + + // public async Task<IList<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken) + // { + // var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + // + // var span = textSpan.HasValue ? textSpan.Value : new TextSpan(0, root.FullSpan.Length); + // var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, span); + // return Formatter.GetFormattedTextChanges(root, new TextSpan[] { formattingSpan }, document.Project.Solution.Workspace, cancellationToken: cancellationToken); + // } + // + // public async Task<IList<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + // { + // var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + // var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, textSpan); + // var service = document.GetLanguageService<ISyntaxFormattingService>(); + // if (service == null) + // { + // return SpecializedCollections.EmptyList<TextChange>(); + // } + // + // var rules = new List<IFormattingRule>() { new PasteFormattingRule() }; + // rules.AddRange(service.GetDefaultFormattingRules()); + // + // return Formatter.GetFormattedTextChanges(root, new[] { formattingSpan }, document.Project.Solution.Workspace, rules: rules, cancellationToken: cancellationToken); + // } + // + // private IEnumerable<IFormattingRule> GetFormattingRules(Document document, int position) + // { + // var workspace = document.Project.Solution.Workspace; + // var formattingRuleFactory = workspace.Services.GetService<IHostDependentFormattingRuleFactoryService>(); + // return formattingRuleFactory.CreateRule(document, position).Concat(Formatter.GetDefaultFormattingRules(document)); + // } + // + // public async Task<IList<TextChange>> GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken) + // { + // var formattingRules = this.GetFormattingRules(document, caretPosition); + // + // // first, find the token user just typed. + // SyntaxToken token = await GetTokenBeforeTheCaretAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); + // + // if (token.IsMissing) + // { + // return null; + // } + // + // string text = null; + // if (IsInvalidToken(token, ref text)) + // { + // return null; + // } + // + // // Check to see if the token is ')' and also the parent is a using statement. If not, bail + // if (TokenShouldNotFormatOnReturn(token)) + // { + // return null; + // } + // + // // if formatting range fails, do format token one at least + // var changes = await FormatRangeAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); + // if (changes.Count > 0) + // { + // return changes; + // } + // + // // if we can't, do normal smart indentation + // return await FormatTokenAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); + // } + // + public static bool TokenShouldNotFormatOnReturn(SyntaxToken token) + { + return !token.IsKind(SyntaxKind.CloseParenToken) || !token.Parent.IsKind(SyntaxKind.UsingStatement); + } + + public static bool TokenShouldNotFormatOnTypeChar(SyntaxToken token) + { + return (token.IsKind(SyntaxKind.CloseParenToken) && !token.Parent.IsKind(SyntaxKind.UsingStatement)) || + (token.IsKind(SyntaxKind.ColonToken) && !(token.Parent.IsKind(SyntaxKind.LabeledStatement) || token.Parent.IsKind(SyntaxKind.CaseSwitchLabel) || token.Parent.IsKind(SyntaxKind.DefaultSwitchLabel))); + } + + // public async Task<IList<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int caretPosition, CancellationToken cancellationToken) + // { + // var formattingRules = this.GetFormattingRules(document, caretPosition); + // + // // first, find the token user just typed. + // SyntaxToken token = await GetTokenBeforeTheCaretAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); + // + // if (token.IsMissing || + // !ValidSingleOrMultiCharactersTokenKind(typedChar, token.Kind()) || + // token.IsKind(SyntaxKind.EndOfFileToken, SyntaxKind.None)) + // { + // return null; + // } + // + // var service = document.GetLanguageService<ISyntaxFactsService>(); + // if (service != null && service.IsInNonUserCode(token.SyntaxTree, caretPosition, cancellationToken)) + // { + // return null; + // } + // + // // Check to see if any of the below. If not, bail. + // // case 1: The token is ')' and the parent is an using statement. + // // case 2: The token is ':' and the parent is either labelled statement or case switch or default switch + // if (TokenShouldNotFormatOnTypeChar(token)) + // { + // return null; + // } + // + // // if formatting range fails, do format token one at least + // var changes = await FormatRangeAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); + // if (changes.Count > 0) + // { + // return changes; + // } + // + // // if we can't, do normal smart indentation + // return await FormatTokenAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); + // } + + public static async Task<SyntaxToken> GetTokenBeforeTheCaretAsync(Document document, int caretPosition, CancellationToken cancellationToken) + { + var tree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + var position = Math.Max(0, caretPosition - 1); + var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var token = root.FindToken(position, findInsideTrivia: true); + return token; + } + + // private async Task<IList<TextChange>> FormatTokenAsync(Document document, SyntaxToken token, IEnumerable<IFormattingRule> formattingRules, CancellationToken cancellationToken) + // { + // var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + // var formatter = CreateSmartTokenFormatter(document.Project.Solution.Workspace.Options, formattingRules, root); + // var changes = formatter.FormatToken(document.Project.Solution.Workspace, token, cancellationToken); + // return changes; + // } + // + // private ISmartTokenFormatter CreateSmartTokenFormatter(OptionSet optionSet, IEnumerable<IFormattingRule> formattingRules, SyntaxNode root) + // { + // return new SmartTokenFormatter(optionSet, formattingRules, (CompilationUnitSyntax)root); + // } + // + // private async Task<IList<TextChange>> FormatRangeAsync( + // Document document, SyntaxToken endToken, IEnumerable<IFormattingRule> formattingRules, + // CancellationToken cancellationToken) + // { + // if (!IsEndToken(endToken)) + // { + // return SpecializedCollections.EmptyList<TextChange>(); + // } + // + // var tokenRange = FormattingRangeHelper.FindAppropriateRange(endToken); + // if (tokenRange == null || tokenRange.Value.Item1.Equals(tokenRange.Value.Item2)) + // { + // return SpecializedCollections.EmptyList<TextChange>(); + // } + // + // if (IsInvalidTokenKind(tokenRange.Value.Item1) || IsInvalidTokenKind(tokenRange.Value.Item2)) + // { + // return SpecializedCollections.EmptyList<TextChange>(); + // } + // + // var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + // var formatter = new SmartTokenFormatter(document.Project.Solution.Workspace.Options, formattingRules, (CompilationUnitSyntax)root); + // + // var changes = formatter.FormatRange(document.Project.Solution.Workspace, tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); + // return changes; + // } + // + // private bool IsEndToken(SyntaxToken endToken) + // { + // if (endToken.IsKind(SyntaxKind.OpenBraceToken)) + // { + // return false; + // } + // + // return true; + // } + // + public bool ValidSingleOrMultiCharactersTokenKind(char typedChar, SyntaxKind kind) + { + ImmutableHashSet<SyntaxKind> set; + if (!_multiWordsMap.TryGetValue(typedChar, out set)) + { + // all single char token is valid + return true; + } + + return set.Contains(kind); + } + + public bool IsInvalidToken(char typedChar, SyntaxToken token) + { + string text = null; + if (IsInvalidToken(token, ref text)) + { + return true; + } + + return text[0] != typedChar; + } + + public bool IsInvalidToken(SyntaxToken token, ref string text) + { + if (IsInvalidTokenKind(token)) + { + return true; + } + + text = token.ToString(); + if (text.Length != 1) + { + return true; + } + + return false; + } + + private bool IsInvalidTokenKind(SyntaxToken token) + { + // invalid token to be formatted + return token.IsKind(SyntaxKind.None) || + token.IsKind(SyntaxKind.EndOfDirectiveToken) || + token.IsKind(SyntaxKind.EndOfFileToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CommonFormattingHelpers.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CommonFormattingHelpers.cs new file mode 100644 index 0000000000..4c3610cfc2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/CommonFormattingHelpers.cs @@ -0,0 +1,372 @@ +// 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.Text; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public static class CommonFormattingHelpers + { +// public static readonly Comparison<SuppressOperation> SuppressOperationComparer = (o1, o2) => +// { +// return o1.TextSpan.Start - o2.TextSpan.Start; +// }; +// +// public static readonly Comparison<IndentBlockOperation> IndentBlockOperationComparer = (o1, o2) => +// { +// // smaller one goes left +// var s = o1.TextSpan.Start - o2.TextSpan.Start; +// if (s != 0) +// { +// return s; +// } +// +// // bigger one goes left +// var e = o2.TextSpan.End - o1.TextSpan.End; +// if (e != 0) +// { +// return e; +// } +// +// return 0; +// }; +// +// public static IEnumerable<ValueTuple<SyntaxToken, SyntaxToken>> ConvertToTokenPairs(this SyntaxNode root, IList<TextSpan> spans) +// { +// Contract.ThrowIfNull(root); +// Contract.ThrowIfFalse(spans.Count > 0); +// +// if (spans.Count == 1) +// { +// // special case, if there is only one span, return right away +// yield return root.ConvertToTokenPair(spans[0]); +// yield break; +// } +// +// var pairs = new List<ValueTuple<SyntaxToken, SyntaxToken>>(); +// var previousOne = root.ConvertToTokenPair(spans[0]); +// +// // iterate through each spans and make sure each one doesn't overlap each other +// for (int i = 1; i < spans.Count; i++) +// { +// var currentOne = root.ConvertToTokenPair(spans[i]); +// if (currentOne.Item1.SpanStart <= previousOne.Item2.Span.End) +// { +// // oops, looks like two spans are overlapping each other. merge them +// previousOne = ValueTuple.Create(previousOne.Item1, previousOne.Item2.Span.End < currentOne.Item2.Span.End ? currentOne.Item2 : previousOne.Item2); +// continue; +// } +// +// // okay, looks like things are in good shape +// yield return previousOne; +// +// // move to next one +// previousOne = currentOne; +// } +// +// // give out the last one +// yield return previousOne; +// } +// +// public static ValueTuple<SyntaxToken, SyntaxToken> ConvertToTokenPair(this SyntaxNode root, TextSpan textSpan) +// { +// Contract.ThrowIfNull(root); +// Contract.ThrowIfTrue(textSpan.IsEmpty); +// +// var startToken = root.FindToken(textSpan.Start); +// +// // empty token, get previous non-zero length token +// if (startToken.IsMissing) +// { +// // if there is no previous token, startToken will be set to SyntaxKind.None +// startToken = startToken.GetPreviousToken(); +// } +// +// // span is on leading trivia +// if (textSpan.Start < startToken.SpanStart) +// { +// // if there is no previous token, startToken will be set to SyntaxKind.None +// startToken = startToken.GetPreviousToken(); +// } +// +// // adjust position where we try to search end token +// var endToken = (root.FullSpan.End <= textSpan.End) ? +// root.GetLastToken(includeZeroWidth: true) : root.FindToken(textSpan.End); +// +// // empty token, get next token +// if (endToken.IsMissing) +// { +// endToken = endToken.GetNextToken(); +// } +// +// // span is on trailing trivia +// if (endToken.Span.End < textSpan.End) +// { +// endToken = endToken.GetNextToken(); +// } +// +// // make sure tokens are not SyntaxKind.None +// startToken = (startToken.RawKind != 0) ? startToken : root.GetFirstToken(includeZeroWidth: true); +// endToken = (endToken.RawKind != 0) ? endToken : root.GetLastToken(includeZeroWidth: true); +// +// // token is in right order +// Contract.ThrowIfFalse(startToken.Equals(endToken) || startToken.Span.End <= endToken.SpanStart); +// return ValueTuple.Create(startToken, endToken); +// } +// +// public static bool IsInvalidTokenRange(this SyntaxNode root, SyntaxToken startToken, SyntaxToken endToken) +// { +// // given token must be token exist excluding EndOfFile token. +// if (startToken.RawKind == 0 || endToken.RawKind == 0) +// { +// return true; +// } +// +// if (startToken.Equals(endToken)) +// { +// return false; +// } +// +// // regular case. +// // start token can't be end of file token and start token must be before end token if it's not the same token. +// return root.FullSpan.End == startToken.SpanStart || startToken.FullSpan.End > endToken.FullSpan.Start; +// } +// +// public static int GetTokenColumn(this SyntaxTree tree, SyntaxToken token, int tabSize) +// { +// Contract.ThrowIfNull(tree); +// Contract.ThrowIfTrue(token.RawKind == 0); +// +// var startPosition = token.SpanStart; +// var line = tree.GetText().Lines.GetLineFromPosition(startPosition); +// +// return line.GetColumnFromLineOffset(startPosition - line.Start, tabSize); +// } +// +// public static string GetText(this SourceText text, SyntaxToken token1, SyntaxToken token2) +// { +// return (token1.RawKind == 0) ? text.ToString(TextSpan.FromBounds(0, token2.SpanStart)) : text.ToString(TextSpan.FromBounds(token1.Span.End, token2.SpanStart)); +// } +// + public static string GetTextBetween(SyntaxToken token1, SyntaxToken token2) + { + var builder = new StringBuilder(); + AppendTextBetween(token1, token2, builder); + + return builder.ToString(); + } + + public static void AppendTextBetween(SyntaxToken token1, SyntaxToken token2, StringBuilder builder) + { +// Contract.ThrowIfTrue(token1.RawKind == 0 && token2.RawKind == 0); +// Contract.ThrowIfTrue(token1.Equals(token2)); +// + if (token1.RawKind == 0) + { + AppendLeadingTriviaText(token2, builder); + return; + } + + if (token2.RawKind == 0) + { + AppendTrailingTriviaText(token1, builder); + return; + } + + var token1PartOftoken2LeadingTrivia = token1.FullSpan.Start > token2.FullSpan.Start; + + if (token1.FullSpan.End == token2.FullSpan.Start) + { + AppendTextBetweenTwoAdjacentTokens(token1, token2, builder); + return; + } + + AppendTrailingTriviaText(token1, builder); + + for (var token = token1.GetNextToken(includeZeroWidth: true); token.FullSpan.End <= token2.FullSpan.Start; token = token.GetNextToken(includeZeroWidth: true)) + { + builder.Append(token.ToFullString()); + } + + AppendPartialLeadingTriviaText(token2, builder, token1.TrailingTrivia.FullSpan.End); + } + + private static void AppendTextBetweenTwoAdjacentTokens(SyntaxToken token1, SyntaxToken token2, StringBuilder builder) + { + AppendTrailingTriviaText(token1, builder); + AppendLeadingTriviaText(token2, builder); + } + + private static void AppendLeadingTriviaText(SyntaxToken token, StringBuilder builder) + { + if (!token.HasLeadingTrivia) + { + return; + } + + foreach (var trivia in token.LeadingTrivia) + { + builder.Append(trivia.ToFullString()); + } + } + + /// <summary> + /// If the token1 is expected to be part of the leading trivia of the token2 then the trivia + /// before the token1FullSpanEnd, which the fullspan end of the token1 should be ignored + /// </summary> + private static void AppendPartialLeadingTriviaText(SyntaxToken token, StringBuilder builder, int token1FullSpanEnd) + { + if (!token.HasLeadingTrivia) + { + return; + } + + foreach (var trivia in token.LeadingTrivia) + { + if (trivia.FullSpan.End <= token1FullSpanEnd) + { + continue; + } + + builder.Append(trivia.ToFullString()); + } + } + + private static void AppendTrailingTriviaText(SyntaxToken token, StringBuilder builder) + { + if (!token.HasTrailingTrivia) + { + return; + } + + foreach (var trivia in token.TrailingTrivia) + { + builder.Append(trivia.ToFullString()); + } + } + +// /// <summary> +// /// this will create a span that includes its trailing trivia of its previous token and leading trivia of its next token +// /// for example, for code such as "class A { int ...", if given tokens are "A" and "{", this will return span [] of "class[ A { ]int ..." +// /// which included trailing trivia of "class" which is previous token of "A", and leading trivia of "int" which is next token of "{" +// /// </summary> +// public static TextSpan GetSpanIncludingTrailingAndLeadingTriviaOfAdjacentTokens(SyntaxToken startToken, SyntaxToken endToken) +// { +// // most of cases we can just ask previous and next token to create the span, but in some corner cases such as omitted token case, +// // those navigation function doesn't work, so we have to explore the tree ourselves to create rigth span +// var startPosition = GetStartPositionOfSpan(startToken); +// var endPosition = GetEndPositionOfSpan(endToken); +// +// return TextSpan.FromBounds(startPosition, endPosition); +// } +// +// private static int GetEndPositionOfSpan(SyntaxToken token) +// { +// var nextToken = token.GetNextToken(); +// if (nextToken.RawKind != 0) +// { +// return nextToken.SpanStart; +// } +// +// var backwardPosition = token.FullSpan.End; +// var parentNode = GetParentThatContainsGivenSpan(token.Parent, backwardPosition, forward: false); +// if (parentNode == null) +// { +// // reached the end of tree +// return token.FullSpan.End; +// } +// +// Contract.ThrowIfFalse(backwardPosition < parentNode.FullSpan.End); +// +// nextToken = parentNode.FindToken(backwardPosition + 1); +// +// Contract.ThrowIfTrue(nextToken.RawKind == 0); +// +// return nextToken.SpanStart; +// } +// +// public static int GetStartPositionOfSpan(SyntaxToken token) +// { +// var previousToken = token.GetPreviousToken(); +// if (previousToken.RawKind != 0) +// { +// return previousToken.Span.End; +// } +// +// // first token in the tree +// var forwardPosition = token.FullSpan.Start; +// if (forwardPosition <= 0) +// { +// return 0; +// } +// +// var parentNode = GetParentThatContainsGivenSpan(token.Parent, forwardPosition, forward: true); +// if (parentNode == null) +// { +// return Contract.FailWithReturn<int>("This can't happen"); +// } +// +// Contract.ThrowIfFalse(parentNode.FullSpan.Start < forwardPosition); +// +// previousToken = parentNode.FindToken(forwardPosition + 1); +// +// Contract.ThrowIfTrue(previousToken.RawKind == 0); +// +// return previousToken.Span.End; +// } +// +// private static SyntaxNode GetParentThatContainsGivenSpan(SyntaxNode node, int position, bool forward) +// { +// while (node != null) +// { +// var fullSpan = node.FullSpan; +// if (forward) +// { +// if (fullSpan.Start < position) +// { +// return node; +// } +// } +// else +// { +// if (position > fullSpan.End) +// { +// return node; +// } +// } +// +// node = node.Parent; +// } +// +// return null; +// } +// +// public static bool HasAnyWhitespaceElasticTrivia(SyntaxToken previousToken, SyntaxToken currentToken) +// { +// if ((!previousToken.ContainsAnnotations && !currentToken.ContainsAnnotations) || +// (!previousToken.HasTrailingTrivia && !currentToken.HasLeadingTrivia)) +// { +// return false; +// } +// +// return previousToken.TrailingTrivia.HasAnyWhitespaceElasticTrivia() || currentToken.LeadingTrivia.HasAnyWhitespaceElasticTrivia(); +// } +// +// public static bool IsNull<T>(T t) where T : class +// { +// return t == null; +// } +// +// public static bool IsNotNull<T>(T t) where T : class +// { +// return !IsNull(t); +// } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingHelpers.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingHelpers.cs new file mode 100644 index 0000000000..357f4e46aa --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingHelpers.cs @@ -0,0 +1,552 @@ +// 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 Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public static class FormattingHelpers + { + // TODO: Need to determine correct way to handle newlines + public const string NewLine = "\r\n"; + + public static string GetIndent(this SyntaxToken token) + { + var precedingTrivia = token.GetAllPrecedingTriviaToPreviousToken(); + + // indent is the spaces/tabs between last new line (if there is one) and end of trivia + var indent = precedingTrivia.AsString(); + int lastNewLinePos = indent.LastIndexOf(NewLine); + if (lastNewLinePos != -1) + { + int start = lastNewLinePos + NewLine.Length; + indent = indent.Substring(start, indent.Length - start); + } + + return indent; + } + + public static string ContentBeforeLastNewLine(this IEnumerable<SyntaxTrivia> trivia) + { + var leading = trivia.AsString(); + int lastNewLinePos = leading.LastIndexOf(NewLine); + if (lastNewLinePos == -1) + { + return string.Empty; + } + else + { + return leading.Substring(0, lastNewLinePos); + } + } + + public static ValueTuple<SyntaxToken, SyntaxToken> GetBracePair(this SyntaxNode node) + { + return node.GetBraces(); + } + + public static bool IsValidBracePair(this ValueTuple<SyntaxToken, SyntaxToken> bracePair) + { + if (bracePair.Item1.IsKind(SyntaxKind.None) || + bracePair.Item1.IsMissing || + bracePair.Item2.IsKind(SyntaxKind.None)) + { + return false; + } + + // don't check whether token is actually braces as long as it is not none. + return true; + } + + public static bool IsOpenParenInParameterList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.OpenParenToken && token.Parent.Kind() == SyntaxKind.ParameterList; + } + + public static bool IsCloseParenInParameterList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CloseParenToken && token.Parent.Kind() == SyntaxKind.ParameterList; + } + + public static bool IsOpenParenInArgumentList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.OpenParenToken && token.Parent.Kind() == SyntaxKind.ArgumentList; + } + + public static bool IsCloseParenInArgumentList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CloseParenToken && token.Parent.Kind() == SyntaxKind.ArgumentList; + } + + public static bool IsColonInTypeBaseList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.ColonToken && token.Parent.Kind() == SyntaxKind.BaseList; + } + + public static bool IsCommaInArgumentOrParameterList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CommaToken && (token.Parent.IsAnyArgumentList() || token.Parent.Kind() == SyntaxKind.ParameterList); + } + + public static bool IsLambdaBodyBlock(this SyntaxNode node) + { + if (node.Kind() != SyntaxKind.Block) + { + return false; + } + + return node.Parent.Kind() == SyntaxKind.SimpleLambdaExpression || + node.Parent.Kind() == SyntaxKind.ParenthesizedLambdaExpression; + } + + public static bool IsAnonymousMethodBlock(this SyntaxNode node) + { + if (node.Kind() != SyntaxKind.Block) + { + return false; + } + + return node.Parent.Kind() == SyntaxKind.AnonymousMethodExpression; + } + + public static bool IsSemicolonInForStatement(this SyntaxToken token) + { + var forStatement = token.Parent as ForStatementSyntax; + return + token.Kind() == SyntaxKind.SemicolonToken && + forStatement != null && + (forStatement.FirstSemicolonToken == token || forStatement.SecondSemicolonToken == token); + } + + public static bool IsSemicolonOfEmbeddedStatement(this SyntaxToken token) + { + if (token.Kind() != SyntaxKind.SemicolonToken) + { + return false; + } + + var statement = token.Parent as StatementSyntax; + if (statement == null || + statement.GetLastToken() != token) + { + return false; + } + + return IsEmbeddedStatement(statement); + } + + public static bool IsCloseBraceOfExpression(this SyntaxToken token) + { + if (token.Kind() != SyntaxKind.CloseBraceToken) + { + return false; + } + + return token.Parent is ExpressionSyntax; + } + + public static bool IsCloseBraceOfEmbeddedBlock(this SyntaxToken token) + { + if (token.Kind() != SyntaxKind.CloseBraceToken) + { + return false; + } + + var block = token.Parent as BlockSyntax; + if (block == null || + block.CloseBraceToken != token) + { + return false; + } + + return IsEmbeddedStatement(block); + } + + public static bool IsEmbeddedStatement(this SyntaxNode node) + { + SyntaxNode statementOrElse = node as StatementSyntax; + if (statementOrElse == null) + { + statementOrElse = node as ElseClauseSyntax; + } + + return statementOrElse != null + && statementOrElse.Parent != null + && statementOrElse.Parent.IsEmbeddedStatementOwner(); + } + + public static bool IsCommaInEnumDeclaration(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CommaToken && + token.Parent.IsKind(SyntaxKind.EnumDeclaration); + } + + public static bool IsCommaInAnyArgumentsList(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CommaToken && + token.Parent.IsAnyArgumentList(); + } + + public static bool IsParenInParenthesizedExpression(this SyntaxToken token) + { + var parenthesizedExpression = token.Parent as ParenthesizedExpressionSyntax; + if (parenthesizedExpression == null) + { + return false; + } + + return parenthesizedExpression.OpenParenToken.Equals(token) || parenthesizedExpression.CloseParenToken.Equals(token); + } + + public static bool IsParenInArgumentList(this SyntaxToken token) + { + var parent = token.Parent; + switch (parent.Kind()) + { + case SyntaxKind.SizeOfExpression: + var sizeOfExpression = (SizeOfExpressionSyntax)parent; + return sizeOfExpression.OpenParenToken == token || sizeOfExpression.CloseParenToken == token; + + case SyntaxKind.TypeOfExpression: + var typeOfExpression = (TypeOfExpressionSyntax)parent; + return typeOfExpression.OpenParenToken == token || typeOfExpression.CloseParenToken == token; + + case SyntaxKind.CheckedExpression: + case SyntaxKind.UncheckedExpression: + var checkedOfExpression = (CheckedExpressionSyntax)parent; + return checkedOfExpression.OpenParenToken == token || checkedOfExpression.CloseParenToken == token; + + case SyntaxKind.DefaultExpression: + var defaultExpression = (DefaultExpressionSyntax)parent; + return defaultExpression.OpenParenToken == token || defaultExpression.CloseParenToken == token; + + case SyntaxKind.MakeRefExpression: + var makeRefExpression = (MakeRefExpressionSyntax)parent; + return makeRefExpression.OpenParenToken == token || makeRefExpression.CloseParenToken == token; + + case SyntaxKind.RefTypeExpression: + var refTypeOfExpression = (RefTypeExpressionSyntax)parent; + return refTypeOfExpression.OpenParenToken == token || refTypeOfExpression.CloseParenToken == token; + + case SyntaxKind.RefValueExpression: + var refValueExpression = (RefValueExpressionSyntax)parent; + return refValueExpression.OpenParenToken == token || refValueExpression.CloseParenToken == token; + + case SyntaxKind.ArgumentList: + var argumentList = (ArgumentListSyntax)parent; + return argumentList.OpenParenToken == token || argumentList.CloseParenToken == token; + + case SyntaxKind.AttributeArgumentList: + var attributeArgumentList = (AttributeArgumentListSyntax)parent; + return attributeArgumentList.OpenParenToken == token || attributeArgumentList.CloseParenToken == token; + } + + return false; + } + + public static bool IsCloseParenInStatement(this SyntaxToken token) + { + var statement = token.Parent as StatementSyntax; + if (statement == null) + { + return false; + } + + var ifStatement = statement as IfStatementSyntax; + if (ifStatement != null) + { + return ifStatement.CloseParenToken.Equals(token); + } + + var switchStatement = statement as SwitchStatementSyntax; + if (switchStatement != null) + { + return switchStatement.CloseParenToken.Equals(token); + } + + var whileStatement = statement as WhileStatementSyntax; + if (whileStatement != null) + { + return whileStatement.CloseParenToken.Equals(token); + } + + var doStatement = statement as DoStatementSyntax; + if (doStatement != null) + { + return doStatement.CloseParenToken.Equals(token); + } + + var forStatement = statement as ForStatementSyntax; + if (forStatement != null) + { + return forStatement.CloseParenToken.Equals(token); + } + + var foreachStatement = statement as ForEachStatementSyntax; + if (foreachStatement != null) + { + return foreachStatement.CloseParenToken.Equals(token); + } + + var lockStatement = statement as LockStatementSyntax; + if (lockStatement != null) + { + return lockStatement.CloseParenToken.Equals(token); + } + + var usingStatement = statement as UsingStatementSyntax; + if (usingStatement != null) + { + return usingStatement.CloseParenToken.Equals(token); + } + + return false; + } + + public static bool IsDotInMemberAccessOrQualifiedName(this SyntaxToken token) + { + return token.IsDotInMemberAccess() || (token.Kind() == SyntaxKind.DotToken && token.Parent.Kind() == SyntaxKind.QualifiedName); + } + + public static bool IsDotInMemberAccess(this SyntaxToken token) + { + var memberAccess = token.Parent as MemberAccessExpressionSyntax; + if (memberAccess == null) + { + return false; + } + + return token.Kind() == SyntaxKind.DotToken + && memberAccess.OperatorToken.Equals(token); + } + + public static bool IsGenericGreaterThanToken(this SyntaxToken token) + { + if (token.Kind() == SyntaxKind.GreaterThanToken) + { + return token.Parent.IsKind(SyntaxKind.TypeParameterList, SyntaxKind.TypeArgumentList); + } + + return false; + } + + public static bool IsCommaInInitializerExpression(this SyntaxToken token) + { + return token.Kind() == SyntaxKind.CommaToken && + ((token.Parent is InitializerExpressionSyntax) || + (token.Parent is AnonymousObjectCreationExpressionSyntax)); + } + + public static bool IsIdentiferInLabeledStatement(this SyntaxToken token) + { + var labeledStatement = token.Parent as LabeledStatementSyntax; + return token.Kind() == SyntaxKind.IdentifierToken && + labeledStatement != null && + labeledStatement.Identifier == token; + } + + public static bool IsColonInSwitchLabel(this SyntaxToken token) + { + return FormattingRangeHelper.IsColonInSwitchLabel(token); + } + + public static bool IsColonInLabeledStatement(this SyntaxToken token) + { + var labeledStatement = token.Parent as LabeledStatementSyntax; + return token.Kind() == SyntaxKind.ColonToken && + labeledStatement != null && + labeledStatement.ColonToken == token; + } + + public static bool IsEmbeddedStatementOwnerWithCloseParen(this SyntaxNode node) + { + return node is IfStatementSyntax || + node is WhileStatementSyntax || + node is ForStatementSyntax || + node is ForEachStatementSyntax || + node is UsingStatementSyntax; + } + + public static bool IsNestedQueryExpression(this SyntaxToken token) + { + var fromClause = token.Parent as FromClauseSyntax; + return token.Kind() == SyntaxKind.InKeyword && + fromClause != null && + fromClause.Expression is QueryExpressionSyntax; + } + + public static bool IsFirstFromKeywordInExpression(this SyntaxToken token) + { + var queryExpression = token.Parent.Parent as QueryExpressionSyntax; + return token.Kind() == SyntaxKind.FromKeyword && + queryExpression != null && + queryExpression.GetFirstToken().Equals(token); + } + + public static bool IsInitializerForObjectOrAnonymousObjectCreationExpression(this SyntaxNode node) + { + var initializer = node as InitializerExpressionSyntax; + AnonymousObjectMemberDeclaratorSyntax anonymousObjectInitializer = null; + if (initializer == null) + { + anonymousObjectInitializer = node as AnonymousObjectMemberDeclaratorSyntax; + if (anonymousObjectInitializer == null) + { + return false; + } + } + + var parent = initializer != null ? initializer.Parent : anonymousObjectInitializer.Parent; + if (parent is AnonymousObjectCreationExpressionSyntax) + { + return true; + } + + if (parent is ObjectCreationExpressionSyntax) + { + if (initializer.Expressions.Count <= 0) + { + return true; + } + + var expression = initializer.Expressions[0]; + if (expression.Kind() == SyntaxKind.SimpleAssignmentExpression) + { + return true; + } + } + + return false; + } + + public static bool IsInitializerForArrayOrCollectionCreationExpression(this SyntaxNode node) + { + var initializer = node as InitializerExpressionSyntax; + AnonymousObjectMemberDeclaratorSyntax anonymousObjectInitializer = null; + if (initializer == null) + { + anonymousObjectInitializer = node as AnonymousObjectMemberDeclaratorSyntax; + if (anonymousObjectInitializer == null) + { + return false; + } + } + + var parent = initializer != null ? initializer.Parent : anonymousObjectInitializer.Parent; + if (parent is ArrayCreationExpressionSyntax || + parent is ImplicitArrayCreationExpressionSyntax || + parent is EqualsValueClauseSyntax || + parent.Kind() == SyntaxKind.SimpleAssignmentExpression) + { + return true; + } + + if (parent is ObjectCreationExpressionSyntax) + { + return !IsInitializerForObjectOrAnonymousObjectCreationExpression(initializer); + } + + return false; + } + + public static bool ParenOrBracketContainsNothing(this SyntaxToken token1, SyntaxToken token2) + { + return (token1.Kind() == SyntaxKind.OpenParenToken && token2.Kind() == SyntaxKind.CloseParenToken) || + (token1.Kind() == SyntaxKind.OpenBracketToken && token2.Kind() == SyntaxKind.CloseBracketToken); + } + + public static bool IsLastTokenInLabelStatement(this SyntaxToken token) + { + if (token.Kind() != SyntaxKind.SemicolonToken && token.Kind() != SyntaxKind.CloseBraceToken) + { + return false; + } + + if (token.Parent == null) + { + return false; + } + + return token.Parent.Parent is LabeledStatementSyntax; + } + + public static ValueTuple<SyntaxToken, SyntaxToken> GetFirstAndLastMemberDeclarationTokensAfterAttributes(this MemberDeclarationSyntax node) + { + // Contract.ThrowIfNull(node); + + // there are no attributes associated with the node. return back first and last token of the node. + var attributes = node.GetAttributes(); + if (attributes.Count == 0) + { + return ValueTuple.Create(node.GetFirstToken(includeZeroWidth: true), node.GetLastToken(includeZeroWidth: true)); + } + + var lastToken = node.GetLastToken(includeZeroWidth: true); + var lastAttributeToken = attributes.Last().GetLastToken(includeZeroWidth: true); + if (lastAttributeToken.Equals(lastToken)) + { + return ValueTuple.Create(default(SyntaxToken), default(SyntaxToken)); + } + + var firstTokenAfterAttribute = lastAttributeToken.GetNextToken(includeZeroWidth: true); + + // there are attributes, get first token after the tokens belong to attributes + return ValueTuple.Create(firstTokenAfterAttribute, lastToken); + } + + public static bool IsBlockBody(this SyntaxNode node) + { + // Contract.ThrowIfNull(node); + + var blockNode = node as BlockSyntax; + if (blockNode == null) + { + return false; + } + + switch (blockNode.Parent.Kind()) + { + case SyntaxKind.AnonymousMethodExpression: + case SyntaxKind.CheckedStatement: + case SyntaxKind.UncheckedStatement: + case SyntaxKind.UnsafeStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.CatchClause: + case SyntaxKind.FinallyClause: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.OperatorDeclaration: + case SyntaxKind.ConversionOperatorDeclaration: + case SyntaxKind.ConstructorDeclaration: + case SyntaxKind.DestructorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + case SyntaxKind.UnknownAccessorDeclaration: + return true; + default: + return false; + } + } + + public static bool IsPlusOrMinusExpression(this SyntaxToken token) + { + if (token.Kind() != SyntaxKind.PlusToken && token.Kind() != SyntaxKind.MinusToken) + { + return false; + } + + return token.Parent is PrefixUnaryExpressionSyntax; + } + + public static bool IsInterpolation(this SyntaxToken currentToken) + { + return currentToken.Parent.IsKind(SyntaxKind.Interpolation); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingOptionsFactory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingOptionsFactory.cs new file mode 100644 index 0000000000..950f735934 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingOptionsFactory.cs @@ -0,0 +1,170 @@ +// +// FormattingOptionsFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Formatting; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// The formatting options factory creates pre defined formatting option styles. + /// </summary> + public static class FormattingOptionsFactory + { + readonly static Workspace defaultWs = new TestWorkspace (); + + internal class TestWorkspace : Workspace + { + readonly static HostServices services = Microsoft.CodeAnalysis.Host.Mef.MefHostServices.DefaultHost; + public TestWorkspace(string workspaceKind = "Test") : base(services , workspaceKind) + { + } + + } +// /// <summary> +// /// Creates empty CSharpFormatting options. +// /// </summary> +// public static CSharpFormattingOptions CreateEmpty() +// { +// return new CSharpFormattingOptions(); +// } + + /// <summary> + /// Creates mono indent style CSharpFormatting options. + /// </summary> + public static OptionSet CreateMono() + { + var options = defaultWs.Options; + options = options.WithChangedOption(CSharpFormattingOptions.SpaceAfterMethodCallName, true); + options = options.WithChangedOption(CSharpFormattingOptions.SpaceAfterSemicolonsInForStatement, true); + + options = options.WithChangedOption(CSharpFormattingOptions.NewLineForCatch, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false); + + options = options.WithChangedOption(CSharpFormattingOptions.IndentSwitchSection, false); + + options = options.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, true); + options = options.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, 4); + options = options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n"); + + return options; + } + + /// <summary> + /// Creates sharp develop indent style CSharpFormatting options. + /// </summary> + public static OptionSet CreateSharpDevelop() + { + var baseOptions = CreateKRStyle(); + return baseOptions; + } + + /// <summary> + /// The K&R style, so named because it was used in Kernighan and Ritchie's book The C Programming Language, + /// is commonly used in C. It is less common for C++, C#, and others. + /// </summary> + public static OptionSet CreateKRStyle() + { + var options = defaultWs.Options; + options = options.WithChangedOption(CSharpFormattingOptions.NewLineForCatch, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false); + options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false); + + options = options.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, true); + options = options.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, 4); + options = options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n"); + + return options; + } + + /// <summary> + /// Creates allman indent style CSharpFormatting options used in Visual Studio. + /// </summary> + public static OptionSet CreateAllman() + { + var options = defaultWs.Options; + options = options.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, true); + options = options.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, 4); + options = options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n"); + return options; + } + +// /// <summary> +// /// The Whitesmiths style, also called Wishart style to a lesser extent, is less common today than the previous three. It was originally used in the documentation for the first commercial C compiler, the Whitesmiths Compiler. +// /// </summary> +// public static CSharpFormattingOptions CreateWhitesmiths() +// { +// var baseOptions = CreateKRStyle(); +// +// baseOptions.NamespaceBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.ClassBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.InterfaceBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.StructBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.EnumBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.MethodBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.ConstructorBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.DestructorBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.AnonymousMethodBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.PropertyBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.PropertyGetBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.PropertySetBraceStyle = BraceStyle.NextLineShifted; +// +// baseOptions.EventBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.EventAddBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.EventRemoveBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.StatementBraceStyle = BraceStyle.NextLineShifted; +// baseOptions.IndentBlocksInsideExpressions = true; +// return baseOptions; +// } +// +// /// <summary> +// /// Like the Allman and Whitesmiths styles, GNU style puts braces on a line by themselves, indented by 2 spaces, +// /// except when opening a function definition, where they are not indented. +// /// In either case, the contained code is indented by 2 spaces from the braces. +// /// Popularised by Richard Stallman, the layout may be influenced by his background of writing Lisp code. +// /// In Lisp the equivalent to a block (a progn) +// /// is a first class data entity and giving it its own indent level helps to emphasize that, +// /// whereas in C a block is just syntax. +// /// Although not directly related to indentation, GNU coding style also includes a space before the bracketed +// /// list of arguments to a function. +// /// </summary> +// public static CSharpFormattingOptions CreateGNU() +// { +// var baseOptions = CreateAllman(); +// baseOptions.StatementBraceStyle = BraceStyle.NextLineShifted2; +// return baseOptions; +// } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingRangeHelper.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingRangeHelper.cs new file mode 100644 index 0000000000..a8a7cb7b91 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/FormattingRangeHelper.cs @@ -0,0 +1,434 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// this help finding a range of tokens to format based on given ending token + /// </summary> + public static class FormattingRangeHelper + { + public static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRange(SyntaxToken endToken, bool useDefaultRange = true) + { + // Contract.ThrowIfTrue(endToken.Kind() == SyntaxKind.None); + + return FixupOpenBrace(FindAppropriateRangeWorker(endToken, useDefaultRange)); + } + + private static ValueTuple<SyntaxToken, SyntaxToken>? FixupOpenBrace(ValueTuple<SyntaxToken, SyntaxToken>? tokenRange) + { + if (!tokenRange.HasValue) + { + return tokenRange; + } + + // with a auto brace completion which will do auto formatting when a user types "{", it is quite common that we will automatically put a space + // between "{" and "}". but user might blindly type without knowing that " " has automatically inserted for him. and ends up have two spaces. + // for those cases, whenever we see previous token of the range is "{", we expand the range to include preceeding "{" + var currentToken = tokenRange.Value.Item1; + var previousToken = currentToken.GetPreviousToken(); + + while (currentToken.Kind() != SyntaxKind.CloseBraceToken && previousToken.Kind() == SyntaxKind.OpenBraceToken) + { + var pair = previousToken.Parent.GetBracePair(); + if (pair.Item2.Kind() == SyntaxKind.None || !AreTwoTokensOnSameLine(previousToken, pair.Item2)) + { + return ValueTuple.Create(currentToken, tokenRange.Value.Item2); + } + + currentToken = previousToken; + previousToken = currentToken.GetPreviousToken(); + } + + return ValueTuple.Create(currentToken, tokenRange.Value.Item2); + } + + private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeWorker(SyntaxToken endToken, bool useDefaultRange) + { + // special token that we know how to find proper starting token + switch (endToken.Kind()) + { + case SyntaxKind.CloseBraceToken: + { + return FindAppropriateRangeForCloseBrace(endToken); + } + + case SyntaxKind.SemicolonToken: + { + return FindAppropriateRangeForSemicolon(endToken); + } + + case SyntaxKind.ColonToken: + { + return FindAppropriateRangeForColon(endToken); + } + + default: + { + // default case + if (!useDefaultRange) + { + return null; + } + + // if given token is skipped token, don't bother to find appropriate + // starting point + if (endToken.Kind() == SyntaxKind.SkippedTokensTrivia) + { + return null; + } + + var parent = endToken.Parent; + if (parent == null) + { + // if there is no parent setup yet, nothing we can do here. + return null; + } + + // if we are called due to things in trivia or literals, don't bother + // finding a starting token + if (parent.Kind() == SyntaxKind.StringLiteralExpression || + parent.Kind() == SyntaxKind.CharacterLiteralExpression) + { + return null; + } + + // format whole node that containing the end token + its previous one + // to do indentation + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + } + } + + private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForSemicolon(SyntaxToken endToken) + { + var parent = endToken.Parent; + if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia) + { + return null; + } + + if ((parent is UsingDirectiveSyntax) || + (parent is DelegateDeclarationSyntax) || + (parent is FieldDeclarationSyntax) || + (parent is EventFieldDeclarationSyntax) || + (parent is MethodDeclarationSyntax)) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken()); + } + + if (parent is AccessorDeclarationSyntax) + { + // if both accessors are on the same line, format the accessor list + // { get; set; } + var propertyDeclaration = GetEnclosingMember(endToken) as PropertyDeclarationSyntax; + if (propertyDeclaration != null && + AreTwoTokensOnSameLine(propertyDeclaration.AccessorList.OpenBraceToken, propertyDeclaration.AccessorList.CloseBraceToken)) + { + return ValueTuple.Create(propertyDeclaration.AccessorList.OpenBraceToken, propertyDeclaration.AccessorList.CloseBraceToken); + } + + // otherwise, just format the accessor + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken()); + } + + if (parent is StatementSyntax && !endToken.IsSemicolonInForStatement()) + { + var container = GetTopContainingNode(parent); + if (container == null) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + + if (IsSpecialContainingNode(container)) + { + return ValueTuple.Create(GetAppropriatePreviousToken(container.GetFirstToken()), container.GetLastToken()); + } + + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken()); + } + + // don't do anything + return null; + } + + private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForCloseBrace(SyntaxToken endToken) + { + // don't do anything if there is no proper parent + var parent = endToken.Parent; + if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia) + { + return null; + } + + // cases such as namespace, type, enum, method almost any top level elements + if (parent is MemberDeclarationSyntax || + parent is SwitchStatementSyntax) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + + // property decl body or initializer + if (parent is AccessorListSyntax) + { + // include property decl + var containerOfList = parent.Parent; + if (containerOfList == null) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + + return ValueTuple.Create(containerOfList.GetFirstToken(), containerOfList.GetLastToken()); + } + + if (parent is AnonymousObjectCreationExpressionSyntax) + { + return ValueTuple.Create(parent.GetFirstToken(), parent.GetLastToken()); + } + + if (parent is InitializerExpressionSyntax) + { + var parentOfParent = parent.Parent; + if (parentOfParent == null) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + + // double initializer case such as + // { { } + var doubleInitializer = parentOfParent as InitializerExpressionSyntax; + if (doubleInitializer != null) + { + // if parent block has a missing brace, and current block is on same line, then + // don't try to indent inner block. + var firstTokenOfInnerBlock = parent.GetFirstToken(); + var lastTokenOfInnerBlock = parent.GetLastToken(); + + var twoTokensOnSameLine = AreTwoTokensOnSameLine(firstTokenOfInnerBlock, lastTokenOfInnerBlock); + if (twoTokensOnSameLine) + { + return ValueTuple.Create(firstTokenOfInnerBlock, lastTokenOfInnerBlock); + } + } + + // include owner of the initializer node such as creation node + return ValueTuple.Create(parentOfParent.GetFirstToken(), parentOfParent.GetLastToken()); + } + + if (parent is BlockSyntax) + { + var containerOfBlock = GetTopContainingNode(parent); + if (containerOfBlock == null) + { + return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken()); + } + + // things like method, constructor, etc and special cases + if (containerOfBlock is MemberDeclarationSyntax || + IsSpecialContainingNode(containerOfBlock)) + { + return ValueTuple.Create(GetAppropriatePreviousToken(containerOfBlock.GetFirstToken()), containerOfBlock.GetLastToken()); + } + + // double block case on single line case + // { { } + if (containerOfBlock is BlockSyntax) + { + // if parent block has a missing brace, and current block is on same line, then + // don't try to indent inner block. + var firstTokenOfInnerBlock = parent.GetFirstToken(); + var lastTokenOfInnerBlock = parent.GetLastToken(); + + var twoTokensOnSameLine = AreTwoTokensOnSameLine(firstTokenOfInnerBlock, lastTokenOfInnerBlock); + if (twoTokensOnSameLine) + { + return ValueTuple.Create(firstTokenOfInnerBlock, lastTokenOfInnerBlock); + } + } + + // okay, for block, indent regardless whether it is first one on the line + return ValueTuple.Create(GetPreviousTokenIfNotFirstTokenInTree(parent.GetFirstToken()), parent.GetLastToken()); + } + + // don't do anything + return null; + } + + private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForColon(SyntaxToken endToken) + { + // don't do anything if there is no proper parent + var parent = endToken.Parent; + if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia) + { + return null; + } + + // cases such as namespace, type, enum, method almost any top level elements + if (IsColonInSwitchLabel(endToken)) + { + return ValueTuple.Create(GetPreviousTokenIfNotFirstTokenInTree(parent.GetFirstToken()), parent.GetLastToken()); + } + + return null; + } + + private static SyntaxToken GetPreviousTokenIfNotFirstTokenInTree(SyntaxToken token) + { + var previousToken = token.GetPreviousToken(); + return previousToken.Kind() == SyntaxKind.None ? token : previousToken; + } + + private static bool AreTwoTokensOnSameLine(SyntaxToken token1, SyntaxToken token2) + { + var tree = token1.SyntaxTree; + var text = default(SourceText); + if (tree != null && tree.TryGetText(out text)) + { + var line1 = text.Lines.IndexOf(token1.Span.End); + var line2 = text.Lines.IndexOf(token2.SpanStart); + + return line1 == line2; + } + + return CommonFormattingHelpers.GetTextBetween(token1, token2).ContainsLineBreak(); + } + + private static SyntaxToken GetAppropriatePreviousToken(SyntaxToken startToken, bool canTokenBeFirstInABlock = false) + { + var previousToken = startToken.GetPreviousToken(); + if (previousToken.Kind() == SyntaxKind.None) + { + // no previous token, return as it is + return startToken; + } + + if (AreTwoTokensOnSameLine(previousToken, startToken)) + { + // The previous token can be '{' of a block and type declaration + // { int s = 0; + if (canTokenBeFirstInABlock) + { + if (IsOpenBraceTokenOfABlockOrTypeOrNamespace(previousToken)) + { + return previousToken; + } + } + + // there is another token on same line. + return startToken; + } + + // start token is the first token on line + + // now check a special case where previous token belongs to a label. + if (previousToken.IsLastTokenInLabelStatement()) + { + var labelNode = previousToken.Parent.Parent; + return GetAppropriatePreviousToken(labelNode.GetFirstToken()); + } + + return previousToken; + } + + private static bool IsOpenBraceTokenOfABlockOrTypeOrNamespace(SyntaxToken previousToken) + { + return previousToken.IsKind(SyntaxKind.OpenBraceToken) && + (previousToken.Parent.IsKind(SyntaxKind.Block) || + previousToken.Parent is TypeDeclarationSyntax || + previousToken.Parent is NamespaceDeclarationSyntax); + } + + private static bool IsSpecialContainingNode(SyntaxNode node) + { + return + node.Kind() == SyntaxKind.IfStatement || + node.Kind() == SyntaxKind.ElseClause || + node.Kind() == SyntaxKind.WhileStatement || + node.Kind() == SyntaxKind.ForStatement || + node.Kind() == SyntaxKind.ForEachStatement || + node.Kind() == SyntaxKind.UsingStatement || + node.Kind() == SyntaxKind.DoStatement || + node.Kind() == SyntaxKind.TryStatement || + node.Kind() == SyntaxKind.CatchClause || + node.Kind() == SyntaxKind.FinallyClause || + node.Kind() == SyntaxKind.LabeledStatement; + } + + private static SyntaxNode GetTopContainingNode(SyntaxNode node) + { + node = node.Parent; + if (!IsSpecialContainingNode(node)) + { + return node; + } + + var lastSpecialContainingNode = node; + node = node.Parent; + + while (node != null) + { + if (!IsSpecialContainingNode(node)) + { + return lastSpecialContainingNode; + } + + lastSpecialContainingNode = node; + node = node.Parent; + } + + return null; + } + + public static bool IsColonInSwitchLabel(SyntaxToken token) + { + var switchLabel = token.Parent as SwitchLabelSyntax; + return token.Kind() == SyntaxKind.ColonToken && + switchLabel != null && + switchLabel.ColonToken == token; + } + + public static bool InBetweenTwoMembers(SyntaxToken previousToken, SyntaxToken currentToken) + { + if (previousToken.Kind() != SyntaxKind.SemicolonToken && previousToken.Kind() != SyntaxKind.CloseBraceToken) + { + return false; + } + + if (currentToken.Kind() == SyntaxKind.CloseBraceToken) + { + return false; + } + + var previousMember = GetEnclosingMember(previousToken); + var nextMember = GetEnclosingMember(currentToken); + + return previousMember != null + && nextMember != null + && previousMember != nextMember; + } + + public static MemberDeclarationSyntax GetEnclosingMember(SyntaxToken token) + { + if (token.Kind() == SyntaxKind.CloseBraceToken) + { + if (token.Parent.Kind() == SyntaxKind.Block || + token.Parent.Kind() == SyntaxKind.AccessorList) + { + return token.Parent.Parent as MemberDeclarationSyntax; + } + } + + return token.Parent.FirstAncestorOrSelf<MemberDeclarationSyntax>(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/Indent.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/Indent.cs new file mode 100644 index 0000000000..8945bcb262 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/Formatter/Indent.cs @@ -0,0 +1,249 @@ +// +// Indent.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public enum IndentType + { + Block, + DoubleBlock, + Continuation, + Alignment, + Label, + Empty + } + + public class Indent + { + readonly CloneableStack<IndentType> indentStack = new CloneableStack<IndentType>(); + readonly OptionSet options; + int curIndent; + int extraSpaces; + string indentString; + + public int CurIndent { + get { + return curIndent; + } + } + + public Indent(OptionSet options) + { + this.options = options; + Reset(); + } + + Indent(Indent engine) + { + this.indentStack = engine.indentStack.Clone(); + this.options = engine.options; + this.curIndent = engine.curIndent; + this.extraSpaces = engine.extraSpaces; + this.indentString = engine.indentString; + } + + public Indent Clone() + { + return new Indent(this); + } + + public void Reset() + { + curIndent = 0; + indentString = ""; + indentStack.Clear(); + } + + public void Push(IndentType type) + { + indentStack.Push(type); + curIndent += GetIndent(type); + Update(); + } + + public void Push(Indent indent) + { + foreach (var i in indent.indentStack) + Push(i); + } + + public void Pop() + { + curIndent -= GetIndent(indentStack.Pop()); + Update(); + } + + public bool PopIf(IndentType type) + { + if (Count > 0 && Peek() == type) + { + Pop(); + return true; + } + + return false; + } + + public void PopWhile(IndentType type) + { + while (Count > 0 && Peek() == type) + { + Pop(); + } + } + + public bool PopTry() + { + if (Count > 0) + { + Pop(); + return true; + } + + return false; + } + + public int Count { + get { + return indentStack.Count; + } + } + + public IndentType Peek() + { + return indentStack.Peek(); + } + + int GetIndent(IndentType indentType) + { + switch (indentType) { + case IndentType.Block: + return options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp); + case IndentType.DoubleBlock: + return options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp) * 2; + case IndentType.Alignment: + case IndentType.Continuation: + return options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp); + case IndentType.Label: + return options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp); + case IndentType.Empty: + return 0; + default: + throw new ArgumentOutOfRangeException(); + } + } + + void Update() + { + if (!options.GetOption(FormattingOptions.UseTabs, LanguageNames.CSharp)) { + indentString = new string(' ', curIndent + ExtraSpaces); + return; + } + var tabSize = options.GetOption(FormattingOptions.TabSize, LanguageNames.CSharp); + indentString = new string('\t', curIndent / tabSize) + new string(' ', curIndent % tabSize) + new string(' ', ExtraSpaces); + } + + public int ExtraSpaces { + get { + return extraSpaces; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException("ExtraSpaces >= 0 but was " + value); + extraSpaces = value; + Update(); + } + } + + + public string IndentString { + get { + return indentString; + } + } + + public override string ToString() + { + return string.Format("[Indent: curIndent={0}]", curIndent); + } + + public Indent GetIndentWithoutSpace () + { + var result = new Indent(options); + foreach (var i in indentStack) + result.Push(i); + return result; + } + + public static Indent ConvertFrom(string indentString, Indent correctIndent, OptionSet options = null) + { + options = options ?? correctIndent.options; + var result = new Indent(options); + + var indent = string.Concat(indentString.Where(c => c == ' ' || c == '\t')); + var indentTypes = new Stack<IndentType>(correctIndent.indentStack); + + foreach (var _ in indent.TakeWhile(c => c == '\t')) + { + if (indentTypes.Count > 0) + result.Push(indentTypes.Pop()); + else + result.Push(IndentType.Continuation); + } + + result.ExtraSpaces = indent + .SkipWhile(c => c == '\t') + .TakeWhile(c => c == ' ') + .Count(); + + return result; + } + + public void RemoveAlignment() + { + ExtraSpaces = 0; + if (Count > 0 && Peek() == IndentType.Alignment) + Pop(); + } + + public void SetAlignment(int i, bool forceSpaces = false) + { + var alignChars = Math.Max(0, i); + if (forceSpaces) { + ExtraSpaces = alignChars; + return; + } + RemoveAlignment(); + Push(IndentType.Alignment); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/FrameworkLookup/FrameworkLookup.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/FrameworkLookup/FrameworkLookup.cs new file mode 100644 index 0000000000..eaeba76ab5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/FrameworkLookup/FrameworkLookup.cs @@ -0,0 +1,490 @@ +// +// FrameworkLookup.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory6.Semantics; +using ICSharpCode.NRefactory6.TypeSystem; +using ICSharpCode.NRefactory6.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + /// <summary> + /// The framework lookup provides a fast lookup where an unknow type or extension method may be defined in. + /// </summary> + public sealed class FrameworkLookup + { + /* Binary format: + * [Header] + * [Version] : [Major (byte)] [Minor (byte)] [Build (byte)] + * [#Types (int)] + * [#Methods (int)] + * [#Assemblies (int)] + * [AssemblyListTable] : #Assemblies x [OffsetToAssemblyLists (int)] + * [TypeLookupTable] : #Types x ( [NameHash (int)] [AssemblyPtrToAssemblyListTable (ushort)] + * [ExtMethodLookupTable] : #Methods x ( [NameHash (int)] [AssemblyPtrToAssemblyListTable (ushort)] + * [AssemblyLists] + * [#Count (byte)] + * #Count x [AssemblyLookup] : [Package (string)] [FullName (string)] [Namespace (string)] + */ + const int headerSize = + 3 + // Version + 4 + // #Types + 4 + // #Methods + 4 // #Assembly + /* + 4*/; + + public static readonly Version CurrentVersion = new Version (2, 0, 1); + public static readonly FrameworkLookup Empty = new FrameworkLookup (); + + string fileName; + int[] assemblyListTable; + int[] typeLookupTable; + int[] extLookupTable; + + /// <summary> + /// This method tries to get a matching extension method. + /// </summary> + /// <returns>The extension method lookups.</returns> + /// <param name="resolveResult">The resolve result.</param> + public IEnumerable<AssemblyLookup> GetExtensionMethodLookups (UnknownMemberResolveResult resolveResult) + { + return GetLookup (resolveResult.MemberName, extLookupTable, headerSize + assemblyListTable.Length * 4 + typeLookupTable.Length * 8); + } + + /// <summary> + /// Tries to get a type out of an unknow identifier result. + /// </summary> + /// <returns>The assemblies the type may be defined (if any).</returns> + /// <param name="resolveResult">The resolve result.</param> + /// <param name="typeParameterCount">Type parameter count.</param> + /// <param name="isInsideAttributeType">If set to <c>true</c> this resolve result may be inside an attribute.</param> + public IEnumerable<AssemblyLookup> GetLookups (UnknownIdentifierResolveResult resolveResult, int typeParameterCount, bool isInsideAttributeType) + { + string name = isInsideAttributeType ? resolveResult.Identifier + "Attribute" : resolveResult.Identifier; + + var identifier = GetIdentifier (name, typeParameterCount); + return GetLookup (identifier, typeLookupTable, headerSize + assemblyListTable.Length * 4); + } + + /// <summary> + /// Tries to get a type out of an unknow member resolve result. (In case of fully qualified names) + /// </summary> + /// <returns>The assemblies the type may be defined (if any).</returns> + /// <param name="resolveResult">The resolve result.</param> + /// <param name="fullMemberName"></param> + /// <param name="typeParameterCount">Type parameter count.</param> + /// <param name="isInsideAttributeType">If set to <c>true</c> this resolve result may be inside an attribute.</param> + public IEnumerable<AssemblyLookup> GetLookups (UnknownMemberResolveResult resolveResult, string fullMemberName, int typeParameterCount, bool isInsideAttributeType) + { + string name = isInsideAttributeType ? resolveResult.MemberName + "Attribute" : resolveResult.MemberName; + + var identifier = GetIdentifier (name, typeParameterCount); + foreach (var lookup in GetLookup (identifier, typeLookupTable, headerSize + assemblyListTable.Length * 4)) { + if (fullMemberName.StartsWith (lookup.Namespace, StringComparison.Ordinal)) + yield return lookup; + } + } + + /// <summary> + /// The assembly lookup determines where a type might be defined. + /// It contains the assembly & the namespace. + /// </summary> + public struct AssemblyLookup + { + readonly string nspace; + + /// <summary> + /// The namespace the requested type is in. + /// </summary> + public string Namespace { + get { + return nspace; + } + } + + readonly string fullName; + /// <summary> + /// Gets the full name af the assembly. + /// </summary> + public string FullName { + get { + return fullName; + } + } + + readonly string package; + /// <summary> + /// Gets the package the assembly is in. + /// </summary> + public string Package { + get { + return package; + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="AssemblyLookup"/> struct. + /// </summary> + /// <param name="package">The package name.</param> + /// <param name="fullName">The full name of the assembly.</param> + /// <param name="nspace">The namespace the type is in.</param> + internal AssemblyLookup (string package, string fullName, string nspace) + { + if (nspace == null) + throw new ArgumentNullException ("nspace"); + if (fullName == null) + throw new ArgumentNullException ("fullName"); + this.package = package; + this.fullName = fullName; + this.nspace = nspace; + } + + public override string ToString () + { + return string.Format ("[AssemblyLookup: Namespace={0}, FullName={1}, Package={2}]", Namespace, FullName, Package); + } + + public override bool Equals (object obj) + { + if (obj == null) + return false; + // if (ReferenceEquals (this, obj)) + // return true; + if (obj.GetType () != typeof(AssemblyLookup)) + return false; + var other = (AssemblyLookup)obj; + return Namespace == other.Namespace && FullName == other.FullName && Package == other.Package; + } + + public override int GetHashCode () + { + unchecked { + return (Namespace != null ? Namespace.GetHashCode () : 0) ^ + (FullName != null ? FullName.GetHashCode () : 0) ^ + (Package != null ? Package.GetHashCode () : 0); + } + } + } + + /// <summary> + /// This method returns a new framework builder to build a new framework lookup data file. + /// </summary> + /// <param name="fileName">The file name of the data file.</param> + public static FrameworkBuilder Create (string fileName) + { + return new FrameworkBuilder (fileName); + } + + /// <summary> + /// Loads a framework lookup object from a file. May return null, if the file wasn't found or has a version mismatch. + /// </summary> + /// <param name="fileName">File name.</param> + public static FrameworkLookup Load (string fileName) + { + try { + if (!File.Exists (fileName)) + return null; + } catch (Exception) { + return null; + } + var result = new FrameworkLookup (); + result.fileName = fileName; + var fs = File.OpenRead (fileName); + using (var reader = new BinaryReader (fs, Encoding.UTF8)) { + var major = reader.ReadByte (); + var minor = reader.ReadByte (); + var build = reader.ReadByte (); + var version = new Version (major, minor, build); + if (version != CurrentVersion) + return null; + int typeLookupListCount = reader.ReadInt32 (); + int extLookupListCount = reader.ReadInt32 (); + int assemblyLookupCount = reader.ReadInt32 (); + + result.assemblyListTable = new int[assemblyLookupCount]; + for (int i = 0; i < assemblyLookupCount; i++) { + result.assemblyListTable[i] = reader.ReadInt32 (); + } + + result.typeLookupTable = new int[typeLookupListCount]; + for (int i = 0; i < typeLookupListCount; i++) { + result.typeLookupTable [i] = reader.ReadInt32 (); + // skip list offset + reader.ReadInt32 (); + } + + result.extLookupTable = new int[extLookupListCount]; + for (int i = 0; i < extLookupListCount; i++) { + result.extLookupTable [i] = reader.ReadInt32 (); + // skip list offset + reader.ReadInt32 (); + } + } + return result; + } + + FrameworkLookup () + { + } + + IEnumerable<AssemblyLookup> GetLookup (string identifier, int[] lookupTable, int tableOffset) + { + if (lookupTable == null) + yield break; + + int index = Array.BinarySearch (lookupTable, GetStableHashCode (identifier)); + if (index < 0) + yield break; + + using (var reader = new BinaryReader (File.Open (fileName, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)) { + reader.BaseStream.Seek (tableOffset + index * 8 + 4, SeekOrigin.Begin); + int listPtr = reader.ReadInt32 (); + + reader.BaseStream.Seek (listPtr, SeekOrigin.Begin); + var b = reader.ReadInt32 (); + var assemblies = new List<ushort> (); + while (b-- > 0) { + var assembly = reader.ReadUInt16 (); + if (assembly < 0 || assembly >= assemblyListTable.Length) + throw new InvalidDataException ("Assembly lookup was " + assembly + " but only " + assemblyListTable.Length + " are known."); + assemblies.Add (assembly); + } + foreach (var assembly in assemblies) { + reader.BaseStream.Seek (assemblyListTable [assembly], SeekOrigin.Begin); + + var package = reader.ReadString (); + var fullName = reader.ReadString (); + var ns = reader.ReadString (); + yield return new AssemblyLookup (package, fullName, ns); + } + } + } + + /// <summary> + /// Retrieves a hash code for the specified string that is stable across + /// .NET upgrades. + /// + /// Use this method instead of the normal <c>string.GetHashCode</c> if the hash code + /// is persisted to disk. + /// </summary> + static int GetStableHashCode(string text) + { + unchecked { + int h = 0; + foreach (char c in text) { + h = (h << 5) - h + c; + } + return h; + } + } + + static string GetIdentifier (string identifier, int tc) + { + if (tc == 0) + return identifier; + return identifier + "`" + tc; + } + + public class FrameworkBuilder : IDisposable + { + readonly string fileName; + + Dictionary<int, List<ushort>> typeLookup = new Dictionary<int, List<ushort>> (); + Dictionary<int, List<ushort>> extensionMethodLookup = new Dictionary<int, List<ushort>> (); + List<AssemblyLookup> assemblyLookups = new List<AssemblyLookup> (); + Dictionary<int, string> methodCheck = new Dictionary<int, string> (); + Dictionary<int, string> typeCheck = new Dictionary<int, string> (); + + internal FrameworkBuilder (string fileName) + { + this.fileName = fileName; + } + + static int[] WriteTable (MemoryStream stream, Dictionary<int, List<ushort>> table, out List<KeyValuePair<int, List<ushort>>> list) + { + list = new List<KeyValuePair<int, List<ushort>>> (table); + list.Sort ((x, y) => x.Key.CompareTo (y.Key)); + + var result = new int[list.Count]; + using (var bw = new BinaryWriter (stream)) { + for (int i = 0; i < result.Length; i++) { + result [i] = (int)stream.Length; + bw.Write (list [i].Value.Count); + foreach (var ii in list [i].Value) + bw.Write (ii); + } + } + + return result; + } + + #region IDisposable implementation + + void IDisposable.Dispose () + { + var typeLookupMemory = new MemoryStream (); + List<KeyValuePair<int, List<ushort>>> typeLookupList; + var typeTable = WriteTable (typeLookupMemory, typeLookup, out typeLookupList); + + var extMethodLookupMemory = new MemoryStream (); + List<KeyValuePair<int, List<ushort>>> extMethodLookuplist; + var extMethodTable = WriteTable (extMethodLookupMemory, extensionMethodLookup, out extMethodLookuplist); + + var assemblyLookupMemory = new MemoryStream (); + var assemblyPositionTable = new int[assemblyLookups.Count]; + using (var writer = new BinaryWriter (assemblyLookupMemory, Encoding.UTF8)) { + for (int i = 0; i < assemblyLookups.Count; i++) { + var lookup = assemblyLookups[i]; + assemblyPositionTable[i] = (int)assemblyLookupMemory.Length; + writer.Write (lookup.Package); + writer.Write (lookup.FullName); + writer.Write (lookup.Namespace); + } + } + + using (var stream = new BinaryWriter (File.OpenWrite (fileName), Encoding.UTF8)) { + stream.Write ((byte)CurrentVersion.Major); + stream.Write ((byte)CurrentVersion.Minor); + stream.Write ((byte)CurrentVersion.Build); + + stream.Write (typeLookupList.Count); + stream.Write (extMethodLookuplist.Count); + stream.Write (assemblyLookups.Count); + + var typeBuffer = typeLookupMemory.ToArray (); + var extMethodBuffer = extMethodLookupMemory.ToArray (); + + int dataOffset = + headerSize + + assemblyLookups.Count * 4 + + typeLookupList.Count * (4 + 4) + + extMethodLookuplist.Count * (4 + 4); + + for (int i = 0; i < assemblyLookups.Count; i++) { + stream.Write ((int)(dataOffset + typeBuffer.Length + extMethodBuffer.Length + assemblyPositionTable[i])); + } + + for (int i = 0; i < typeLookupList.Count; i++) { + stream.Write (typeLookupList [i].Key); + stream.Write (dataOffset + typeTable[i]); + } + + for (int i = 0; i < extMethodLookuplist.Count; i++) { + stream.Write (extMethodLookuplist [i].Key); + stream.Write (dataOffset + typeBuffer.Length + extMethodTable[i]); + } + + stream.Write (typeBuffer); + stream.Write (extMethodBuffer); + stream.Write (assemblyLookupMemory.ToArray ()); + stream.Flush (); + } + } + #endregion + + struct FrameworkLookupId + { + public string PackageName; + public string AssemblyName; + public string NameSpace; + } + + Dictionary<FrameworkLookupId, ushort> frameworkLookupTable = new Dictionary<FrameworkLookupId, ushort> (); + ushort GetLookup (string packageName, string assemblyName, string ns) + { + var id = new FrameworkLookupId { + PackageName = packageName, + AssemblyName = assemblyName, + NameSpace = ns + }; + ushort value; + if (frameworkLookupTable.TryGetValue (id, out value)) + return value; + + var result = new AssemblyLookup (packageName, assemblyName, ns); + assemblyLookups.Add (result); + var index = assemblyLookups.Count - 1; + if (index > ushort.MaxValue) + throw new InvalidOperationException ("Assembly lookup list overflow > " + ushort.MaxValue + " assemblies."); + frameworkLookupTable.Add (id, (ushort)index); + return (ushort)index; + } + + bool AddToTable (string packageName, string assemblyName, Dictionary<int, List<ushort>> table, Dictionary<int, string> checkTable, string id, string ns) + { + List<ushort> list; + var hash = GetStableHashCode (id); + + if (!table.TryGetValue (hash, out list)) { + list = new List<ushort> (); + table [hash] = list; + } else { + string existingString; + if (checkTable.TryGetValue (hash, out existingString)) { + if (existingString != id) + throw new InvalidOperationException ("Duplicate hash for " + existingString + " and "+ id); + } else { + checkTable.Add (hash, id); + } + } + var assemblyLookup = GetLookup (packageName, assemblyName, ns); + if (!list.Any (a => a.Equals (assemblyLookup))) { + list.Add (assemblyLookup); + return true; + } + return false; + } + + /// <summary> + /// Add a type to the framework lookup. + /// </summary> + /// <param name="packageName">The package the assembly of the type is defined (can be null).</param> + /// <param name="fullAssemblyName">The full assembly name the type is defined (needs to be != null).</param> + /// <param name="type">The type definition (needs to be != null).</param> + public void AddLookup (string packageName, string fullAssemblyName, IUnresolvedTypeDefinition type) + { + if (fullAssemblyName == null) + throw new ArgumentNullException ("fullAssemblyName"); + if (type == null) + throw new ArgumentNullException ("type"); + var id = GetIdentifier (type.Name, type.TypeParameters.Count); + if (AddToTable (packageName, fullAssemblyName, typeLookup, typeCheck, id, type.Namespace)) { + if (type.IsSealed || type.IsStatic) { + foreach (var method in type.Methods) { + var m = method as DefaultUnresolvedMethod; + if (m == null || !m.IsExtensionMethod) + continue; + AddToTable (packageName, fullAssemblyName, extensionMethodLookup, methodCheck, method.Name, method.DeclaringTypeDefinition.Namespace); + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractCodeRefactoringResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractCodeRefactoringResult.cs new file mode 100644 index 0000000000..761a71fe6e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractCodeRefactoringResult.cs @@ -0,0 +1,31 @@ +// 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.CodeRefactorings; +using ICSharpCode.NRefactory6.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers +{ + public abstract class AbstractCodeRefactoringResult + { + private readonly CodeRefactoring _codeRefactoring; + + protected AbstractCodeRefactoringResult(CodeRefactoring codeRefactoring) + { + _codeRefactoring = codeRefactoring; + } + + public bool ContainsChanges + { + get + { + return _codeRefactoring != null; + } + } + + public CodeRefactoring GetCodeRefactoring(CancellationToken cancellationToken) + { + return _codeRefactoring; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractGenerateFromMembersService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractGenerateFromMembersService.cs new file mode 100644 index 0000000000..bfe16dad94 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/AbstractGenerateFromMembersService.cs @@ -0,0 +1,160 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers +{ + public abstract class AbstractGenerateFromMembersService<TMemberDeclarationSyntax> + where TMemberDeclarationSyntax : SyntaxNode + { + protected AbstractGenerateFromMembersService() + { + } + + protected abstract Task<IList<TMemberDeclarationSyntax>> GetSelectedMembersAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); + protected abstract IEnumerable<ISymbol> GetDeclaredSymbols(SemanticModel semanticModel, TMemberDeclarationSyntax memberDeclaration, CancellationToken cancellationToken); + + protected class SelectedMemberInfo + { + public INamedTypeSymbol ContainingType; + public IList<TMemberDeclarationSyntax> SelectedDeclarations; + public IList<ISymbol> SelectedMembers; + } + + protected async Task<SelectedMemberInfo> GetSelectedMemberInfoAsync( + Document document, TextSpan textSpan, CancellationToken cancellationToken) + { + var selectedDeclarations = await this.GetSelectedMembersAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + + if (selectedDeclarations.Count > 0) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var selectedMembers = selectedDeclarations.SelectMany( + d => this.GetDeclaredSymbols(semanticModel, d, cancellationToken)).WhereNotNull().ToList(); + if (selectedMembers.Count > 0) + { + var containingType = selectedMembers.First().ContainingType; + if (containingType != null) + { + return new SelectedMemberInfo { ContainingType = containingType, SelectedDeclarations = selectedDeclarations, SelectedMembers = selectedMembers }; + } + } + } + + return null; + } + + protected static bool IsWritableInstanceFieldOrProperty(ISymbol symbol) + { + // Can use non const fields and properties with setters in them. + return + IsInstanceFieldOrProperty(symbol) && + IsWritableFieldOrProperty(symbol); + } + + private static bool IsWritableFieldOrProperty(ISymbol symbol) + { + return symbol.TypeSwitch( + (IFieldSymbol field) => !field.IsConst, + (IPropertySymbol property) => property.SetMethod != null); + } + + protected static bool IsInstanceFieldOrProperty(ISymbol symbol) + { + return !symbol.IsStatic && (IsField(symbol) || IsProperty(symbol)); + } + + private static bool IsProperty(ISymbol symbol) + { + return symbol.Kind == SymbolKind.Property; + } + + private static bool IsField(ISymbol symbol) + { + return symbol.Kind == SymbolKind.Field; + } + + protected CodeRefactoring CreateCodeRefactoring( + IList<TMemberDeclarationSyntax> selectedDeclarations, + IEnumerable<CodeAction> actions) + { + #if false + var lastDeclaration = selectedDeclarations.Last(); + var endSpan = new TextSpan(lastDeclaration.Span.End - 1, 1); + return new CodeRefactoring(actions, endSpan); + #endif + return new CodeRefactoring(null, actions); + } + + protected List<IParameterSymbol> DetermineParameters( + IList<ISymbol> selectedMembers) + { + var parameters = new List<IParameterSymbol>(); + + foreach (var symbol in selectedMembers) + { + var type = symbol is IFieldSymbol + ? ((IFieldSymbol)symbol).Type + : ((IPropertySymbol)symbol).Type; + + parameters.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( + attributes: null, + refKind: RefKind.None, + isParams: false, + type: type, + name: symbol.Name.ToCamelCase())); + } + + return parameters; + } + + protected IMethodSymbol GetDelegatedConstructor( + INamedTypeSymbol containingType, + List<IParameterSymbol> parameters) + { + var q = + from c in containingType.InstanceConstructors + orderby c.Parameters.Length descending + where c.Parameters.Length > 0 && c.Parameters.Length < parameters.Count + where c.Parameters.All(p => p.RefKind == RefKind.None) && !c.Parameters.Any(p => p.IsParams) + let constructorTypes = c.Parameters.Select(p => p.Type) + let symbolTypes = parameters.Take(c.Parameters.Length).Select(p => p.Type) + where constructorTypes.SequenceEqual(symbolTypes) + select c; + + return q.FirstOrDefault(); + } + + protected bool HasMatchingConstructor( + INamedTypeSymbol containingType, + List<IParameterSymbol> parameters) + { + return containingType.InstanceConstructors.Any(c => MatchesConstructor(c, parameters)); + } + + private bool MatchesConstructor( + IMethodSymbol constructor, + List<IParameterSymbol> parameters) + { + return parameters.Select(p => p.Type).SequenceEqual(constructor.Parameters.Select(p => p.Type)); + } + + protected static readonly SymbolDisplayFormat SimpleFormat = + new SymbolDisplayFormat( + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + parameterOptions: SymbolDisplayParameterOptions.IncludeParamsRefOut | SymbolDisplayParameterOptions.IncludeType, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/AbstractGenerateConstructorService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/AbstractGenerateConstructorService.cs new file mode 100644 index 0000000000..f8e2bcfd82 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -0,0 +1,267 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers.GenerateConstructor +{ + public abstract partial class AbstractGenerateConstructorService<TService, TMemberDeclarationSyntax> : + AbstractGenerateFromMembersService<TMemberDeclarationSyntax> + where TService : AbstractGenerateConstructorService<TService, TMemberDeclarationSyntax> + where TMemberDeclarationSyntax : SyntaxNode + { + protected AbstractGenerateConstructorService() + { + } + + public async Task<GenerateConstructorResult> GenerateConstructorAsync( + Document document, TextSpan textSpan, CancellationToken cancellationToken) + { +// using (Logger.LogBlock(FunctionId.Refactoring_GenerateFromMembers_GenerateConstructor, cancellationToken)) +// { + var info = await GetSelectedMemberInfoAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + if (info != null) + { + var state = State.Generate((TService)this, document, textSpan, info.ContainingType, info.SelectedMembers, cancellationToken); + if (state != null) + { + return new GenerateConstructorResult( + CreateCodeRefactoring(info.SelectedDeclarations, GetCodeActions(document, state))); + } + } + + return GenerateConstructorResult.Failure; +// } + } + + private IEnumerable<CodeAction> GetCodeActions(Document document, State state) + { + yield return new FieldDelegatingCodeAction((TService)this, document, state); + if (state.DelegatedConstructor != null) + { + yield return new ConstructorDelegatingCodeAction((TService)this, document, state); + } + } + + private class ConstructorDelegatingCodeAction : CodeAction + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + + public ConstructorDelegatingCodeAction( + TService service, + Document document, + State state) + { + _service = service; + _document = document; + _state = state; + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + // First, see if there are any constructors that would take the first 'n' arguments + // we've provided. If so, delegate to those, and then create a field for any + // remaining arguments. Try to match from largest to smallest. + // + // Otherwise, just generate a normal constructor that assigns any provided + // parameters into fields. + var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.ContainingType.Language); + var factory = provider.GetService<SyntaxGenerator>(); + + var thisConstructorArguments = _state.DelegatedConstructor.Parameters.Select (par => factory.Argument (par.RefKind, SyntaxFactory.IdentifierName (par.Name))).ToList (); + var statements = new List<SyntaxNode>(); + + for (var i = _state.DelegatedConstructor.Parameters.Length; i < _state.Parameters.Count; i++) + { + var symbolName = _state.SelectedMembers[i].Name; + var parameterName = _state.Parameters[i].Name; + var assignExpression = factory.AssignmentStatement( + factory.MemberAccessExpression( + factory.ThisExpression(), + factory.IdentifierName(symbolName)), + factory.IdentifierName(parameterName)); + + var expressionStatement = factory.ExpressionStatement(assignExpression); + statements.Add(expressionStatement); + } + + var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var codeGenerationService = new CSharpCodeGenerationService (_document.Project.Solution.Workspace.Services.GetLanguageServices (LanguageNames.CSharp)); + var result = await codeGenerationService.AddMethodAsync( + _document.Project.Solution, + _state.ContainingType, + CodeGenerationSymbolFactory.CreateConstructorSymbol( + attributes: null, + accessibility: Accessibility.Public, + modifiers: new DeclarationModifiers(), + typeName: _state.ContainingType.Name, + parameters: _state.Parameters, + statements: statements, + thisConstructorArguments: thisConstructorArguments), + new CodeGenerationOptions(contextLocation: syntaxTree.GetLocation(_state.TextSpan), generateDefaultAccessibility: false), + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + return result; + } + + public override string Title + { + get + { + // var symbolDisplayService = _document.GetLanguageService<ISymbolDisplayService>(); + var parameters = _state.Parameters.Select(p => p.ToDisplayString(SimpleFormat)); + var parameterString = string.Join(", ", parameters); + + return string.Format(Resources.GenerateDelegatingConstructor, + _state.ContainingType.Name, parameterString); + } + } + } + + private class FieldDelegatingCodeAction : CodeAction + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + + public FieldDelegatingCodeAction( + TService service, + Document document, + State state) + { + _service = service; + _document = document; + _state = state; + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + // First, see if there are any constructors that would take the first 'n' arguments + // we've provided. If so, delegate to those, and then create a field for any + // remaining arguments. Try to match from largest to smallest. + // + // Otherwise, just generate a normal constructor that assigns any provided + // parameters into fields. + var parameterToExistingFieldMap = new Dictionary<string, ISymbol>(); + for (int i = 0; i < _state.Parameters.Count; i++) + { + parameterToExistingFieldMap[_state.Parameters[i].Name] = _state.SelectedMembers[i]; + } + + var factory = _document.GetLanguageService<SyntaxGenerator>(); + + var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var members = factory.CreateFieldDelegatingConstructor( + _state.ContainingType.Name, + _state.ContainingType, + _state.Parameters, + parameterToExistingFieldMap, + parameterToNewFieldMap: null, + cancellationToken: cancellationToken); + var codeGenerationService = new CSharpCodeGenerationService (_document.Project.Solution.Workspace.Services.GetLanguageServices (LanguageNames.CSharp)); + + var result = await codeGenerationService.AddMembersAsync( + _document.Project.Solution, + _state.ContainingType, + members, + new CodeGenerationOptions(contextLocation: syntaxTree.GetLocation(_state.TextSpan), generateDefaultAccessibility: false), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + + + public override string Title + { + get + { + var parameters = _state.Parameters.Select(p => p.ToDisplayString(SimpleFormat)); + var parameterString = string.Join(", ", parameters); + + if (_state.DelegatedConstructor == null) + { + return string.Format(Resources.GenerateConstructor, + _state.ContainingType.Name, parameterString); + } + else + { + return string.Format(Resources.GenerateFieldAssigningConstructor, + _state.ContainingType.Name, parameterString); + } + } + } + } + + + private class State + { + public TextSpan TextSpan { get; private set; } + public IMethodSymbol DelegatedConstructor { get; private set; } + public INamedTypeSymbol ContainingType { get; private set; } + public IList<ISymbol> SelectedMembers { get; private set; } + public List<IParameterSymbol> Parameters { get; private set; } + + public static State Generate( + TService service, + Document document, + TextSpan textSpan, + INamedTypeSymbol containingType, + IList<ISymbol> selectedMembers, + CancellationToken cancellationToken) + { + var state = new State(); + if (!state.TryInitialize(service, document, textSpan, containingType, selectedMembers, cancellationToken)) + { + return null; + } + + return state; + } + + private bool TryInitialize( + TService service, + Document document, + TextSpan textSpan, + INamedTypeSymbol containingType, + IList<ISymbol> selectedMembers, + CancellationToken cancellationToken) + { + if (!selectedMembers.All(IsWritableInstanceFieldOrProperty)) + { + return false; + } + + this.SelectedMembers = selectedMembers; + this.ContainingType = containingType; + this.TextSpan = textSpan; + if (this.ContainingType == null || this.ContainingType.TypeKind == TypeKind.Interface) + { + return false; + } + + this.Parameters = service.DetermineParameters(selectedMembers); + + if (service.HasMatchingConstructor(this.ContainingType, this.Parameters)) + { + return false; + } + + this.DelegatedConstructor = service.GetDelegatedConstructor(this.ContainingType, this.Parameters); + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/CSharpGenerateConstructorService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/CSharpGenerateConstructorService.cs new file mode 100644 index 0000000000..86667889c2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/CSharpGenerateConstructorService.cs @@ -0,0 +1,53 @@ +// +// CSharpGenerateConstructorService.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System.Threading; +using System.Collections.Generic; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers.GenerateConstructor +{ + public class CSharpGenerateConstructorService : + AbstractGenerateConstructorService<CSharpGenerateConstructorService, MemberDeclarationSyntax> + { + protected override Task<IList<MemberDeclarationSyntax>> GetSelectedMembersAsync( + Document document, TextSpan textSpan, CancellationToken cancellationToken) + { + return GenerateFromMembersHelpers.GetSelectedMembersAsync(document, textSpan, cancellationToken); + } + + protected override IEnumerable<ISymbol> GetDeclaredSymbols( + SemanticModel semanticModel, MemberDeclarationSyntax memberDeclaration, CancellationToken cancellationToken) + { + return GenerateFromMembersHelpers.GetDeclaredSymbols(semanticModel, memberDeclaration, cancellationToken); + } + } + +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/GenerateConstructorResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/GenerateConstructorResult.cs new file mode 100644 index 0000000000..597871d6b7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateConstructor/GenerateConstructorResult.cs @@ -0,0 +1,17 @@ +// 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.CodeRefactorings; +using ICSharpCode.NRefactory6.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers.GenerateConstructor +{ + public class GenerateConstructorResult : AbstractCodeRefactoringResult + { + public static readonly GenerateConstructorResult Failure = new GenerateConstructorResult(null); + + public GenerateConstructorResult(CodeRefactoring codeRefactoring) + : base(codeRefactoring) + { + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateFromMembersHelpers.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateFromMembersHelpers.cs new file mode 100644 index 0000000000..233b242c45 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateFromMembers/GenerateFromMembersHelpers.cs @@ -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. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateFromMembers +{ + internal static class GenerateFromMembersHelpers + { + internal static async Task<IList<MemberDeclarationSyntax>> GetSelectedMembersAsync( + Document document, TextSpan textSpan, CancellationToken cancellationToken) + { + var tree = await document.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + return tree.GetMembersInSpan(textSpan, cancellationToken); + } + + internal static IEnumerable<ISymbol> GetDeclaredSymbols(SemanticModel semanticModel, MemberDeclarationSyntax memberDeclaration, CancellationToken cancellationToken) + { + if (memberDeclaration is FieldDeclarationSyntax) + { + return ((FieldDeclarationSyntax)memberDeclaration).Declaration.Variables.Select( + v => semanticModel.GetDeclaredSymbol(v, cancellationToken)); + } + + return SpecializedCollections.SingletonEnumerable( + semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken)); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractCodeRefactoringResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractCodeRefactoringResult.cs new file mode 100644 index 0000000000..6b86b6cb09 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractCodeRefactoringResult.cs @@ -0,0 +1,31 @@ +// 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.CodeRefactorings; +using ICSharpCode.NRefactory6.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember +{ + public abstract class AbstractCodeRefactoringResult + { + private readonly CodeRefactoring _codeRefactoring; + + protected AbstractCodeRefactoringResult(CodeRefactoring codeRefactoring) + { + _codeRefactoring = codeRefactoring; + } + + public bool ContainsChanges + { + get + { + return _codeRefactoring != null; + } + } + + public CodeRefactoring GetCodeRefactoring(CancellationToken cancellationToken) + { + return _codeRefactoring; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractGenerateMemberService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractGenerateMemberService.cs new file mode 100644 index 0000000000..721f7e6560 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/AbstractGenerateMemberService.cs @@ -0,0 +1,138 @@ +// 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.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember +{ + public abstract partial class AbstractGenerateMemberService<TSimpleNameSyntax, TExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + { + protected AbstractGenerateMemberService() + { + } + + protected static readonly ISet<TypeKind> EnumType = new HashSet<TypeKind> { TypeKind.Enum }; + protected static readonly ISet<TypeKind> ClassInterfaceModuleStructTypes = new HashSet<TypeKind> + { + TypeKind.Class, + TypeKind.Module, + TypeKind.Struct, + TypeKind.Interface + }; + + protected bool ValidateTypeToGenerateIn( + Solution solution, + INamedTypeSymbol typeToGenerateIn, + bool isStatic, + ISet<TypeKind> typeKinds, + CancellationToken cancellationToken) + { + if (typeToGenerateIn == null) + { + return false; + } + + if (typeToGenerateIn.IsAnonymousType) + { + return false; + } + + if (!typeKinds.Contains(typeToGenerateIn.TypeKind)) + { + return false; + } + + if (typeToGenerateIn.TypeKind == TypeKind.Interface && isStatic) + { + return false; + } + + // TODO(cyrusn): Make sure that there is a totally visible part somewhere (i.e. + // venus) that we can generate into. + var locations = typeToGenerateIn.Locations; + return locations.Any(loc => loc.IsInSource); + } + + protected bool TryDetermineTypeToGenerateIn( + SemanticDocument document, + INamedTypeSymbol containingType, + TExpressionSyntax simpleNameOrMemberAccessExpression, + CancellationToken cancellationToken, + out INamedTypeSymbol typeToGenerateIn, + out bool isStatic) + { + typeToGenerateIn = null; + isStatic = false; + + var semanticModel = document.SemanticModel; + var isMemberAccessExpression = simpleNameOrMemberAccessExpression.IsMemberAccessExpression(); + if (isMemberAccessExpression || + simpleNameOrMemberAccessExpression.IsConditionalMemberAccessExpression()) + { + var beforeDotExpression = isMemberAccessExpression ? + simpleNameOrMemberAccessExpression.GetExpressionOfMemberAccessExpression() : + simpleNameOrMemberAccessExpression.GetExpressionOfConditionalMemberAccessExpression(); + if (beforeDotExpression != null) + { + var typeInfo = semanticModel.GetTypeInfo(beforeDotExpression, cancellationToken); + var semanticInfo = semanticModel.GetSymbolInfo(beforeDotExpression, cancellationToken); + + typeToGenerateIn = typeInfo.Type is ITypeParameterSymbol + ? ((ITypeParameterSymbol)typeInfo.Type).GetNamedTypeSymbolConstraint() + : typeInfo.Type as INamedTypeSymbol; + + isStatic = semanticInfo.Symbol is INamedTypeSymbol; + } + } + else if (simpleNameOrMemberAccessExpression.IsPointerMemberAccessExpression()) + { + var beforeArrowExpression = simpleNameOrMemberAccessExpression.GetExpressionOfMemberAccessExpression(); + if (beforeArrowExpression != null) + { + var typeInfo = semanticModel.GetTypeInfo(beforeArrowExpression, cancellationToken); + + if (typeInfo.Type.IsPointerType()) + { + typeToGenerateIn = ((IPointerTypeSymbol)typeInfo.Type).PointedAtType as INamedTypeSymbol; + isStatic = false; + } + } + } + else if (simpleNameOrMemberAccessExpression.IsAttributeNamedArgumentIdentifier()) + { + var attributeNode = simpleNameOrMemberAccessExpression.GetAncestors().FirstOrDefault(CSharpSyntaxFactsService.IsAttribute); + var attributeName = attributeNode.GetNameOfAttribute(); + var attributeType = semanticModel.GetTypeInfo(attributeName, cancellationToken); + + typeToGenerateIn = attributeType.Type as INamedTypeSymbol; + isStatic = false; + } + else if (simpleNameOrMemberAccessExpression.IsObjectInitializerNamedAssignmentIdentifier()) + { + var objectCreationNode = simpleNameOrMemberAccessExpression.GetAncestors().FirstOrDefault(CSharpSyntaxFactsService.IsObjectCreationExpression); + typeToGenerateIn = semanticModel.GetTypeInfo(objectCreationNode, cancellationToken).Type as INamedTypeSymbol; + isStatic = false; + } + else + { + // Generating into the containing type. + typeToGenerateIn = containingType; + isStatic = simpleNameOrMemberAccessExpression.IsInStaticContext(); + } + + if (typeToGenerateIn != null) + { + typeToGenerateIn = typeToGenerateIn.OriginalDefinition; + } + + return typeToGenerateIn != null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs new file mode 100644 index 0000000000..6f7df24395 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -0,0 +1,703 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis.Editing; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using System; +using Microsoft.CodeAnalysis.FindSymbols; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateConstructor +{ + public abstract partial class AbstractGenerateConstructorService<TService, TArgumentSyntax, TAttributeArgumentSyntax> + where TService : AbstractGenerateConstructorService<TService, TArgumentSyntax, TAttributeArgumentSyntax> + where TArgumentSyntax : SyntaxNode + where TAttributeArgumentSyntax : SyntaxNode + { + + protected AbstractGenerateConstructorService() + { + } + + protected abstract bool IsSimpleNameGeneration(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken); + protected abstract bool IsConstructorInitializerGeneration(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken); + + protected abstract bool TryInitializeConstructorInitializerGeneration(SemanticDocument document, SyntaxNode constructorInitializer, CancellationToken cancellationToken, out SyntaxToken token, out IList<TArgumentSyntax> arguments, out INamedTypeSymbol typeToGenerateIn); + protected abstract bool TryInitializeSimpleNameGenerationState(SemanticDocument document, SyntaxNode simpleName, CancellationToken cancellationToken, out SyntaxToken token, out IList<TArgumentSyntax> arguments, out INamedTypeSymbol typeToGenerateIn); + protected abstract bool TryInitializeSimpleAttributeNameGenerationState(SemanticDocument document, SyntaxNode simpleName, CancellationToken cancellationToken, out SyntaxToken token, out IList<TArgumentSyntax> arguments, out IList<TAttributeArgumentSyntax> attributeArguments, out INamedTypeSymbol typeToGenerateIn); + + protected abstract IList<string> GenerateParameterNames(SemanticModel semanticModel, IEnumerable<TArgumentSyntax> arguments, IList<string> reservedNames = null); + protected virtual IList<string> GenerateParameterNames(SemanticModel semanticModel, IEnumerable<TAttributeArgumentSyntax> arguments, IList<string> reservedNames = null) { return null; } + protected abstract string GenerateNameForArgument(SemanticModel semanticModel, TArgumentSyntax argument); + protected virtual string GenerateNameForArgument(SemanticModel semanticModel, TAttributeArgumentSyntax argument) { return null; } + protected abstract RefKind GetRefKind(TArgumentSyntax argument); + protected abstract bool IsNamedArgument(TArgumentSyntax argument); + protected abstract ITypeSymbol GetArgumentType(SemanticModel semanticModel, TArgumentSyntax argument, CancellationToken cancellationToken); + protected virtual ITypeSymbol GetAttributeArgumentType(SemanticModel semanticModel, TAttributeArgumentSyntax argument, CancellationToken cancellationToken) { return null; } + + public async Task<IEnumerable<CodeAction>> GenerateConstructorAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); + if (state == null) + { + return SpecializedCollections.EmptyEnumerable<CodeAction>(); + } + + return GetActions(document, state); + } + + private IEnumerable<CodeAction> GetActions(Document document, State state) + { + yield return new GenerateConstructorCodeAction((TService)this, document, state); + } + + private class GenerateConstructorCodeAction : CodeAction + { + private readonly State _state; + private readonly Document _document; + private readonly TService _service; + + public GenerateConstructorCodeAction( + TService service, + Document document, + State state) + { + _service = service; + _document = document; + _state = state; + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(_document, cancellationToken).ConfigureAwait(false); + var editor = new Editor(_service, semanticDocument, _state, cancellationToken); + return await editor.GetEditAsync().ConfigureAwait(false); + } + + public override string Title + { + get + { + return string.Format(Resources.GenerateNewConstructorIn, + _state.TypeToGenerateIn.Name); + } + } + } + + protected abstract bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType); + + internal abstract IMethodSymbol GetDelegatingConstructor(State state, SemanticDocument document, int argumentCount, INamedTypeSymbol namedType, ISet<IMethodSymbol> candidates, CancellationToken cancellationToken); + + private partial class Editor + { + private readonly TService _service; + private readonly SemanticDocument _document; + private readonly State _state; + private readonly CancellationToken _cancellationToken; + + public Editor( + TService service, + SemanticDocument document, + State state, + CancellationToken cancellationToken) + { + _service = service; + _document = document; + _state = state; + _cancellationToken = cancellationToken; + } + + internal async Task<Document> GetEditAsync() + { + // First, see if there's an accessible base constructor that would accept these + // types, then just call into that instead of generating fields. + // + // then, see if there are any constructors that would take the first 'n' arguments + // we've provided. If so, delegate to those, and then create a field for any + // remaining arguments. Try to match from largest to smallest. + // + // Otherwise, just generate a normal constructor that assigns any provided + // parameters into fields. + + var edit = await GenerateThisOrBaseDelegatingConstructorAsync().ConfigureAwait(false); + if (edit != null) + { + return edit; + } + + return await GenerateFieldDelegatingConstructorAsync().ConfigureAwait(false); + } + + private async Task<Document> GenerateThisOrBaseDelegatingConstructorAsync() + { + // We don't have to deal with the zero length case, since there's nothing to + // delegate. It will fall out of the GenerateFieldDelegatingConstructor above. + for (int i = _state.Arguments.Count; i >= 1; i--) + { + var edit = await GenerateThisOrBaseDelegatingConstructorAsync(i).ConfigureAwait(false); + if (edit != null) + { + return edit; + } + } + + return null; + } + + private async Task<Document> GenerateThisOrBaseDelegatingConstructorAsync(int argumentCount) + { + Document edit; + if ((edit = await GenerateDelegatingConstructorAsync(argumentCount, _state.TypeToGenerateIn).ConfigureAwait(false)) != null || + (edit = await GenerateDelegatingConstructorAsync(argumentCount, _state.TypeToGenerateIn.BaseType).ConfigureAwait(false)) != null) + { + return edit; + } + + return null; + } + + private async Task<Document> GenerateDelegatingConstructorAsync( + int argumentCount, + INamedTypeSymbol namedType) + { + if (namedType == null) + { + return null; + } + + // We can't resolve overloads across language. + if (_document.Project.Language != namedType.Language) + { + return null; + } + + var arguments = _state.Arguments.Take(argumentCount).ToList(); + var remainingArguments = _state.Arguments.Skip(argumentCount).ToList(); + var remainingAttributeArguments = _state.AttributeArguments != null ? _state.AttributeArguments.Skip(argumentCount).ToList() : null; + var remainingParameterTypes = _state.ParameterTypes.Skip(argumentCount).ToList(); + + var instanceConstructors = namedType.InstanceConstructors.Where(IsSymbolAccessible).ToSet(); + if (instanceConstructors.IsEmpty()) + { + return null; + } + + var delegatedConstructor = _service.GetDelegatingConstructor(_state, _document, argumentCount, namedType, instanceConstructors, _cancellationToken); + if (delegatedConstructor == null) + { + return null; + } + + // There was a best match. Call it directly. + var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language); + var syntaxFactory = provider.GetService<SyntaxGenerator>(); + var codeGenerationService = new CSharpCodeGenerationService (_document.Project.Solution.Workspace); + + // Map the first N parameters to the other constructor in this type. Then + // try to map any further parameters to existing fields. Finally, generate + // new fields if no such parameters exist. + + // Find the names of the parameters that will follow the parameters we're + // delegating. + var remainingParameterNames = _service.GenerateParameterNames( + _document.SemanticModel, remainingArguments, delegatedConstructor.Parameters.Select(p => p.Name).ToList()); + + // Can't generate the constructor if the parameter names we're copying over forcibly + // conflict with any names we generated. + if (delegatedConstructor.Parameters.Select(p => p.Name).Intersect(remainingParameterNames).Any()) + { + return null; + } + + // Try to map those parameters to fields. + Dictionary<string, ISymbol> parameterToExistingFieldMap; + Dictionary<string, string> parameterToNewFieldMap; + List<IParameterSymbol> remainingParameters; + this.GetParameters(remainingArguments, remainingAttributeArguments, remainingParameterTypes, remainingParameterNames, out parameterToExistingFieldMap, out parameterToNewFieldMap, out remainingParameters); + + var fields = syntaxFactory.CreateFieldsForParameters(remainingParameters, parameterToNewFieldMap); + var assignStatements = syntaxFactory.CreateAssignmentStatements(remainingParameters, parameterToExistingFieldMap, parameterToNewFieldMap); + + var allParameters = delegatedConstructor.Parameters.Concat(remainingParameters).ToList(); + + var isThis = namedType.Equals(_state.TypeToGenerateIn); + var delegatingArguments = syntaxFactory.CreateArguments(delegatedConstructor.Parameters); + var baseConstructorArguments = isThis ? null : delegatingArguments; + var thisConstructorArguments = isThis ? delegatingArguments : null; + + var constructor = CodeGenerationSymbolFactory.CreateConstructorSymbol( + attributes: null, + accessibility: Accessibility.Public, + modifiers: default(DeclarationModifiers), + typeName: _state.TypeToGenerateIn.Name, + parameters: allParameters, + statements: assignStatements.ToList(), + baseConstructorArguments: baseConstructorArguments, + thisConstructorArguments: thisConstructorArguments); + + var members = new List<ISymbol>(fields) { constructor }; + var result = await codeGenerationService.AddMembersAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + members, + new CodeGenerationOptions(_state.Token.GetLocation(), generateDefaultAccessibility: false), + _cancellationToken) + .ConfigureAwait(false); + + return result; + } + + private async Task<Document> GenerateFieldDelegatingConstructorAsync() + { + var arguments = _state.Arguments.ToList(); + var parameterTypes = _state.ParameterTypes; + + var typeParametersNames = _state.TypeToGenerateIn.GetAllTypeParameters().Select(t => t.Name).ToList(); + var parameterNames = _state.AttributeArguments != null + ? _service.GenerateParameterNames(_document.SemanticModel, _state.AttributeArguments, typeParametersNames) + : _service.GenerateParameterNames(_document.SemanticModel, arguments, typeParametersNames); + + Dictionary<string, ISymbol> parameterToExistingFieldMap; + Dictionary<string, string> parameterToNewFieldMap; + List<IParameterSymbol> parameters; + GetParameters(arguments, _state.AttributeArguments, parameterTypes, parameterNames, out parameterToExistingFieldMap, out parameterToNewFieldMap, out parameters); + + var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language); + var syntaxFactory = provider.GetService<SyntaxGenerator>(); + var codeGenerationService = new CSharpCodeGenerationService (_document.Project.Solution.Workspace); + + var syntaxTree = _document.SyntaxTree; + var members = syntaxFactory.CreateFieldDelegatingConstructor( + _state.TypeToGenerateIn.Name, _state.TypeToGenerateIn, parameters, + parameterToExistingFieldMap, parameterToNewFieldMap, _cancellationToken); + + var result = await codeGenerationService.AddMembersAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + members, + new CodeGenerationOptions(_state.Token.GetLocation(), generateDefaultAccessibility: false), + _cancellationToken) + .ConfigureAwait(false); + + return result; + } + + private void GetParameters( + IList<TArgumentSyntax> arguments, + IList<TAttributeArgumentSyntax> attributeArguments, + IList<ITypeSymbol> parameterTypes, + IList<string> parameterNames, + out Dictionary<string, ISymbol> parameterToExistingFieldMap, + out Dictionary<string, string> parameterToNewFieldMap, + out List<IParameterSymbol> parameters) + { + parameterToExistingFieldMap = new Dictionary<string, ISymbol>(); + parameterToNewFieldMap = new Dictionary<string, string>(); + parameters = new List<IParameterSymbol>(); + + for (var i = 0; i < parameterNames.Count; i++) + { + // See if there's a matching field we can use. First test in a case sensitive + // manner, then case insensitively. + if (!TryFindMatchingField(arguments, attributeArguments, parameterNames, parameterTypes, i, parameterToExistingFieldMap, parameterToNewFieldMap, caseSentitive: true)) + { + if (!TryFindMatchingField(arguments, attributeArguments, parameterNames, parameterTypes, i, parameterToExistingFieldMap, parameterToNewFieldMap, caseSentitive: false)) + { + parameterToNewFieldMap[parameterNames[i]] = parameterNames[i]; + } + } + + parameters.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( + attributes: null, + refKind: _service.GetRefKind(arguments[i]), + isParams: false, + type: parameterTypes[i], + name: parameterNames[i])); + } + } + + private bool TryFindMatchingField( + IList<TArgumentSyntax> arguments, + IList<TAttributeArgumentSyntax> attributeArguments, + IList<string> parameterNames, + IList<ITypeSymbol> parameterTypes, + int index, + Dictionary<string, ISymbol> parameterToExistingFieldMap, + Dictionary<string, string> parameterToNewFieldMap, + bool caseSentitive) + { + var parameterName = parameterNames[index]; + var parameterType = parameterTypes[index]; + var isFixed = _service.IsNamedArgument(arguments[index]); + + // For non-out parameters, see if there's already a field there with the same name. + // If so, and it has a compatible type, then we can just assign to that field. + // Otherwise, we'll need to choose a different name for this member so that it + // doesn't conflict with something already in the type. First check the current type + // for a matching field. If so, defer to it. + var comparison = caseSentitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + foreach (var type in _state.TypeToGenerateIn.GetBaseTypesAndThis()) + { + var ignoreAccessibility = type.Equals(_state.TypeToGenerateIn); + var symbol = type.GetMembers() + .FirstOrDefault(s => s.Name.Equals(parameterName, comparison)); + + if (symbol != null) + { + if (ignoreAccessibility || IsSymbolAccessible(symbol)) + { + if (IsViableFieldOrProperty(parameterType, symbol)) + { + // Ok! We can just the existing field. + parameterToExistingFieldMap[parameterName] = symbol; + } + else + { + // Uh-oh. Now we have a problem. We can't assign this parameter to + // this field. So we need to create a new field. Find a name not in + // use so we can assign to that. + var newFieldName = NameGenerator.EnsureUniqueness( + attributeArguments != null ? + _service.GenerateNameForArgument(_document.SemanticModel, attributeArguments[index]) : + _service.GenerateNameForArgument(_document.SemanticModel, arguments[index]), + GetUnavailableMemberNames().Concat(parameterToNewFieldMap.Values)); + + if (isFixed) + { + // Can't change the parameter name, so map the existing parameter + // name to the new field name. + parameterToNewFieldMap[parameterName] = newFieldName; + } + else + { + // Can change the parameter name, so do so. + parameterNames[index] = newFieldName; + parameterToNewFieldMap[newFieldName] = newFieldName; + } + } + + return true; + } + } + } + + return false; + } + + private IEnumerable<string> GetUnavailableMemberNames() + { + return _state.TypeToGenerateIn.MemberNames.Concat( + from type in _state.TypeToGenerateIn.GetBaseTypes() + from member in type.GetMembers() + select member.Name); + } + + private bool IsViableFieldOrProperty( + ITypeSymbol parameterType, + ISymbol symbol) + { + if (parameterType.Language != symbol.Language) + { + return false; + } + + if (symbol != null && !symbol.IsStatic) + { + if (symbol is IFieldSymbol) + { + var field = (IFieldSymbol)symbol; + return + !field.IsConst && + _service.IsConversionImplicit(_document.SemanticModel.Compilation, parameterType, field.Type); + } + else if (symbol is IPropertySymbol) + { + var property = (IPropertySymbol)symbol; + return + property.Parameters.Length == 0 && + property.SetMethod != null && + _service.IsConversionImplicit(_document.SemanticModel.Compilation, parameterType, property.Type); + } + } + + return false; + } + + private bool IsSymbolAccessible( + ISymbol symbol) + { + if (symbol == null) + { + return false; + } + + if (symbol.Kind == SymbolKind.Property) + { + if (!IsSymbolAccessible(((IPropertySymbol)symbol).SetMethod)) + { + return false; + } + } + + // Public and protected constructors are accessible. Internal constructors are + // accessible if we have friend access. We can't call the normal accessibility + // checkers since they will think that a protected constructor isn't accessible + // (since we don't have the destination type that would have access to them yet). + switch (symbol.DeclaredAccessibility) + { + case Accessibility.ProtectedOrInternal: + case Accessibility.Protected: + case Accessibility.Public: + return true; + case Accessibility.ProtectedAndInternal: + case Accessibility.Internal: + return _document.SemanticModel.Compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo( + symbol.ContainingAssembly); + + default: + return false; + } + } + } + + protected internal class State + { + public IList<TArgumentSyntax> Arguments { get; private set; } + + public IList<TAttributeArgumentSyntax> AttributeArguments { get; private set; } + + // The type we're creating a constructor for. Will be a class or struct type. + public INamedTypeSymbol TypeToGenerateIn { get; private set; } + + public IList<ITypeSymbol> ParameterTypes { get; private set; } + public IList<RefKind> ParameterRefKinds { get; private set; } + + public SyntaxToken Token { get; private set; } + public bool IsConstructorInitializerGeneration { get; private set; } + + private State() + { + this.IsConstructorInitializerGeneration = false; + } + + public static async Task<State> GenerateAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var state = new State(); + if (!await state.TryInitializeAsync(service, document, node, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return state; + } + + private async Task<bool> TryInitializeAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + if (service.IsConstructorInitializerGeneration(document, node, cancellationToken)) + { + if (!await TryInitializeConstructorInitializerGenerationAsync(service, document, node, cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + else if (service.IsSimpleNameGeneration(document, node, cancellationToken)) + { + if (!await TryInitializeSimpleNameGenerationAsync(service, document, node, cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + else + { + return false; + } + + if (!new CSharpCodeGenerationService (document.Project.Solution.Workspace).CanAddTo(this.TypeToGenerateIn, document.Project.Solution, cancellationToken)) + { + return false; + } + + this.ParameterTypes = this.ParameterTypes ?? GetParameterTypes(service, document, cancellationToken); + this.ParameterRefKinds = this.Arguments.Select(service.GetRefKind).ToList(); + + return !ClashesWithExistingConstructor(service, document, cancellationToken); + } + + private bool ClashesWithExistingConstructor(TService service, SemanticDocument document, CancellationToken cancellationToken) + { + var parameters = this.ParameterTypes.Zip(this.ParameterRefKinds, (t, r) => CodeGenerationSymbolFactory.CreateParameterSymbol( + attributes: null, + refKind: r, + isParams: false, + type: t, + name: string.Empty)).ToList(); + + var destinationProvider = document.Project.Solution.Workspace.Services.GetLanguageServices(this.TypeToGenerateIn.Language); + + return this.TypeToGenerateIn.InstanceConstructors.Any(c => SignatureComparer.HaveSameSignature(parameters, c.Parameters, compareParameterName: true, isCaseSensitive: true)); + } + + internal List<ITypeSymbol> GetParameterTypes( + TService service, + SemanticDocument document, + CancellationToken cancellationToken) + { + var allTypeParameters = this.TypeToGenerateIn.GetAllTypeParameters(); + var semanticModel = document.SemanticModel; + var allTypes = this.AttributeArguments != null + ? this.AttributeArguments.Select(a => service.GetAttributeArgumentType(semanticModel, a, cancellationToken)) + : this.Arguments.Select(a => service.GetArgumentType(semanticModel, a, cancellationToken)); + + return allTypes.Select(t => FixType(t, semanticModel, allTypeParameters)).ToList(); + } + + private ITypeSymbol FixType(ITypeSymbol typeSymbol, SemanticModel semanticModel, IEnumerable<ITypeParameterSymbol> allTypeParameters) + { + var compilation = semanticModel.Compilation; + return typeSymbol.RemoveAnonymousTypes(compilation) + .RemoveUnavailableTypeParameters(compilation, allTypeParameters) + .RemoveUnnamedErrorTypes(compilation); + } + + private async Task<bool> TryInitializeConstructorInitializerGenerationAsync( + TService service, + SemanticDocument document, + SyntaxNode constructorInitializer, + CancellationToken cancellationToken) + { + SyntaxToken token; + IList<TArgumentSyntax> arguments; + INamedTypeSymbol typeToGenerateIn; + if (!service.TryInitializeConstructorInitializerGeneration(document, constructorInitializer, cancellationToken, + out token, out arguments, out typeToGenerateIn)) + { + return false; + } + + this.Token = token; + this.Arguments = arguments; + this.IsConstructorInitializerGeneration = true; + + var semanticModel = document.SemanticModel; + var semanticInfo = semanticModel.GetSymbolInfo(constructorInitializer, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + if (semanticInfo.Symbol != null) + { + return false; + } + + return await TryDetermineTypeToGenerateInAsync(document, typeToGenerateIn, cancellationToken).ConfigureAwait(false); + } + + private async Task<bool> TryInitializeSimpleNameGenerationAsync( + TService service, + SemanticDocument document, + SyntaxNode simpleName, + CancellationToken cancellationToken) + { + SyntaxToken token; + IList<TArgumentSyntax> arguments; + IList<TAttributeArgumentSyntax> attributeArguments; + INamedTypeSymbol typeToGenerateIn; + if (service.TryInitializeSimpleNameGenerationState(document, simpleName, cancellationToken, + out token, out arguments, out typeToGenerateIn)) + { + this.Token = token; + this.Arguments = arguments; + } + else if (service.TryInitializeSimpleAttributeNameGenerationState(document, simpleName, cancellationToken, + out token, out arguments, out attributeArguments, out typeToGenerateIn)) + { + this.Token = token; + this.AttributeArguments = attributeArguments; + this.Arguments = arguments; + + //// Attribute parameters are restricted to be constant values (simple types or string, etc). + if (this.AttributeArguments != null && GetParameterTypes(service, document, cancellationToken).Any(t => !IsValidAttributeParameterType(t))) + { + return false; + } + else if (GetParameterTypes(service, document, cancellationToken).Any(t => !IsValidAttributeParameterType(t))) + { + return false; + } + } + else + { + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + + return await TryDetermineTypeToGenerateInAsync(document, typeToGenerateIn, cancellationToken).ConfigureAwait(false); + } + + private bool IsValidAttributeParameterType(ITypeSymbol type) + { + if (type.Kind == SymbolKind.ArrayType) + { + var arrayType = (IArrayTypeSymbol)type; + if (arrayType.Rank != 1) + { + return false; + } + + type = arrayType.ElementType; + } + + if (type.IsEnumType()) + { + return true; + } + + switch (type.SpecialType) + { + case SpecialType.System_Boolean: + case SpecialType.System_Byte: + case SpecialType.System_Char: + case SpecialType.System_Int16: + case SpecialType.System_Int32: + case SpecialType.System_Int64: + case SpecialType.System_Double: + case SpecialType.System_Single: + case SpecialType.System_String: + return true; + + default: + return false; + } + } + + private async Task<bool> TryDetermineTypeToGenerateInAsync( + SemanticDocument document, + INamedTypeSymbol original, + CancellationToken cancellationToken) + { + var definition = await SymbolFinder.FindSourceDefinitionAsync(original, document.Project.Solution, cancellationToken).ConfigureAwait(false); + this.TypeToGenerateIn = definition as INamedTypeSymbol; + + return this.TypeToGenerateIn != null && + (this.TypeToGenerateIn.TypeKind == TypeKind.Class || + this.TypeToGenerateIn.TypeKind == TypeKind.Struct); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/CSharpGenerateConstructorService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/CSharpGenerateConstructorService.cs new file mode 100644 index 0000000000..04eeba23d7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/CSharpGenerateConstructorService.cs @@ -0,0 +1,278 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateConstructor +{ + public class CSharpGenerateConstructorService : AbstractGenerateConstructorService<CSharpGenerateConstructorService, ArgumentSyntax, AttributeArgumentSyntax> + { + private static readonly SyntaxAnnotation s_annotation = new SyntaxAnnotation(); + + protected override bool IsSimpleNameGeneration(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken) + { + return node is SimpleNameSyntax; + } + + protected override bool IsConstructorInitializerGeneration(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken) + { + return node is ConstructorInitializerSyntax; + } + + protected override bool TryInitializeConstructorInitializerGeneration( + SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken, + out SyntaxToken token, out IList<ArgumentSyntax> arguments, out INamedTypeSymbol typeToGenerateIn) + { + var constructorInitializer = (ConstructorInitializerSyntax)node; + + if (!constructorInitializer.ArgumentList.CloseParenToken.IsMissing) + { + token = constructorInitializer.ThisOrBaseKeyword; + arguments = constructorInitializer.ArgumentList.Arguments.ToList(); + + var semanticModel = document.SemanticModel; + var currentType = semanticModel.GetEnclosingNamedType(constructorInitializer.SpanStart, cancellationToken); + typeToGenerateIn = constructorInitializer.IsKind(SyntaxKind.ThisConstructorInitializer) + ? currentType + : currentType.BaseType.OriginalDefinition; + return typeToGenerateIn != null; + } + + token = default(SyntaxToken); + arguments = null; + typeToGenerateIn = null; + return false; + } + + protected override bool TryInitializeSimpleNameGenerationState( + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken, + out SyntaxToken token, + out IList<ArgumentSyntax> arguments, + out INamedTypeSymbol typeToGenerateIn) + { + var simpleName = (SimpleNameSyntax)node; + var fullName = simpleName.IsRightSideOfQualifiedName() + ? (NameSyntax)simpleName.Parent + : simpleName; + + if (fullName.Parent is ObjectCreationExpressionSyntax) + { + var objectCreationExpression = (ObjectCreationExpressionSyntax)fullName.Parent; + if (objectCreationExpression.ArgumentList != null && + !objectCreationExpression.ArgumentList.CloseParenToken.IsMissing) + { + var symbolInfo = document.SemanticModel.GetSymbolInfo(objectCreationExpression.Type, cancellationToken); + token = simpleName.Identifier; + arguments = objectCreationExpression.ArgumentList.Arguments.ToList(); + typeToGenerateIn = symbolInfo.GetAnySymbol() as INamedTypeSymbol; + return typeToGenerateIn != null; + } + } + + token = default(SyntaxToken); + arguments = null; + typeToGenerateIn = null; + return false; + } + + protected override bool TryInitializeSimpleAttributeNameGenerationState( + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken, + out SyntaxToken token, + out IList<ArgumentSyntax> arguments, + out IList<AttributeArgumentSyntax> attributeArguments, + out INamedTypeSymbol typeToGenerateIn) + { + var simpleName = (SimpleNameSyntax)node; + var fullName = simpleName.IsRightSideOfQualifiedName() + ? (NameSyntax)simpleName.Parent + : simpleName; + + if (fullName.Parent is AttributeSyntax) + { + var attribute = (AttributeSyntax)fullName.Parent; + if (attribute.ArgumentList != null && + !attribute.ArgumentList.CloseParenToken.IsMissing) + { + var symbolInfo = document.SemanticModel.GetSymbolInfo(attribute, cancellationToken); + if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure && !symbolInfo.CandidateSymbols.IsEmpty) + { + token = simpleName.Identifier; + attributeArguments = attribute.ArgumentList.Arguments.ToList(); + arguments = attributeArguments.Select(x => SyntaxFactory.Argument(x.NameColon ?? ((x.NameEquals != null) ? SyntaxFactory.NameColon(x.NameEquals.Name) : null), default(SyntaxToken), x.Expression)).ToList(); + + typeToGenerateIn = symbolInfo.CandidateSymbols.FirstOrDefault().ContainingSymbol as INamedTypeSymbol; + return typeToGenerateIn != null; + } + } + } + + token = default(SyntaxToken); + arguments = null; + attributeArguments = null; + typeToGenerateIn = null; + return false; + } + + protected override IList<string> GenerateParameterNames( + SemanticModel semanticModel, IEnumerable<ArgumentSyntax> arguments, IList<string> reservedNames) + { + return semanticModel.GenerateParameterNames(arguments, reservedNames); + } + + protected override IList<string> GenerateParameterNames( + SemanticModel semanticModel, IEnumerable<AttributeArgumentSyntax> arguments, IList<string> reservedNames) + { + return semanticModel.GenerateParameterNames(arguments, reservedNames); + } + + protected override string GenerateNameForArgument( + SemanticModel semanticModel, ArgumentSyntax argument) + { + return semanticModel.GenerateNameForArgument(argument); + } + + protected override string GenerateNameForArgument( + SemanticModel semanticModel, AttributeArgumentSyntax argument) + { + return semanticModel.GenerateNameForArgument(argument); + } + + protected override RefKind GetRefKind(ArgumentSyntax argument) + { + return argument.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword ? RefKind.Ref : + argument.RefOrOutKeyword.Kind() == SyntaxKind.OutKeyword ? RefKind.Out : RefKind.None; + } + + protected override bool IsNamedArgument(ArgumentSyntax argument) + { + return argument.NameColon != null; + } + + protected override ITypeSymbol GetArgumentType( + SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken) + { + return semanticModel.GetType(argument.Expression, cancellationToken); + } + + protected override ITypeSymbol GetAttributeArgumentType( + SemanticModel semanticModel, AttributeArgumentSyntax argument, CancellationToken cancellationToken) + { + return semanticModel.GetType(argument.Expression, cancellationToken); + } + + protected override bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType) + { + return compilation.ClassifyConversion(sourceType, targetType).IsImplicit; + } + + internal override IMethodSymbol GetDelegatingConstructor(State state, SemanticDocument document, int argumentCount, INamedTypeSymbol namedType, ISet<IMethodSymbol> candidates, CancellationToken cancellationToken) + { + var oldToken = state.Token; + var tokenKind = oldToken.Kind(); + + if (state.IsConstructorInitializerGeneration) + { + SyntaxToken thisOrBaseKeyword; + SyntaxKind newCtorInitializerKind; + if (tokenKind != SyntaxKind.BaseKeyword && state.TypeToGenerateIn == namedType) + { + thisOrBaseKeyword = SyntaxFactory.Token(SyntaxKind.ThisKeyword); + newCtorInitializerKind = SyntaxKind.ThisConstructorInitializer; + } + else + { + thisOrBaseKeyword = SyntaxFactory.Token(SyntaxKind.BaseKeyword); + newCtorInitializerKind = SyntaxKind.BaseConstructorInitializer; + } + + var ctorInitializer = (ConstructorInitializerSyntax)oldToken.Parent; + var oldArgumentList = ctorInitializer.ArgumentList; + var newArgumentList = GetNewArgumentList(oldArgumentList, argumentCount); + + var newCtorInitializer = SyntaxFactory.ConstructorInitializer(newCtorInitializerKind, ctorInitializer.ColonToken, thisOrBaseKeyword, newArgumentList); + SemanticModel speculativeModel; + if (document.SemanticModel.TryGetSpeculativeSemanticModel(ctorInitializer.Span.Start, newCtorInitializer, out speculativeModel)) + { + var symbolInfo = speculativeModel.GetSymbolInfo(newCtorInitializer, cancellationToken); + return GenerateConstructorHelpers.GetDelegatingConstructor(symbolInfo, candidates, namedType); + } + } + else + { + var oldNode = oldToken.Parent + .AncestorsAndSelf(ascendOutOfTrivia: false) + .Where(node => SpeculationAnalyzer.CanSpeculateOnNode(node)) + .LastOrDefault(); + + var typeNameToReplace = (TypeSyntax)oldToken.Parent; + TypeSyntax newTypeName; + if (namedType != state.TypeToGenerateIn) + { + while (true) + { + var parentType = typeNameToReplace.Parent as TypeSyntax; + if (parentType == null) + { + break; + } + + typeNameToReplace = parentType; + } + + newTypeName = namedType.GenerateTypeSyntax().WithAdditionalAnnotations(s_annotation); + } + else + { + newTypeName = typeNameToReplace.WithAdditionalAnnotations(s_annotation); + } + + var newNode = oldNode.ReplaceNode(typeNameToReplace, newTypeName); + newTypeName = (TypeSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); + + var oldArgumentList = (ArgumentListSyntax)newTypeName.Parent.ChildNodes().FirstOrDefault(n => n is ArgumentListSyntax); + if (oldArgumentList != null) + { + var newArgumentList = GetNewArgumentList(oldArgumentList, argumentCount); + if (newArgumentList != oldArgumentList) + { + newNode = newNode.ReplaceNode(oldArgumentList, newArgumentList); + newTypeName = (TypeSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); + } + } + + var speculativeModel = SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(oldNode, newNode, document.SemanticModel); + if (speculativeModel != null) + { + var symbolInfo = speculativeModel.GetSymbolInfo(newTypeName.Parent, cancellationToken); + return GenerateConstructorHelpers.GetDelegatingConstructor(symbolInfo, candidates, namedType); + } + } + + return null; + } + + private static ArgumentListSyntax GetNewArgumentList(ArgumentListSyntax oldArgumentList, int argumentCount) + { + if (oldArgumentList.IsMissing || oldArgumentList.Arguments.Count == argumentCount) + { + return oldArgumentList; + } + + var newArguments = oldArgumentList.Arguments.Take(argumentCount); + return SyntaxFactory.ArgumentList(new SeparatedSyntaxList<ArgumentSyntax>().AddRange(newArguments)); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs new file mode 100644 index 0000000000..ce7fdf3c5d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.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 System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateConstructor +{ + internal static class GenerateConstructorHelpers + { + public static IMethodSymbol GetDelegatingConstructor(SymbolInfo symbolInfo, ISet<IMethodSymbol> candidateInstanceConstructors, INamedTypeSymbol containingType) + { + var symbol = symbolInfo.Symbol as IMethodSymbol; + if (symbol == null && symbolInfo.CandidateSymbols.Count() == 1) + { + // Even though the symbol info has a non-viable candidate symbol, we are trying to speculate a base constructor + // invocation from a different position then where the invocation to it would be generated. + // Passed in candidateInstanceConstructors actually represent all accessible and invocable constructor symbols. + // So, we allow candidate symbol for inaccessible OR not creatable candidate reason if it is in the given candidateInstanceConstructors. + if (symbolInfo.CandidateReason == CandidateReason.Inaccessible || + (symbolInfo.CandidateReason == CandidateReason.NotCreatable && containingType.IsAbstract)) + { + symbol = symbolInfo.CandidateSymbols.Single() as IMethodSymbol; + } + } + + if (symbol != null && candidateInstanceConstructors.Contains(symbol)) + { + return symbol; + } + + return null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs new file mode 100644 index 0000000000..94feddabdb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs @@ -0,0 +1,242 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis.Editing; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateDefaultConstructors +{ + public abstract partial class AbstractGenerateDefaultConstructorsService<TService> + where TService : AbstractGenerateDefaultConstructorsService<TService> + { + protected AbstractGenerateDefaultConstructorsService() + { + } + + protected abstract bool TryInitializeState(SemanticDocument document, TextSpan textSpan, CancellationToken cancellationToken, out SyntaxNode baseTypeNode, out INamedTypeSymbol classType); + + public async Task<GenerateDefaultConstructorsResult> GenerateDefaultConstructorsAsync( + Document document, + TextSpan textSpan, + CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + if (textSpan.IsEmpty) + { + var state = State.Generate((TService)this, semanticDocument, textSpan, cancellationToken); + if (state != null) + { + return new GenerateDefaultConstructorsResult(new CodeRefactoring(null, GetActions(document, state))); + } + } + + return GenerateDefaultConstructorsResult.Failure; + } + + private IEnumerable<CodeAction> GetActions(Document document, State state) + { + foreach (var constructor in state.UnimplementedConstructors) + { + yield return new GenerateDefaultConstructorCodeAction((TService)this, document, state, constructor); + } + + if (state.UnimplementedConstructors.Count > 1) + { + yield return new CodeActionAll((TService)this, document, state, state.UnimplementedConstructors); + } + } + + private abstract class AbstractCodeAction : CodeAction + { + private readonly IList<IMethodSymbol> _constructors; + private readonly Document _document; + private readonly State _state; + private readonly TService _service; + private readonly string _title; + + protected AbstractCodeAction( + TService service, + Document document, + State state, + IList<IMethodSymbol> constructors, + string title) + { + _service = service; + _document = document; + _state = state; + _constructors = constructors; + _title = title; + } + + public override string Title + { + get { return _title; } + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var result = await CodeGenerator.AddMemberDeclarationsAsync( + _document.Project.Solution, + _state.ClassType, + _constructors.Select(CreateConstructorDefinition), + cancellationToken: cancellationToken).ConfigureAwait(false); + + return result; + } + + private IMethodSymbol CreateConstructorDefinition( + IMethodSymbol constructor) + { + var syntaxFactory = _document.GetLanguageService<SyntaxGenerator>(); + var baseConstructorArguments = constructor.Parameters.Length != 0 + ? syntaxFactory.CreateArguments(constructor.Parameters) + : null; + + return CodeGenerationSymbolFactory.CreateConstructorSymbol( + attributes: null, + accessibility: constructor.DeclaredAccessibility, + modifiers: new DeclarationModifiers(), + typeName: _state.ClassType.Name, + parameters: constructor.Parameters, + statements: null, + baseConstructorArguments: baseConstructorArguments); + } + } + + + private class GenerateDefaultConstructorCodeAction : AbstractCodeAction + { + public GenerateDefaultConstructorCodeAction( + TService service, + Document document, + State state, + IMethodSymbol constructor) + : base(service, document, state, new[] { constructor }, GetDisplayText(state, constructor)) + { + } + + private static string GetDisplayText(State state, IMethodSymbol constructor) + { + var parameters = constructor.Parameters.Select(p => p.Name); + var parameterString = string.Join(", ", parameters); + + return string.Format(Resources.GenerateConstructor + ".", + state.ClassType.Name, parameterString); + } + } + + private class CodeActionAll : AbstractCodeAction + { + public CodeActionAll( + TService service, + Document document, + State state, + IList<IMethodSymbol> constructors) + : base(service, document, state, GetConstructors(state, constructors), Resources.GenerateAll) + { + } + + private static IList<IMethodSymbol> GetConstructors(State state, IList<IMethodSymbol> constructors) + { + return state.UnimplementedDefaultConstructor != null + ? new[] { state.UnimplementedDefaultConstructor }.Concat(constructors).ToList() + : constructors; + } + } + private class State + { + public INamedTypeSymbol ClassType { get; private set; } + + public IList<IMethodSymbol> UnimplementedConstructors { get; private set; } + public IMethodSymbol UnimplementedDefaultConstructor { get; private set; } + + public SyntaxNode BaseTypeNode { get; private set; } + + private State() + { + } + + public static State Generate( + TService service, + SemanticDocument document, + TextSpan textSpan, + CancellationToken cancellationToken) + { + var state = new State(); + if (!state.TryInitialize(service, document, textSpan, cancellationToken)) + { + return null; + } + + return state; + } + + private bool TryInitialize( + TService service, + SemanticDocument document, + TextSpan textSpan, + CancellationToken cancellationToken) + { + SyntaxNode baseTypeNode; + INamedTypeSymbol classType; + if (!service.TryInitializeState(document, textSpan, cancellationToken, out baseTypeNode, out classType)) + { + return false; + } + + if (!baseTypeNode.Span.IntersectsWith(textSpan.Start)) + { + return false; + } + + this.BaseTypeNode = baseTypeNode; + this.ClassType = classType; + + var baseType = this.ClassType.BaseType; + + if (this.ClassType.TypeKind != TypeKind.Class || + this.ClassType.IsStatic || + baseType == null || + baseType.SpecialType == SpecialType.System_Object || + baseType.TypeKind == TypeKind.Error) + { + return false; + } + + var classConstructors = this.ClassType.InstanceConstructors; + var baseTypeConstructors = + baseType.InstanceConstructors + .Where(c => c.IsAccessibleWithin(this.ClassType)); + + var destinationProvider = document.Project.Solution.Workspace.Services.GetLanguageServices(this.ClassType.Language); + + var missingConstructors = + baseTypeConstructors.Where(c1 => !classConstructors.Any( + c2 => SignatureComparer.HaveSameSignature(c1.Parameters, c2.Parameters, compareParameterName: true, isCaseSensitive: true))).ToList(); + + this.UnimplementedConstructors = missingConstructors; + + this.UnimplementedDefaultConstructor = baseTypeConstructors.FirstOrDefault(c => c.Parameters.Length == 0); + if (this.UnimplementedDefaultConstructor != null) + { + if (classConstructors.Any(c => c.Parameters.Length == 0 && !c.IsImplicitlyDeclared)) + { + this.UnimplementedDefaultConstructor = null; + } + } + + return this.UnimplementedConstructors.Count > 0; + } + } + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/CSharpGenerateDefaultConstructorsService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/CSharpGenerateDefaultConstructorsService.cs new file mode 100644 index 0000000000..b443a65e52 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/CSharpGenerateDefaultConstructorsService.cs @@ -0,0 +1,49 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateDefaultConstructors +{ + public class CSharpGenerateDefaultConstructorsService : AbstractGenerateDefaultConstructorsService<CSharpGenerateDefaultConstructorsService> + { + protected override bool TryInitializeState( + SemanticDocument document, TextSpan textSpan, CancellationToken cancellationToken, + out SyntaxNode baseTypeNode, out INamedTypeSymbol classType) + { + if (!cancellationToken.IsCancellationRequested) + { + var syntaxTree = document.SyntaxTree; + var node = document.Root.FindToken(textSpan.Start).GetAncestor<TypeSyntax>(); + if (node != null) + { + if (node.Parent is BaseTypeSyntax && node.Parent.IsParentKind(SyntaxKind.BaseList)) + { + var baseList = (BaseListSyntax)node.Parent.Parent; + if (baseList.Types.Count > 0 && + baseList.Types[0].Type == node && + baseList.IsParentKind(SyntaxKind.ClassDeclaration)) + { + var semanticModel = document.SemanticModel; + classType = semanticModel.GetDeclaredSymbol(baseList.Parent, cancellationToken) as INamedTypeSymbol; + baseTypeNode = node; + return classType != null; + } + } + } + } + + baseTypeNode = null; + classType = null; + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/GenerateDefaultConstructorsResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/GenerateDefaultConstructorsResult.cs new file mode 100644 index 0000000000..7dc5f0de2d --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateDefaultConstructors/GenerateDefaultConstructorsResult.cs @@ -0,0 +1,17 @@ +// 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.CodeRefactorings; +using ICSharpCode.NRefactory6.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateDefaultConstructors +{ + public class GenerateDefaultConstructorsResult : AbstractCodeRefactoringResult + { + public static readonly GenerateDefaultConstructorsResult Failure = new GenerateDefaultConstructorsResult(null); + + internal GenerateDefaultConstructorsResult(CodeRefactoring codeRefactoring) + : base(codeRefactoring) + { + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/AbstractGenerateEnumMemberService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/AbstractGenerateEnumMemberService.cs new file mode 100644 index 0000000000..c13d3b7d85 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/AbstractGenerateEnumMemberService.cs @@ -0,0 +1,241 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateEnumMember +{ + public abstract class AbstractGenerateEnumMemberService<TService, TSimpleNameSyntax, TExpressionSyntax> : + AbstractGenerateMemberService<TSimpleNameSyntax, TExpressionSyntax> + where TService : AbstractGenerateEnumMemberService<TService, TSimpleNameSyntax, TExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + { + protected AbstractGenerateEnumMemberService() + { + } + + protected abstract bool IsIdentifierNameGeneration(SyntaxNode node); + protected abstract bool TryInitializeIdentifierNameState(SemanticDocument document, TSimpleNameSyntax identifierName, CancellationToken cancellationToken, out SyntaxToken identifierToken, out TExpressionSyntax simpleNameOrMemberAccessExpression); + + public async Task<IEnumerable<CodeAction>> GenerateEnumMemberAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); + if (state == null) + { + return SpecializedCollections.EmptyEnumerable<CodeAction>(); + } + + return GetActions(document, state); + } + + private IEnumerable<CodeAction> GetActions(Document document, State state) + { + yield return new GenerateEnumMemberCodeAction((TService)this, document, state); + } + + private partial class GenerateEnumMemberCodeAction : CodeAction + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + + public GenerateEnumMemberCodeAction( + TService service, + Document document, + State state) + { + _service = service; + _document = document; + _state = state; + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var languageServices = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language); + + var value = _state.TypeToGenerateIn.LastEnumValueHasInitializer() + ? EnumValueUtilities.GetNextEnumValue(_state.TypeToGenerateIn, cancellationToken) + : null; + + var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var result = await new CSharpCodeGenerationService (_document.Project.Solution.Workspace).AddFieldAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + CodeGenerationSymbolFactory.CreateFieldSymbol( + attributes: null, + accessibility: Accessibility.Public, + modifiers: default(DeclarationModifiers), + type: _state.TypeToGenerateIn, + name: _state.IdentifierToken.ValueText, + hasConstantValue: value != null, + constantValue: value), + new CodeGenerationOptions(contextLocation: _state.IdentifierToken.GetLocation(), generateDefaultAccessibility: false), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + + public override string Title + { + get + { + var text = Resources.GenerateEnumMemberIn; + + return string.Format( + text, + _state.IdentifierToken.ValueText, + _state.TypeToGenerateIn.Name); + } + } + } + + + private partial class State + { + // public TypeDeclarationSyntax ContainingTypeDeclaration { get; private set; } + public INamedTypeSymbol TypeToGenerateIn { get; private set; } + + // Just the name of the method. i.e. "Foo" in "Foo" or "X.Foo" + public SyntaxToken IdentifierToken { get; private set; } + public TSimpleNameSyntax SimpleName { get; private set; } + public TExpressionSyntax SimpleNameOrMemberAccessExpression { get; private set; } + + public static async Task<State> GenerateAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var state = new State(); + if (!await state.TryInitializeAsync(service, document, node, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return state; + } + + private async Task<bool> TryInitializeAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + if (service.IsIdentifierNameGeneration(node)) + { + if (!TryInitializeIdentifierName(service, document, (TSimpleNameSyntax)node, cancellationToken)) + { + return false; + } + } + else + { + return false; + } + + // Ok. It either didn't bind to any symbols, or it bound to a symbol but with + // errors. In the former case we definitely want to offer to generate a field. In + // the latter case, we want to generate a field *unless* there's an existing member + // with the same name. Note: it's ok if there's an existing field with the same + // name. + var existingMembers = this.TypeToGenerateIn.GetMembers(this.IdentifierToken.ValueText); + if (existingMembers.Any()) + { + // TODO: Code coverage There was an existing member that the new member would + // clash with. + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + this.TypeToGenerateIn = await SymbolFinder.FindSourceDefinitionAsync(this.TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol; + if (!service.ValidateTypeToGenerateIn( + document.Project.Solution, this.TypeToGenerateIn, true, EnumType, cancellationToken)) + { + return false; + } + + return CodeGenerator.CanAdd(document.Project.Solution, this.TypeToGenerateIn, cancellationToken); + } + + private bool TryInitializeIdentifierName( + TService service, + SemanticDocument document, + TSimpleNameSyntax identifierName, + CancellationToken cancellationToken) + { + this.SimpleName = identifierName; + + SyntaxToken identifierToken; + TExpressionSyntax simpleNameOrMemberAccessExpression; + if (!service.TryInitializeIdentifierNameState(document, identifierName, cancellationToken, + out identifierToken, out simpleNameOrMemberAccessExpression)) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.SimpleNameOrMemberAccessExpression = simpleNameOrMemberAccessExpression; + + var semanticModel = document.SemanticModel; + if ((simpleNameOrMemberAccessExpression as ExpressionSyntax).IsWrittenTo() || + simpleNameOrMemberAccessExpression.IsInNamespaceOrTypeContext()) + { + return false; + } + + // Now, try to bind the invocation and see if it succeeds or not. if it succeeds and + // binds uniquely, then we don't need to offer this quick fix. + cancellationToken.ThrowIfCancellationRequested(); + var containingType = semanticModel.GetEnclosingNamedType(identifierToken.SpanStart, cancellationToken); + if (containingType == null) + { + return false; + } + + var semanticInfo = semanticModel.GetSymbolInfo(this.SimpleNameOrMemberAccessExpression, cancellationToken); + if (cancellationToken.IsCancellationRequested) + { + return false; + } + + if (semanticInfo.Symbol != null) + { + return false; + } + + // Either we found no matches, or this was ambiguous. Either way, we might be able + // to generate a method here. Determine where the user wants to generate the method + // into, and if it's valid then proceed. + INamedTypeSymbol typeToGenerateIn; + bool isStatic; + if (!service.TryDetermineTypeToGenerateIn( + document, containingType, simpleNameOrMemberAccessExpression, cancellationToken, + out typeToGenerateIn, out isStatic)) + { + return false; + } + + if (!isStatic) + { + return false; + } + + this.TypeToGenerateIn = typeToGenerateIn; + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/CSharpGenerateEnumMemberService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/CSharpGenerateEnumMemberService.cs new file mode 100644 index 0000000000..5852b0d939 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateEnumMember/CSharpGenerateEnumMemberService.cs @@ -0,0 +1,54 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateEnumMember +{ + public partial class CSharpGenerateEnumMemberService : + AbstractGenerateEnumMemberService<CSharpGenerateEnumMemberService, SimpleNameSyntax, ExpressionSyntax> + { + protected override bool IsIdentifierNameGeneration(SyntaxNode node) + { + return node is IdentifierNameSyntax; + } + + protected override bool TryInitializeIdentifierNameState( + SemanticDocument document, SimpleNameSyntax identifierName, CancellationToken cancellationToken, + out SyntaxToken identifierToken, out ExpressionSyntax simpleNameOrMemberAccessExpression) + { + identifierToken = identifierName.Identifier; + if (identifierToken.ValueText != string.Empty && + !identifierName.IsVar) + { + var memberAccess = identifierName.Parent as MemberAccessExpressionSyntax; + simpleNameOrMemberAccessExpression = memberAccess != null && memberAccess.Name == identifierName + ? (ExpressionSyntax)memberAccess + : identifierName; + + // If we're being invoked, then don't offer this, offer generate method instead. + // Note: we could offer to generate a field with a delegate type. However, that's + // very esoteric and probably not what most users want. + if (simpleNameOrMemberAccessExpression.IsParentKind(SyntaxKind.InvocationExpression) || + simpleNameOrMemberAccessExpression.IsParentKind(SyntaxKind.ObjectCreationExpression) || + simpleNameOrMemberAccessExpression.IsParentKind(SyntaxKind.GotoStatement) || + simpleNameOrMemberAccessExpression.IsParentKind(SyntaxKind.AliasQualifiedName)) + { + return false; + } + + return true; + } + + identifierToken = default(SyntaxToken); + simpleNameOrMemberAccessExpression = null; + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateConversionService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateConversionService.cs new file mode 100644 index 0000000000..dc664d9f14 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateConversionService.cs @@ -0,0 +1,135 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public abstract partial class AbstractGenerateConversionService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> : + AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> + where TService : AbstractGenerateConversionService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + where TInvocationExpressionSyntax : TExpressionSyntax + { + protected abstract bool IsImplicitConversionGeneration(SyntaxNode node); + protected abstract bool IsExplicitConversionGeneration(SyntaxNode node); + protected abstract bool TryInitializeImplicitConversionState(SemanticDocument document, SyntaxNode expression, ISet<TypeKind> classInterfaceModuleStructTypes, CancellationToken cancellationToken, out SyntaxToken identifierToken, out IMethodSymbol methodSymbol, out INamedTypeSymbol typeToGenerateIn); + protected abstract bool TryInitializeExplicitConversionState(SemanticDocument document, SyntaxNode expression, ISet<TypeKind> classInterfaceModuleStructTypes, CancellationToken cancellationToken, out SyntaxToken identifierToken, out IMethodSymbol methodSymbol, out INamedTypeSymbol typeToGenerateIn); + + public async Task<IEnumerable<CodeAction>> GenerateConversionAsync( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var state = await State.GenerateConversionStateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); + if (state == null) + { + return SpecializedCollections.EmptyEnumerable<CodeAction>(); + } + + return GetActions(document, state, cancellationToken); + } + + protected new class State : AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>.State + { + public static async Task<State> GenerateConversionStateAsync( + TService service, + SemanticDocument document, + SyntaxNode interfaceNode, + CancellationToken cancellationToken) + { + var state = new State(); + if (!await state.TryInitializeConversionAsync(service, document, interfaceNode, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return state; + } + + private Task<bool> TryInitializeConversionAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + if (service.IsImplicitConversionGeneration(node)) + { + if (!TryInitializeImplicitConversion(service, document, node, cancellationToken)) + { + return Task.FromResult (false); + } + } + else if (service.IsExplicitConversionGeneration(node)) + { + if (!TryInitializeExplicitConversion(service, document, node, cancellationToken)) + { + return Task.FromResult (false); + } + } + + return TryFinishInitializingState(service, document, cancellationToken); + } + + private bool TryInitializeExplicitConversion(TService service, SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken) + { + MethodKind = MethodKind.Conversion; + SyntaxToken identifierToken; + IMethodSymbol methodSymbol; + INamedTypeSymbol typeToGenerateIn; + if (!service.TryInitializeExplicitConversionState( + document, node, ClassInterfaceModuleStructTypes, cancellationToken, + out identifierToken, out methodSymbol, out typeToGenerateIn)) + { + return false; + } + + this.ContainingType = document.SemanticModel.GetEnclosingNamedType(node.SpanStart, cancellationToken); + if (ContainingType == null) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.TypeToGenerateIn = typeToGenerateIn; + this.SignatureInfo = new MethodSignatureInfo(document, this, methodSymbol); + this.location = node.GetLocation(); + this.MethodGenerationKind = MethodGenerationKind.ExplicitConversion; + return true; + } + + private bool TryInitializeImplicitConversion(TService service, SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken) + { + MethodKind = MethodKind.Conversion; + SyntaxToken identifierToken; + IMethodSymbol methodSymbol; + INamedTypeSymbol typeToGenerateIn; + if (!service.TryInitializeImplicitConversionState( + document, node, ClassInterfaceModuleStructTypes, cancellationToken, + out identifierToken, out methodSymbol, out typeToGenerateIn)) + { + return false; + } + + this.ContainingType = document.SemanticModel.GetEnclosingNamedType(node.SpanStart, cancellationToken); + if (ContainingType == null) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.TypeToGenerateIn = typeToGenerateIn; + this.SignatureInfo = new MethodSignatureInfo(document, this, methodSymbol); + this.MethodGenerationKind = MethodGenerationKind.ImplicitConversion; + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.cs new file mode 100644 index 0000000000..9d7e794ee5 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.cs @@ -0,0 +1,266 @@ +// 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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServices; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public abstract partial class AbstractGenerateMethodService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> : + AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> + where TService : AbstractGenerateMethodService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + where TInvocationExpressionSyntax : TExpressionSyntax + { + protected abstract bool IsSimpleNameGeneration(SyntaxNode node); + protected abstract bool IsExplicitInterfaceGeneration(SyntaxNode node); + protected abstract bool TryInitializeExplicitInterfaceState(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken, out SyntaxToken identifierToken, out IMethodSymbol methodSymbol, out INamedTypeSymbol typeToGenerateIn); + protected abstract bool TryInitializeSimpleNameState(SemanticDocument document, TSimpleNameSyntax simpleName, CancellationToken cancellationToken, out SyntaxToken identifierToken, out TExpressionSyntax simpleNameOrMemberAccessExpression, out TInvocationExpressionSyntax invocationExpressionOpt, out bool isInConditionalExpression); + protected abstract ITypeSymbol CanGenerateMethodForSimpleNameOrMemberAccessExpression(SemanticModel semanticModel, TExpressionSyntax expresion, CancellationToken cancellationToken); + + public async Task<IEnumerable<CodeAction>> GenerateMethodAsync( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var state = await State.GenerateMethodStateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); + if (state == null) + { + return SpecializedCollections.EmptyEnumerable<CodeAction>(); + } + + return GetActions(document, state, cancellationToken); + } + + internal protected new class State : AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>.State + { + public static async Task<State> GenerateMethodStateAsync( + TService service, + SemanticDocument document, + SyntaxNode interfaceNode, + CancellationToken cancellationToken) + { + var state = new State(); + if (!await state.TryInitializeMethodAsync(service, document, interfaceNode, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return state; + } + + private Task<bool> TryInitializeMethodAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + // Cases that we deal with currently: + // + // 1) expr.Foo + // 2) expr->Foo + // 3) Foo + // 4) expr.Foo() + // 5) expr->Foo() + // 6) Foo() + // 7) ReturnType Explicit.Interface.Foo() + // + // In the first 3 invocationExpressionOpt will be null and we'll have to infer a + // delegate type in order to figure out the right method signature to generate. In + // the next 3 invocationExpressionOpt will be non null and will be used to figure + // out the types/name of the parameters to generate. In the last one, we're going to + // generate into an interface. + if (service.IsExplicitInterfaceGeneration(node)) + { + if (!TryInitializeExplicitInterface(service, document, node, cancellationToken)) + { + return Task.FromResult (false); + } + } + else if (service.IsSimpleNameGeneration(node)) + { + if (!TryInitializeSimpleName(service, document, (TSimpleNameSyntax)node, cancellationToken)) + { + return Task.FromResult (false); + } + } + + return TryFinishInitializingState(service, document, cancellationToken); + } + + private bool TryInitializeExplicitInterface( + TService service, + SemanticDocument document, + SyntaxNode methodDeclaration, + CancellationToken cancellationToken) + { + MethodKind = MethodKind.Ordinary; + SyntaxToken identifierToken; + IMethodSymbol methodSymbol; + INamedTypeSymbol typeToGenerateIn; + if (!service.TryInitializeExplicitInterfaceState( + document, methodDeclaration, cancellationToken, + out identifierToken, out methodSymbol, out typeToGenerateIn)) + { + return false; + } + + if (methodSymbol.ExplicitInterfaceImplementations.Any()) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.TypeToGenerateIn = typeToGenerateIn; + + cancellationToken.ThrowIfCancellationRequested(); + var semanticModel = document.SemanticModel; + this.ContainingType = semanticModel.GetEnclosingNamedType(methodDeclaration.SpanStart, cancellationToken); + if (this.ContainingType == null) + { + return false; + } + + if (!this.ContainingType.Interfaces.Contains(this.TypeToGenerateIn)) + { + return false; + } + + this.SignatureInfo = new MethodSignatureInfo(document, this, methodSymbol); + return true; + } + + private bool TryInitializeSimpleName( + TService service, + SemanticDocument document, + TSimpleNameSyntax simpleName, + CancellationToken cancellationToken) + { + MethodKind = MethodKind.Ordinary; + this.SimpleNameOpt = simpleName; + + SyntaxToken identifierToken; + TExpressionSyntax simpleNameOrMemberAccessExpression; + TInvocationExpressionSyntax invocationExpressionOpt; + bool isInConditionalExpression; + if (!service.TryInitializeSimpleNameState( + document, simpleName, cancellationToken, + out identifierToken, out simpleNameOrMemberAccessExpression, out invocationExpressionOpt, out isInConditionalExpression)) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.SimpleNameOrMemberAccessExpression = simpleNameOrMemberAccessExpression; + this.InvocationExpressionOpt = invocationExpressionOpt; + this.IsInConditionalAccessExpression = isInConditionalExpression; + + if (string.IsNullOrWhiteSpace(this.IdentifierToken.ValueText)) + { + return false; + } + + // If we're not in a type, don't even bother. NOTE(cyrusn): We'll have to rethink this + // for C# Script. + cancellationToken.ThrowIfCancellationRequested(); + var semanticModel = document.SemanticModel; + this.ContainingType = semanticModel.GetEnclosingNamedType(this.SimpleNameOpt.SpanStart, cancellationToken); + if (this.ContainingType == null) + { + return false; + } + + if (this.InvocationExpressionOpt != null) + { + this.SignatureInfo = service.CreateInvocationMethodInfo(document, this); + } + else + { + var delegateType = TypeGuessing.typeInferenceService.InferDelegateType(semanticModel, this.SimpleNameOrMemberAccessExpression, cancellationToken); + if (delegateType != null && delegateType.DelegateInvokeMethod != null) + { + this.SignatureInfo = new MethodSignatureInfo(document, this, delegateType.DelegateInvokeMethod); + } + else + { + // We don't have and invocation expression or a delegate, but we may have a special expression without parenthesis. Lets see + // if the type inference service can directly infer the type for our expression. + var expressionType = service.CanGenerateMethodForSimpleNameOrMemberAccessExpression(semanticModel, this.SimpleNameOrMemberAccessExpression, cancellationToken); + if (expressionType == null) + { + return false; + } + + this.SignatureInfo = new MethodSignatureInfo(document, this, CreateMethodSymbolWithReturnType(expressionType)); + } + } + + // Now, try to bind the invocation and see if it succeeds or not. if it succeeds and + // binds uniquely, then we don't need to offer this quick fix. + cancellationToken.ThrowIfCancellationRequested(); + + // If the name bound with errors, then this is a candidate for generate method. + var semanticInfo = semanticModel.GetSymbolInfo(this.SimpleNameOrMemberAccessExpression, cancellationToken); + if (semanticInfo.GetAllSymbols().Any(s => s.Kind == SymbolKind.Local || s.Kind == SymbolKind.Parameter) && + !service.AreSpecialOptionsActive(semanticModel)) + { + // if the name bound to something in scope then we don't want to generate the + // method because it will be shadowed by what's in scope. Unless we are in a + // special state such as Option Strict On where we want to generate fixes even + // if we shadow types. + return false; + } + + // Check if the symbol is on the list of valid symbols for this language. + cancellationToken.ThrowIfCancellationRequested(); + if (semanticInfo.Symbol != null && !service.IsValidSymbol(semanticInfo.Symbol, semanticModel)) + { + return false; + } + + // Either we found no matches, or this was ambiguous. Either way, we might be able + // to generate a method here. Determine where the user wants to generate the method + // into, and if it's valid then proceed. + cancellationToken.ThrowIfCancellationRequested(); + INamedTypeSymbol typeToGenerateIn; + bool isStatic; + if (!service.TryDetermineTypeToGenerateIn( + document, this.ContainingType, this.SimpleNameOrMemberAccessExpression, cancellationToken, + out typeToGenerateIn, out isStatic)) + { + return false; + } + + var expressionSyntax = (this.InvocationExpressionOpt ?? this.SimpleNameOrMemberAccessExpression) as ExpressionSyntax; + this.IsWrittenTo = expressionSyntax.IsWrittenTo(); + this.TypeToGenerateIn = typeToGenerateIn; + this.IsStatic = isStatic; + this.MethodGenerationKind = MethodGenerationKind.Member; + return true; + } + + private static IMethodSymbol CreateMethodSymbolWithReturnType(ITypeSymbol expressionType) + { + return CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes: SpecializedCollections.EmptyList<AttributeData>(), + accessibility: default(Accessibility), + modifiers: default(DeclarationModifiers), + returnType: expressionType, + explicitInterfaceSymbol: null, + name: null, + typeParameters: SpecializedCollections.EmptyList<ITypeParameterSymbol>(), + parameters: SpecializedCollections.EmptyList<IParameterSymbol>()); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs new file mode 100644 index 0000000000..f70958dcd8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs @@ -0,0 +1,579 @@ +// 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; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis; +using System.Linq; +using System; +using System.Threading.Tasks; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.FindSymbols; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public abstract partial class AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> : + AbstractGenerateMemberService<TSimpleNameSyntax, TExpressionSyntax> + where TService : AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + where TInvocationExpressionSyntax : TExpressionSyntax + { + protected AbstractGenerateParameterizedMemberService() + { + } + + protected abstract AbstractInvocationInfo CreateInvocationMethodInfo(SemanticDocument document, State abstractState); + + protected abstract bool IsValidSymbol(ISymbol symbol, SemanticModel semanticModel); + protected abstract bool AreSpecialOptionsActive(SemanticModel semanticModel); + + protected virtual bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType) + { + return false; + } + + protected virtual string GetImplicitConversionDisplayText(State state) + { + return string.Empty; + } + + protected virtual string GetExplicitConversionDisplayText(State state) + { + return string.Empty; + } + + protected IEnumerable<CodeAction> GetActions(Document document, State state, CancellationToken cancellationToken) + { + yield return new GenerateParameterizedMemberCodeAction((TService)this, document, state, isAbstract: false, generateProperty: false); + + // If we're trying to generate an instance method into an abstract class (but not a + // static class or an interface), then offer to generate it abstractly. + var canGenerateAbstractly = state.TypeToGenerateIn.IsAbstract && + !state.TypeToGenerateIn.IsStatic && + state.TypeToGenerateIn.TypeKind != TypeKind.Interface && + !state.IsStatic; + + if (canGenerateAbstractly) + { + yield return new GenerateParameterizedMemberCodeAction((TService)this, document, state, isAbstract: true, generateProperty: false); + } + + if (true/*semanticFacts.SupportsParameterizedProperties*/ && + state.InvocationExpressionOpt != null) + { + var typeParameters = state.SignatureInfo.DetermineTypeParameters(cancellationToken); + var returnType = state.SignatureInfo.DetermineReturnType(cancellationToken); + + if (typeParameters.Count == 0 && returnType.SpecialType != SpecialType.System_Void) + { + yield return new GenerateParameterizedMemberCodeAction((TService)this, document, state, isAbstract: false, generateProperty: true); + + if (canGenerateAbstractly) + { + yield return new GenerateParameterizedMemberCodeAction((TService)this, document, state, isAbstract: true, generateProperty: true); + } + } + } + } + internal protected abstract class AbstractInvocationInfo : SignatureInfo + { + protected abstract bool IsIdentifierName(); + + protected abstract IList<ITypeParameterSymbol> GetCapturedTypeParameters(CancellationToken cancellationToken); + protected abstract IList<ITypeParameterSymbol> GenerateTypeParameters(CancellationToken cancellationToken); + + protected AbstractInvocationInfo(SemanticDocument document, State state) + : base(document, state) + { + } + + public override IList<ITypeParameterSymbol> DetermineTypeParameters(CancellationToken cancellationToken) + { + var typeParameters = DetermineTypeParametersWorker(cancellationToken); + return typeParameters.Select(tp => MassageTypeParameter(tp, cancellationToken)).ToList(); + } + + private IList<ITypeParameterSymbol> DetermineTypeParametersWorker( + CancellationToken cancellationToken) + { + if (IsIdentifierName()) + { + // If the user wrote something like Foo(x) then we still might want to generate + // a generic method if the expression 'x' captured any method type variables. + var capturedTypeParameters = GetCapturedTypeParameters(cancellationToken); + var availableTypeParameters = this.State.TypeToGenerateIn.GetAllTypeParameters(); + var result = capturedTypeParameters.Except(availableTypeParameters).ToList(); + return result; + } + else + { + return GenerateTypeParameters(cancellationToken); + } + } + + private ITypeParameterSymbol MassageTypeParameter( + ITypeParameterSymbol typeParameter, + CancellationToken cancellationToken) + { + var constraints = typeParameter.ConstraintTypes.Where(ts => !ts.IsUnexpressableTypeParameterConstraint()).ToList(); + var classTypes = constraints.Where(ts => ts.TypeKind == TypeKind.Class).ToList(); + var nonClassTypes = constraints.Where(ts => ts.TypeKind != TypeKind.Class).ToList(); + + classTypes = MergeClassTypes(classTypes, cancellationToken); + constraints = classTypes.Concat(nonClassTypes).ToList(); + if (constraints.SequenceEqual(typeParameter.ConstraintTypes)) + { + return typeParameter; + } + + return CodeGenerationSymbolFactory.CreateTypeParameter( + attributes: null, + varianceKind: typeParameter.Variance, + name: typeParameter.Name, + constraintTypes: ImmutableArray.CreateRange<ITypeSymbol>(constraints), + hasConstructorConstraint: typeParameter.HasConstructorConstraint, + hasReferenceConstraint: typeParameter.HasReferenceTypeConstraint, + hasValueConstraint: typeParameter.HasValueTypeConstraint); + } + + private List<ITypeSymbol> MergeClassTypes(List<ITypeSymbol> classTypes, CancellationToken cancellationToken) + { + var compilation = this.Document.SemanticModel.Compilation; + for (int i = classTypes.Count - 1; i >= 0; i--) + { + // For example, 'Attribute'. + var type1 = classTypes[i]; + + for (int j = 0; j < classTypes.Count; j++) + { + if (j != i) + { + // For example 'FooAttribute'. + var type2 = classTypes[j]; + + if (IsImplicitReferenceConversion(compilation, type2, type1)) + { + // If there's an implicit reference conversion (i.e. from + // FooAttribute to Attribute), then we don't need Attribute as it's + // implied by the second attribute; + classTypes.RemoveAt(i); + break; + } + } + } + } + + return classTypes; + } + + protected abstract bool IsImplicitReferenceConversion(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType); + } + private partial class GenerateParameterizedMemberCodeAction : CodeAction + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + private readonly bool _isAbstract; + private readonly bool _generateProperty; + + public GenerateParameterizedMemberCodeAction( + TService service, + Document document, + State state, + bool isAbstract, + bool generateProperty) + { + _service = service; + _document = document; + _state = state; + _isAbstract = isAbstract; + _generateProperty = generateProperty; + } + + private string GetDisplayText( + State state, + bool isAbstract, + bool generateProperty) + { + switch (state.MethodGenerationKind) + { + case MethodGenerationKind.Member: + var text = generateProperty ? + isAbstract ? Resources.GenerateAbstractProperty : Resources.GeneratePropertyIn : + isAbstract ? Resources.GenerateAbstractMethod : Resources.GenerateMethodIn; + + var name = state.IdentifierToken.ValueText; + var destination = state.TypeToGenerateIn.Name; + return string.Format(text, name, destination); + case MethodGenerationKind.ImplicitConversion: + return _service.GetImplicitConversionDisplayText(_state); + case MethodGenerationKind.ExplicitConversion: + return _service.GetExplicitConversionDisplayText(_state); + default: + throw new InvalidOperationException(); + } + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var syntaxFactory = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language).GetService<SyntaxGenerator>(); + + if (_generateProperty) + { + var property = _state.SignatureInfo.GenerateProperty(syntaxFactory, _isAbstract, _state.IsWrittenTo, cancellationToken); + + var result = await CodeGenerator.AddPropertyDeclarationAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + property, + new CodeGenerationOptions(afterThisLocation: _state.IdentifierToken.GetLocation(), generateDefaultAccessibility: false), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + else + { + var method = _state.SignatureInfo.GenerateMethod(syntaxFactory, _isAbstract, cancellationToken); + + var result = await CodeGenerator.AddMethodDeclarationAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + method, + new CodeGenerationOptions(afterThisLocation: _state.Location, generateDefaultAccessibility: false), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + } + + public override string Title + { + get + { + return GetDisplayText(_state, _isAbstract, _generateProperty); + } + } + } + + protected class MethodSignatureInfo : SignatureInfo + { + private readonly IMethodSymbol _methodSymbol; + + public MethodSignatureInfo( + SemanticDocument document, + State state, + IMethodSymbol methodSymbol) + : base(document, state) + { + _methodSymbol = methodSymbol; + } + + protected override ITypeSymbol DetermineReturnTypeWorker(CancellationToken cancellationToken) + { + if (State.IsInConditionalAccessExpression) + { + return _methodSymbol.ReturnType.RemoveNullableIfPresent(); + } + + return _methodSymbol.ReturnType; + } + + public override IList<ITypeParameterSymbol> DetermineTypeParameters(CancellationToken cancellationToken) + { + return _methodSymbol.TypeParameters; + } + + protected override IList<RefKind> DetermineParameterModifiers(CancellationToken cancellationToken) + { + return _methodSymbol.Parameters.Select(p => p.RefKind).ToList(); + } + + protected override IList<bool> DetermineParameterOptionality(CancellationToken cancellationToken) + { + return _methodSymbol.Parameters.Select(p => p.IsOptional).ToList(); + } + + protected override IList<ITypeSymbol> DetermineParameterTypes(CancellationToken cancellationToken) + { + return _methodSymbol.Parameters.Select(p => p.Type).ToList(); + } + + protected override IList<string> DetermineParameterNames(CancellationToken cancellationToken) + { + return _methodSymbol.Parameters.Select(p => p.Name).ToList(); + } + } + + internal protected abstract class SignatureInfo + { + protected readonly SemanticDocument Document; + protected readonly State State; + + public SignatureInfo( + SemanticDocument document, + State state) + { + this.Document = document; + this.State = state; + } + + public abstract IList<ITypeParameterSymbol> DetermineTypeParameters(CancellationToken cancellationToken); + public ITypeSymbol DetermineReturnType(CancellationToken cancellationToken) + { + return FixType(DetermineReturnTypeWorker(cancellationToken), cancellationToken); + } + + protected abstract ITypeSymbol DetermineReturnTypeWorker(CancellationToken cancellationToken); + protected abstract IList<RefKind> DetermineParameterModifiers(CancellationToken cancellationToken); + protected abstract IList<ITypeSymbol> DetermineParameterTypes(CancellationToken cancellationToken); + protected abstract IList<bool> DetermineParameterOptionality(CancellationToken cancellationToken); + protected abstract IList<string> DetermineParameterNames(CancellationToken cancellationToken); + + internal IPropertySymbol GenerateProperty( + SyntaxGenerator factory, + bool isAbstract, bool includeSetter, + CancellationToken cancellationToken) + { + var accessibility = DetermineAccessibility(isAbstract); + var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: null, + accessibility: accessibility, + statements: GenerateStatements(factory, isAbstract, cancellationToken)); + + var setMethod = includeSetter ? getMethod : null; + + return CodeGenerationSymbolFactory.CreatePropertySymbol( + attributes: null, + accessibility: accessibility, + modifiers: DeclarationModifiers.None.WithIsStatic(State.IsStatic).WithIsAbstract (isAbstract), + type: DetermineReturnType(cancellationToken), + explicitInterfaceSymbol: null, + name: this.State.IdentifierToken.ValueText, + parameters: DetermineParameters(cancellationToken), + getMethod: getMethod, + setMethod: setMethod); + } + + public IMethodSymbol GenerateMethod( + SyntaxGenerator factory, + bool isAbstract, + CancellationToken cancellationToken) + { + var parameters = DetermineParameters(cancellationToken); + var returnType = DetermineReturnType(cancellationToken); + var isUnsafe = (parameters + .Any(p => p.Type.IsUnsafe()) || returnType.IsUnsafe()) && + !State.IsContainedInUnsafeType; + var method = CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes: null, + accessibility: DetermineAccessibility(isAbstract), + modifiers: DeclarationModifiers.None.WithIsStatic(State.IsStatic).WithIsAbstract (isAbstract).WithIsUnsafe (isUnsafe), + returnType: returnType, + explicitInterfaceSymbol: null, + name: this.State.IdentifierToken.ValueText, + typeParameters: DetermineTypeParameters(cancellationToken), + parameters: parameters, + statements: GenerateStatements(factory, isAbstract, cancellationToken), + handlesExpressions: null, + returnTypeAttributes: null, + methodKind: State.MethodKind); + + // Ensure no conflicts between type parameter names and parameter names. + var languageServiceProvider = this.Document.Project.Solution.Workspace.Services.GetLanguageServices(this.State.TypeToGenerateIn.Language); + + var equalityComparer = true ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + var reservedParameterNames = this.DetermineParameterNames(cancellationToken).ToSet(equalityComparer); + var newTypeParameterNames = NameGenerator.EnsureUniqueness( + method.TypeParameters.Select(t => t.Name).ToList(), n => !reservedParameterNames.Contains(n)); + + return method.RenameTypeParameters(newTypeParameterNames); + } + + private ITypeSymbol FixType( + ITypeSymbol typeSymbol, + CancellationToken cancellationToken) + { + // A type can't refer to a type parameter that isn't available in the type we're + // eventually generating into. + var availableMethodTypeParameters = this.DetermineTypeParameters(cancellationToken); + var availableTypeParameters = this.State.TypeToGenerateIn.GetAllTypeParameters(); + + var compilation = this.Document.SemanticModel.Compilation; + var allTypeParameters = availableMethodTypeParameters.Concat(availableTypeParameters); + + return typeSymbol.RemoveAnonymousTypes(compilation) + .ReplaceTypeParametersBasedOnTypeConstraints(compilation, allTypeParameters, this.Document.Document.Project.Solution, cancellationToken) + .RemoveUnavailableTypeParameters(compilation, allTypeParameters) + .RemoveUnnamedErrorTypes(compilation); + } + + private IList<SyntaxNode> GenerateStatements( + SyntaxGenerator factory, + bool isAbstract, + CancellationToken cancellationToken) + { + var throwStatement = CodeGenerationHelpers.GenerateThrowStatement(factory, this.Document, "System.NotImplementedException", cancellationToken); + + return isAbstract || State.TypeToGenerateIn.TypeKind == TypeKind.Interface || throwStatement == null + ? null + : new[] { throwStatement }; + } + + private IList<IParameterSymbol> DetermineParameters(CancellationToken cancellationToken) + { + var modifiers = DetermineParameterModifiers(cancellationToken); + var types = DetermineParameterTypes(cancellationToken).Select(t => FixType(t, cancellationToken)).ToList(); + var optionality = DetermineParameterOptionality(cancellationToken); + var names = DetermineParameterNames(cancellationToken); + + var result = new List<IParameterSymbol>(); + for (var i = 0; i < modifiers.Count; i++) + { + result.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( + attributes: null, + refKind: modifiers[i], + isParams: false, + isOptional: optionality[i], + type: types[i], + name: names[i])); + } + + return result; + } + + private Accessibility DetermineAccessibility(bool isAbstract) + { + var containingType = this.State.ContainingType; + + // If we're generating into an interface, then we don't use any modifiers. + if (State.TypeToGenerateIn.TypeKind != TypeKind.Interface) + { + // Otherwise, figure out what accessibility modifier to use and optionally + // mark it as static. + if (containingType.IsContainedWithin(State.TypeToGenerateIn) && !isAbstract) + { + return Accessibility.Private; + } + else if (DerivesFrom(containingType) && State.IsStatic) + { + // NOTE(cyrusn): We only generate protected in the case of statics. Consider + // the case where we're generating into one of our base types. i.e.: + // + // class B : A { void Foo() { A a; a.Foo(); } + // + // In this case we can *not* mark the method as protected. 'B' can only + // access protected members of 'A' through an instance of 'B' (or a subclass + // of B). It can not access protected members through an instance of the + // superclass. In this case we need to make the method public or internal. + // + // However, this does not apply if the method will be static. i.e. + // + // class B : A { void Foo() { A.Foo(); } + // + // B can access the protected statics of A, and so we generate 'Foo' as + // protected. + + // TODO: Code coverage + return Accessibility.Protected; + } + else if (containingType.ContainingAssembly.IsSameAssemblyOrHasFriendAccessTo(State.TypeToGenerateIn.ContainingAssembly)) + { + return Accessibility.Internal; + } + else + { + // TODO: Code coverage + return Accessibility.Public; + } + } + + return Accessibility.NotApplicable; + } + + private bool DerivesFrom(INamedTypeSymbol containingType) + { + return containingType.GetBaseTypes().Select(t => t.OriginalDefinition) + .OfType<INamedTypeSymbol>() + .Contains(State.TypeToGenerateIn); + } + } + + internal protected abstract class State + { + public INamedTypeSymbol ContainingType { get; protected set; } + public INamedTypeSymbol TypeToGenerateIn { get; protected set; } + public bool IsStatic { get; protected set; } + public bool IsContainedInUnsafeType { get; protected set; } + + // Just the name of the method. i.e. "Foo" in "X.Foo" or "X.Foo()" + public SyntaxToken IdentifierToken { get; protected set; } + public TSimpleNameSyntax SimpleNameOpt { get; protected set; } + + // The entire expression containing the name, not including the invocation. i.e. "X.Foo" + // in "X.Foo()". + public TExpressionSyntax SimpleNameOrMemberAccessExpression { get; protected set; } + public TInvocationExpressionSyntax InvocationExpressionOpt { get; protected set; } + public bool IsInConditionalAccessExpression { get; protected set; } + + public bool IsWrittenTo { get; protected set; } + + public SignatureInfo SignatureInfo { get; protected set; } + public MethodKind MethodKind { get; internal set; } + public MethodGenerationKind MethodGenerationKind { get; protected set; } + protected Location location = null; + public Location Location + { + get + { + if (IdentifierToken.SyntaxTree != null) + { + return IdentifierToken.GetLocation(); + } + + return location; + } + } + + protected async Task<bool> TryFinishInitializingState(TService service, SemanticDocument document, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + this.TypeToGenerateIn = await SymbolFinder.FindSourceDefinitionAsync(this.TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol; + if (this.TypeToGenerateIn.IsErrorType()) + { + return false; + } + + if (!service.ValidateTypeToGenerateIn(document.Project.Solution, this.TypeToGenerateIn, + this.IsStatic, ClassInterfaceModuleStructTypes, cancellationToken)) + { + return false; + } + + if (!new CSharpCodeGenerationService(document.Project.Solution.Workspace).CanAddTo(this.TypeToGenerateIn, document.Project.Solution, cancellationToken)) + { + return false; + } + + // Ok. It either didn't bind to any symbols, or it bound to a symbol but with + // errors. In the former case we definitely want to offer to generate a method. In + // the latter case, we want to generate a method *unless* there's an existing method + // with the same signature. + var existingMethods = this.TypeToGenerateIn.GetMembers(this.IdentifierToken.ValueText) + .OfType<IMethodSymbol>(); + + var destinationProvider = document.Project.Solution.Workspace.Services.GetLanguageServices(this.TypeToGenerateIn.Language); + var syntaxFactory = destinationProvider.GetService<SyntaxGenerator>(); + this.IsContainedInUnsafeType = service.ContainingTypesOrSelfHasUnsafeKeyword(this.TypeToGenerateIn); + var generatedMethod = this.SignatureInfo.GenerateMethod(syntaxFactory, false, cancellationToken); + return !existingMethods.Any(m => SignatureComparer.HaveSameSignature(m, generatedMethod, caseSensitive: true, compareParameterName: true, isParameterCaseSensitive: true)); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpCommonGenerationServiceMethods.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpCommonGenerationServiceMethods.cs new file mode 100644 index 0000000000..1f9a4ae970 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpCommonGenerationServiceMethods.cs @@ -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. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + internal static class CSharpCommonGenerationServiceMethods + { + public static bool AreSpecialOptionsActive(SemanticModel semanticModel) + { + return false; + } + + public static bool IsValidSymbol(ISymbol symbol, SemanticModel semanticModel) + { + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateConversionService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateConversionService.cs new file mode 100644 index 0000000000..a1ba0631c4 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateConversionService.cs @@ -0,0 +1,233 @@ +// 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.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public partial class CSharpGenerateConversionService : + AbstractGenerateConversionService<CSharpGenerateConversionService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax> + { + protected override bool IsImplicitConversionGeneration(SyntaxNode node) + { + return node is ExpressionSyntax && + (node.Parent is AssignmentExpressionSyntax || node.Parent is EqualsValueClauseSyntax) && + !(node is CastExpressionSyntax) && + !(node is MemberAccessExpressionSyntax); + } + + protected override bool IsExplicitConversionGeneration(SyntaxNode node) + { + return node is CastExpressionSyntax; + } + + protected override bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType) + { + return containingType.ContainingTypesOrSelfHasUnsafeKeyword(); + } + + protected override AbstractInvocationInfo CreateInvocationMethodInfo(SemanticDocument document, AbstractGenerateParameterizedMemberService<CSharpGenerateConversionService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state) + { + return new CSharpGenerateParameterizedMemberService<CSharpGenerateConversionService>.InvocationExpressionInfo(document, state); + } + + protected override bool AreSpecialOptionsActive(SemanticModel semanticModel) + { + return CSharpCommonGenerationServiceMethods.AreSpecialOptionsActive(semanticModel); + } + + protected override bool IsValidSymbol(ISymbol symbol, SemanticModel semanticModel) + { + return CSharpCommonGenerationServiceMethods.IsValidSymbol(symbol, semanticModel); + } + + protected override bool TryInitializeImplicitConversionState( + SemanticDocument document, + SyntaxNode expression, + ISet<TypeKind> classInterfaceModuleStructTypes, + CancellationToken cancellationToken, + out SyntaxToken identifierToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + if (TryGetConversionMethodAndTypeToGenerateIn(document, expression, classInterfaceModuleStructTypes, cancellationToken, out methodSymbol, out typeToGenerateIn)) + { + identifierToken = SyntaxFactory.Token( + default(SyntaxTriviaList), + SyntaxKind.ImplicitKeyword, + WellKnownMemberNames.ImplicitConversionName, + WellKnownMemberNames.ImplicitConversionName, + default(SyntaxTriviaList)); + return true; + } + + identifierToken = default(SyntaxToken); + methodSymbol = null; + typeToGenerateIn = null; + return false; + } + + protected override bool TryInitializeExplicitConversionState( + SemanticDocument document, + SyntaxNode expression, + ISet<TypeKind> classInterfaceModuleStructTypes, + CancellationToken cancellationToken, + out SyntaxToken identifierToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + if (TryGetConversionMethodAndTypeToGenerateIn(document, expression, classInterfaceModuleStructTypes, cancellationToken, out methodSymbol, out typeToGenerateIn)) + { + identifierToken = SyntaxFactory.Token( + default(SyntaxTriviaList), + SyntaxKind.ImplicitKeyword, + WellKnownMemberNames.ExplicitConversionName, + WellKnownMemberNames.ExplicitConversionName, + default(SyntaxTriviaList)); + return true; + } + + identifierToken = default(SyntaxToken); + methodSymbol = null; + typeToGenerateIn = null; + return false; + } + + private bool TryGetConversionMethodAndTypeToGenerateIn( + SemanticDocument document, + SyntaxNode expression, + ISet<TypeKind> classInterfaceModuleStructTypes, + CancellationToken cancellationToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + var castExpression = expression as CastExpressionSyntax; + if (castExpression != null) + { + return TryGetExplicitConversionMethodAndTypeToGenerateIn( + document, + castExpression, + classInterfaceModuleStructTypes, + cancellationToken, + out methodSymbol, + out typeToGenerateIn); + } + + return TryGetImplicitConversionMethodAndTypeToGenerateIn( + document, + expression, + classInterfaceModuleStructTypes, + cancellationToken, + out methodSymbol, + out typeToGenerateIn); + } + + private bool TryGetExplicitConversionMethodAndTypeToGenerateIn( + SemanticDocument document, + CastExpressionSyntax castExpression, + ISet<TypeKind> classInterfaceModuleStructTypes, + CancellationToken cancellationToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + methodSymbol = null; + typeToGenerateIn = document.SemanticModel.GetTypeInfo(castExpression.Type, cancellationToken).Type as INamedTypeSymbol; + var parameterSymbol = document.SemanticModel.GetTypeInfo(castExpression.Expression, cancellationToken).Type as INamedTypeSymbol; + if (typeToGenerateIn == null || parameterSymbol == null || typeToGenerateIn.IsErrorType() || parameterSymbol.IsErrorType()) + { + return false; + } + + methodSymbol = GenerateMethodSymbol(typeToGenerateIn, parameterSymbol); + + if (!ValidateTypeToGenerateIn( + document.Project.Solution, + typeToGenerateIn, + true, + classInterfaceModuleStructTypes, + cancellationToken)) + { + typeToGenerateIn = parameterSymbol; + } + + return true; + } + + private bool TryGetImplicitConversionMethodAndTypeToGenerateIn( + SemanticDocument document, + SyntaxNode expression, + ISet<TypeKind> classInterfaceModuleStructTypes, + CancellationToken cancellationToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + methodSymbol = null; + typeToGenerateIn = document.SemanticModel.GetTypeInfo(expression, cancellationToken).ConvertedType as INamedTypeSymbol; + var parameterSymbol = document.SemanticModel.GetTypeInfo(expression, cancellationToken).Type as INamedTypeSymbol; + if (typeToGenerateIn == null || parameterSymbol == null || typeToGenerateIn.IsErrorType() || parameterSymbol.IsErrorType()) + { + return false; + } + + methodSymbol = GenerateMethodSymbol(typeToGenerateIn, parameterSymbol); + + if (!ValidateTypeToGenerateIn( + document.Project.Solution, + typeToGenerateIn, + true, + classInterfaceModuleStructTypes, + cancellationToken)) + { + typeToGenerateIn = parameterSymbol; + } + + return true; + } + + private static IMethodSymbol GenerateMethodSymbol(INamedTypeSymbol typeToGenerateIn, INamedTypeSymbol parameterSymbol) + { + // Remove any generic parameters + if (typeToGenerateIn.IsGenericType) + { + typeToGenerateIn = typeToGenerateIn.ConstructUnboundGenericType().ConstructedFrom; + } + + return CodeGenerationSymbolFactory.CreateMethodSymbol( + attributes: SpecializedCollections.EmptyList<AttributeData>(), + accessibility: default(Accessibility), + modifiers: default(DeclarationModifiers), + returnType: typeToGenerateIn, + explicitInterfaceSymbol: null, + name: null, + typeParameters: SpecializedCollections.EmptyList<ITypeParameterSymbol>(), + parameters: new[] { CodeGenerationSymbolFactory.CreateParameterSymbol(parameterSymbol, "v") }, + methodKind: MethodKind.Conversion); + } + + protected override string GetImplicitConversionDisplayText(AbstractGenerateParameterizedMemberService<CSharpGenerateConversionService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state) + { + return string.Format(Resources.ImplicitConversionDisplayText, state.TypeToGenerateIn.Name); + } + + protected override string GetExplicitConversionDisplayText(AbstractGenerateParameterizedMemberService<CSharpGenerateConversionService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state) + { + return string.Format(Resources.ExplicitConversionDisplayText, state.TypeToGenerateIn.Name); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateMethodService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateMethodService.cs new file mode 100644 index 0000000000..41155db16b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateMethodService.cs @@ -0,0 +1,173 @@ +// 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.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public partial class CSharpGenerateMethodService : + AbstractGenerateMethodService<CSharpGenerateMethodService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax> + { + protected override bool IsExplicitInterfaceGeneration(SyntaxNode node) + { + return node is MethodDeclarationSyntax; + } + + protected override bool IsSimpleNameGeneration(SyntaxNode node) + { + return node is SimpleNameSyntax; + } + + protected override bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType) + { + return containingType.ContainingTypesOrSelfHasUnsafeKeyword(); + } + + protected override AbstractInvocationInfo CreateInvocationMethodInfo(SemanticDocument document, AbstractGenerateParameterizedMemberService<CSharpGenerateMethodService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state) + { + return new CSharpGenerateParameterizedMemberService<CSharpGenerateMethodService>.InvocationExpressionInfo(document, state); + } + + protected override bool AreSpecialOptionsActive(SemanticModel semanticModel) + { + return CSharpCommonGenerationServiceMethods.AreSpecialOptionsActive(semanticModel); + } + + protected override bool IsValidSymbol(ISymbol symbol, SemanticModel semanticModel) + { + return CSharpCommonGenerationServiceMethods.IsValidSymbol(symbol, semanticModel); + } + + protected override bool TryInitializeExplicitInterfaceState( + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken, + out SyntaxToken identifierToken, + out IMethodSymbol methodSymbol, + out INamedTypeSymbol typeToGenerateIn) + { + var methodDeclaration = (MethodDeclarationSyntax)node; + identifierToken = methodDeclaration.Identifier; + + if (methodDeclaration.ExplicitInterfaceSpecifier != null && + !methodDeclaration.ParameterList.OpenParenToken.IsMissing && + !methodDeclaration.ParameterList.CloseParenToken.IsMissing) + { + var semanticModel = document.SemanticModel; + methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, cancellationToken) as IMethodSymbol; + if (methodSymbol != null && !methodSymbol.ExplicitInterfaceImplementations.Any()) + { + var semanticInfo = semanticModel.GetTypeInfo(methodDeclaration.ExplicitInterfaceSpecifier.Name, cancellationToken); + typeToGenerateIn = semanticInfo.Type as INamedTypeSymbol; + return typeToGenerateIn != null; + } + } + + identifierToken = default(SyntaxToken); + methodSymbol = null; + typeToGenerateIn = null; + return false; + } + + protected override bool TryInitializeSimpleNameState( + SemanticDocument document, + SimpleNameSyntax simpleName, + CancellationToken cancellationToken, + out SyntaxToken identifierToken, + out ExpressionSyntax simpleNameOrMemberAccessExpression, + out InvocationExpressionSyntax invocationExpressionOpt, + out bool isInConditionalAccessExpression) + { + identifierToken = simpleName.Identifier; + + var memberAccess = simpleName?.Parent as MemberAccessExpressionSyntax; + var conditionalMemberAccess = simpleName?.Parent?.Parent?.Parent as ConditionalAccessExpressionSyntax; + var inConditionalMemberAccess = conditionalMemberAccess != null; + if (memberAccess != null) + { + simpleNameOrMemberAccessExpression = (ExpressionSyntax)memberAccess; + } + else if (inConditionalMemberAccess) + { + simpleNameOrMemberAccessExpression = (ExpressionSyntax)conditionalMemberAccess; + } + else + { + simpleNameOrMemberAccessExpression = simpleName; + } + + if (memberAccess == null || memberAccess.Name == simpleName) + { + if (simpleNameOrMemberAccessExpression.IsParentKind(SyntaxKind.InvocationExpression)) + { + invocationExpressionOpt = (InvocationExpressionSyntax)simpleNameOrMemberAccessExpression.Parent; + isInConditionalAccessExpression = inConditionalMemberAccess; + return !invocationExpressionOpt.ArgumentList.CloseParenToken.IsMissing; + } + // We need to check that the tree is structured like so: + // ConditionalAccessExpressionSyntax + // -> InvocationExpressionSyntax + // -> MemberBindingExpressionSyntax + // and that the name at the end of this expression matches the simple name we were given + else if ((((simpleNameOrMemberAccessExpression as ConditionalAccessExpressionSyntax) + ?.WhenNotNull as InvocationExpressionSyntax) + ?.Expression as MemberBindingExpressionSyntax) + ?.Name == simpleName) + { + invocationExpressionOpt = (InvocationExpressionSyntax)((ConditionalAccessExpressionSyntax)simpleNameOrMemberAccessExpression).WhenNotNull; + isInConditionalAccessExpression = inConditionalMemberAccess; + return !invocationExpressionOpt.ArgumentList.CloseParenToken.IsMissing; + } + else if (simpleName.IsKind(SyntaxKind.IdentifierName)) + { + // If we don't have an invocation node, then see if we can infer a delegate in + // this location. Check if this is a place where a delegate can go. Only do this + // for identifier names. for now. It gets really funky if you have to deal with + // a generic name here. + + // Can't assign into a method. + if (!simpleNameOrMemberAccessExpression.IsLeftSideOfAnyAssignExpression()) + { + invocationExpressionOpt = null; + isInConditionalAccessExpression = inConditionalMemberAccess; + return true; + } + } + } + + identifierToken = default(SyntaxToken); + simpleNameOrMemberAccessExpression = null; + invocationExpressionOpt = null; + isInConditionalAccessExpression = false; + return false; + } + + protected override ITypeSymbol CanGenerateMethodForSimpleNameOrMemberAccessExpression( + SemanticModel semanticModel, + ExpressionSyntax expresion, + CancellationToken cancellationToken) + { + if (semanticModel.SyntaxTree.IsNameOfContext(expresion.SpanStart, semanticModel, cancellationToken)) + { + return TypeGuessing.typeInferenceService.InferType(semanticModel, expresion, true, cancellationToken); + } + + return null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs new file mode 100644 index 0000000000..f6784ece1a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -0,0 +1,172 @@ +// 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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + internal abstract class CSharpGenerateParameterizedMemberService<TService> : AbstractGenerateParameterizedMemberService<TService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax> + where TService : AbstractGenerateParameterizedMemberService<TService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax> + { + internal protected partial class InvocationExpressionInfo : AbstractInvocationInfo + { + private readonly InvocationExpressionSyntax _invocationExpression; + + public InvocationExpressionInfo( + SemanticDocument document, + AbstractGenerateParameterizedMemberService<TService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state) + : base(document, state) + { + _invocationExpression = state.InvocationExpressionOpt; + } + + protected override IList<string> DetermineParameterNames(CancellationToken cancellationToken) + { + return this.Document.SemanticModel.GenerateParameterNames( + _invocationExpression.ArgumentList); + } + + protected override ITypeSymbol DetermineReturnTypeWorker(CancellationToken cancellationToken) + { + // Defer to the type inferrer to figure out what the return type of this new method + // should be. + var inferredType = TypeGuessing.typeInferenceService.InferType(this.Document.SemanticModel, + _invocationExpression, objectAsDefault: true, cancellationToken: cancellationToken); + if (State.IsInConditionalAccessExpression) + { + return inferredType.RemoveNullableIfPresent(); + } + + return inferredType; + } + + protected override IList<ITypeParameterSymbol> GetCapturedTypeParameters(CancellationToken cancellationToken) + { + var result = new List<ITypeParameterSymbol>(); + var semanticModel = this.Document.SemanticModel; + foreach (var argument in _invocationExpression.ArgumentList.Arguments) + { + var type = semanticModel.GetType(argument.Expression, cancellationToken); + type.GetReferencedTypeParameters(result); + } + + return result; + } + + protected override IList<ITypeParameterSymbol> GenerateTypeParameters(CancellationToken cancellationToken) + { + // Generate dummy type parameter names for a generic method. If the user is inside a + // generic method, and calls a generic method with type arguments from the outer + // method, then use those same names for the generated type parameters. + // + // TODO(cyrusn): If we do capture method type variables, then we should probably + // capture their constraints as well. + var genericName = (GenericNameSyntax)this.State.SimpleNameOpt; + var semanticModel = this.Document.SemanticModel; + + if (genericName.TypeArgumentList.Arguments.Count == 1) + { + var typeParameter = GetUniqueTypeParameter( + genericName.TypeArgumentList.Arguments.First(), + s => !State.TypeToGenerateIn.GetAllTypeParameters().Any(t => t.Name == s), + cancellationToken); + + return new List<ITypeParameterSymbol> { typeParameter }; + } + else + { + var list = new List<ITypeParameterSymbol>(); + + var usedIdentifiers = new HashSet<string> { "T" }; + foreach (var type in genericName.TypeArgumentList.Arguments) + { + var typeParameter = GetUniqueTypeParameter( + type, + s => !usedIdentifiers.Contains(s) && !State.TypeToGenerateIn.GetAllTypeParameters().Any(t => t.Name == s), + cancellationToken); + + usedIdentifiers.Add(typeParameter.Name); + + list.Add(typeParameter); + } + + return list; + } + } + + private ITypeParameterSymbol GetUniqueTypeParameter( + TypeSyntax type, + Func<string, bool> isUnique, + CancellationToken cancellationToken) + { + var methodTypeParameter = GetMethodTypeParameter(type, cancellationToken); + return methodTypeParameter != null + ? methodTypeParameter + : CodeGenerationSymbolFactory.CreateTypeParameterSymbol(NameGenerator.GenerateUniqueName("T", isUnique)); + } + + private ITypeParameterSymbol GetMethodTypeParameter(TypeSyntax type, CancellationToken cancellationToken) + { + if (type is IdentifierNameSyntax) + { + var info = this.Document.SemanticModel.GetTypeInfo(type, cancellationToken); + if (info.Type is ITypeParameterSymbol && + ((ITypeParameterSymbol)info.Type).TypeParameterKind == TypeParameterKind.Method) + { + return (ITypeParameterSymbol)info.Type; + } + } + + return null; + } + + protected override IList<RefKind> DetermineParameterModifiers(CancellationToken cancellationToken) + { + return + _invocationExpression.ArgumentList.Arguments.Select( + a => a.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword ? RefKind.Ref : + a.RefOrOutKeyword.Kind() == SyntaxKind.OutKeyword ? RefKind.Out : RefKind.None).ToList(); + } + + protected override IList<ITypeSymbol> DetermineParameterTypes(CancellationToken cancellationToken) + { + return _invocationExpression.ArgumentList.Arguments.Select(a => DetermineParameterType(a, cancellationToken)).ToList(); + } + + private ITypeSymbol DetermineParameterType( + ArgumentSyntax argument, + CancellationToken cancellationToken) + { + return argument.DetermineParameterType(this.Document.SemanticModel, cancellationToken); + } + + protected override IList<bool> DetermineParameterOptionality(CancellationToken cancellationToken) + { + return _invocationExpression.ArgumentList.Arguments.Select(a => false).ToList(); + } + + protected override bool IsIdentifierName() + { + return this.State.SimpleNameOpt.Kind() == SyntaxKind.IdentifierName; + } + + protected override bool IsImplicitReferenceConversion(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType) + { + var conversion = compilation.ClassifyConversion(sourceType, targetType); + return conversion.IsImplicit && conversion.IsReference; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/MethodGenerationKind.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/MethodGenerationKind.cs new file mode 100644 index 0000000000..cbafecdebb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateParameterizedMember/MethodGenerationKind.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. + +namespace ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember +{ + public enum MethodGenerationKind + { + Member, + ImplicitConversion, + ExplicitConversion + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs new file mode 100644 index 0000000000..e4bc88e3cc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs @@ -0,0 +1,696 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp.GenerateMember; +using ICSharpCode.NRefactory6.CSharp; +using Microsoft.CodeAnalysis.Editing; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.GenerateMember.GenerateVariable +{ + public abstract partial class AbstractGenerateVariableService<TService, TSimpleNameSyntax, TExpressionSyntax> : + AbstractGenerateMemberService<TSimpleNameSyntax, TExpressionSyntax> + where TService : AbstractGenerateVariableService<TService, TSimpleNameSyntax, TExpressionSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + { + protected AbstractGenerateVariableService() + { + } + + protected abstract bool IsExplicitInterfaceGeneration(SyntaxNode node); + protected abstract bool IsIdentifierNameGeneration(SyntaxNode node); + + protected abstract bool TryInitializeExplicitInterfaceState(SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken, out SyntaxToken identifierToken, out IPropertySymbol propertySymbol, out INamedTypeSymbol typeToGenerateIn); + protected abstract bool TryInitializeIdentifierNameState(SemanticDocument document, TSimpleNameSyntax identifierName, CancellationToken cancellationToken, out SyntaxToken identifierToken, out TExpressionSyntax simpleNameOrMemberAccessExpression, out bool isInExecutableBlock, out bool isinConditionalAccessExpression); + + protected abstract bool TryConvertToLocalDeclaration(ITypeSymbol type, SyntaxToken identifierToken, OptionSet options, out SyntaxNode newRoot); + + public async Task<IEnumerable<CodeAction>> GenerateVariableAsync( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); + if (state == null) + { + return SpecializedCollections.EmptyEnumerable<CodeAction>(); + } + + var result = new List<CodeAction>(); + + var canGenerateMember = CodeGenerator.CanAdd(document.Project.Solution, state.TypeToGenerateIn, cancellationToken); + + // prefer fields over properties (and vice versa) depending on the casing of the member. + // lowercase -> fields. title case -> properties. + var name = state.IdentifierToken.ValueText; + if (char.IsUpper(name.FirstOrDefault())) + { + if (canGenerateMember) + { + AddPropertyCodeActions(result, document, state); + AddFieldCodeActions(result, document, state); + } + + AddLocalCodeActions(result, document, state); + } + else + { + if (canGenerateMember) + { + AddFieldCodeActions(result, document, state); + AddPropertyCodeActions(result, document, state); + } + + AddLocalCodeActions(result, document, state); + } + + return result; + } + + protected virtual bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType) + { + return false; + } + + private void AddPropertyCodeActions(List<CodeAction> result, Document document, State state) + { + if (state.IsInRefContext || state.IsInOutContext) + { + return; + } + + if (state.IsConstant) + { + return; + } + + if (state.TypeToGenerateIn.TypeKind == TypeKind.Interface && state.IsStatic) + { + return; + } + + result.Add(new GenerateVariableCodeAction((TService)this, document, state, generateProperty: true, isReadonly: false, isConstant: false)); + + if (state.TypeToGenerateIn.TypeKind == TypeKind.Interface && !state.IsWrittenTo) + { + result.Add(new GenerateVariableCodeAction((TService)this, document, state, generateProperty: true, isReadonly: true, isConstant: false)); + } + } + + private void AddFieldCodeActions(List<CodeAction> result, Document document, State state) + { + if (state.TypeToGenerateIn.TypeKind != TypeKind.Interface) + { + if (state.IsConstant) + { + result.Add(new GenerateVariableCodeAction((TService)this, document, state, generateProperty: false, isReadonly: false, isConstant: true)); + } + else + { + result.Add(new GenerateVariableCodeAction((TService)this, document, state, generateProperty: false, isReadonly: false, isConstant: false)); + + // If we haven't written to the field, or we're in the constructor for the type + // we're writing into, then we can generate this field read-only. + if (!state.IsWrittenTo || state.IsInConstructor) + { + result.Add(new GenerateVariableCodeAction((TService)this, document, state, generateProperty: false, isReadonly: true, isConstant: false)); + } + } + } + } + + private void AddLocalCodeActions(List<CodeAction> result, Document document, State state) + { + if (state.CanGenerateLocal()) + { + result.Add(new GenerateLocalCodeAction((TService)this, document, state)); + } + } + + private partial class GenerateVariableCodeAction : CodeAction + { + private readonly TService _service; + private readonly State _state; + private readonly bool _generateProperty; + private readonly bool _isReadonly; + private readonly bool _isConstant; + private readonly Document _document; + private readonly string _equivalenceKey; + + public GenerateVariableCodeAction( + TService service, + Document document, + State state, + bool generateProperty, + bool isReadonly, + bool isConstant) + { + _service = service; + _document = document; + _state = state; + _generateProperty = generateProperty; + _isReadonly = isReadonly; + _isConstant = isConstant; + _equivalenceKey = Title; + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var generateUnsafe = _state.TypeMemberType.IsUnsafe() && + !_state.IsContainedInUnsafeType; + + if (_generateProperty) + { + var getAccessor = CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: null, + accessibility: DetermineMaximalAccessibility(_state), + statements: null); + var setAccessor = _isReadonly ? null : CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: null, + accessibility: DetermineMinimalAccessibility(_state), + statements: null); + + var result = await CodeGenerator.AddPropertyDeclarationAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + CodeGenerationSymbolFactory.CreatePropertySymbol( + attributes: null, + accessibility: DetermineMaximalAccessibility(_state), + modifiers: DeclarationModifiers.None.WithIsStatic(_state.IsStatic).WithIsUnsafe (generateUnsafe), + type: _state.TypeMemberType, + explicitInterfaceSymbol: null, + name: _state.IdentifierToken.ValueText, + isIndexer: _state.IsIndexer, + parameters: _state.Parameters, + getMethod: getAccessor, + setMethod: setAccessor), + new CodeGenerationOptions(contextLocation: _state.IdentifierToken.GetLocation(), generateDefaultAccessibility: false), + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + return result; + } + else + { + var result = await CodeGenerator.AddFieldDeclarationAsync( + _document.Project.Solution, + _state.TypeToGenerateIn, + CodeGenerationSymbolFactory.CreateFieldSymbol( + attributes: null, + accessibility: DetermineMinimalAccessibility(_state), + modifiers: _isConstant ? + DeclarationModifiers.None.WithIsConst(true).WithIsUnsafe(generateUnsafe) : + DeclarationModifiers.None.WithIsStatic(_state.IsStatic).WithIsReadOnly (_isReadonly).WithIsUnsafe(generateUnsafe), + type: _state.TypeMemberType, + name: _state.IdentifierToken.ValueText), + new CodeGenerationOptions(contextLocation: _state.IdentifierToken.GetLocation(), generateDefaultAccessibility: false), + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + return result; + } + } + + private Accessibility DetermineMaximalAccessibility(State state) + { + if (state.TypeToGenerateIn.TypeKind == TypeKind.Interface) + { + return Accessibility.NotApplicable; + } + + var accessibility = Accessibility.Public; + + // Ensure that we're not overly exposing a type. + var containingTypeAccessibility = state.TypeToGenerateIn.DetermineMinimalAccessibility(); + var effectiveAccessibility = CommonAccessibilityUtilities.Minimum( + containingTypeAccessibility, accessibility); + + var returnTypeAccessibility = state.TypeMemberType.DetermineMinimalAccessibility(); + + if (CommonAccessibilityUtilities.Minimum(effectiveAccessibility, returnTypeAccessibility) != + effectiveAccessibility) + { + return returnTypeAccessibility; + } + + return accessibility; + } + + private Accessibility DetermineMinimalAccessibility(State state) + { + if (state.TypeToGenerateIn.TypeKind == TypeKind.Interface) + { + return Accessibility.NotApplicable; + } + + // Otherwise, figure out what accessibility modifier to use and optionally mark + // it as static. + if (state.SimpleNameOrMemberAccessExpressionOpt.IsAttributeNamedArgumentIdentifier()) + { + return Accessibility.Public; + } + else if (state.ContainingType.IsContainedWithin(state.TypeToGenerateIn)) + { + return Accessibility.Private; + } + else if (DerivesFrom(state, state.ContainingType) && state.IsStatic) + { + // NOTE(cyrusn): We only generate protected in the case of statics. Consider + // the case where we're generating into one of our base types. i.e.: + // + // class B : A { void Foo() { A a; a.Foo(); } + // + // In this case we can *not* mark the method as protected. 'B' can only + // access protected members of 'A' through an instance of 'B' (or a subclass + // of B). It can not access protected members through an instance of the + // superclass. In this case we need to make the method public or internal. + // + // However, this does not apply if the method will be static. i.e. + // + // class B : A { void Foo() { A.Foo(); } + // + // B can access the protected statics of A, and so we generate 'Foo' as + // protected. + return Accessibility.Protected; + } + else if (state.ContainingType.ContainingAssembly.IsSameAssemblyOrHasFriendAccessTo(state.TypeToGenerateIn.ContainingAssembly)) + { + return Accessibility.Internal; + } + else + { + // TODO: Code coverage - we need a unit-test that generates across projects + return Accessibility.Public; + } + } + + private bool DerivesFrom(State state, INamedTypeSymbol containingType) + { + return containingType.GetBaseTypes().Select(t => t.OriginalDefinition) + .Contains(state.TypeToGenerateIn); + } + + public override string Title + { + get + { + var text = _isConstant + ? Resources.GenerateConstantIn + : _generateProperty + ? _isReadonly ? Resources.GenerateReadonlyProperty : Resources.GeneratePropertyIn + : _isReadonly ? Resources.GenerateReadonlyField : Resources.GenerateFieldIn; + + return string.Format( + text, + _state.IdentifierToken.ValueText, + _state.TypeToGenerateIn.Name); + } + } + + public override string EquivalenceKey + { + get + { + return _equivalenceKey; + } + } + } + + private class GenerateLocalCodeAction : CodeAction + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + + public GenerateLocalCodeAction(TService service, Document document, State state) + { + _service = service; + _document = document; + _state = state; + } + + public override string Title + { + get + { + var text = Resources.GenerateLocal; + + return string.Format( + text, + _state.IdentifierToken.ValueText); + } + } + + protected override Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var newRoot = GetNewRoot(cancellationToken); + var newDocument = _document.WithSyntaxRoot(newRoot); + + return Task.FromResult(newDocument); + } + + private SyntaxNode GetNewRoot(CancellationToken cancellationToken) + { + SyntaxNode newRoot; + if (_service.TryConvertToLocalDeclaration(_state.LocalType, _state.IdentifierToken, _document.Project.Solution.Workspace.Options, out newRoot)) + { + return newRoot; + } + + var syntaxFactory = _document.GetLanguageService<SyntaxGenerator>(); + var initializer = _state.IsOnlyWrittenTo + ? null + : syntaxFactory.DefaultExpression(_state.LocalType); + + var type = _state.LocalType; + var localStatement = syntaxFactory.LocalDeclarationStatement(type, _state.IdentifierToken.ValueText, initializer); + localStatement = localStatement.WithAdditionalAnnotations(Microsoft.CodeAnalysis.Formatting.Formatter.Annotation); + + var codeGenService = new CSharpCodeGenerationService (_document.Project.Solution.Workspace); + var root = _state.IdentifierToken.GetAncestors<SyntaxNode>().Last(); + + return codeGenService.AddStatements( + root, + SpecializedCollections.SingletonEnumerable(localStatement), + options: new CodeGenerationOptions(beforeThisLocation: _state.IdentifierToken.GetLocation()), + cancellationToken: cancellationToken); + } + } + + private partial class State + { + public INamedTypeSymbol ContainingType { get; private set; } + public INamedTypeSymbol TypeToGenerateIn { get; private set; } + public bool IsStatic { get; private set; } + public bool IsConstant { get; private set; } + public bool IsIndexer { get; private set; } + public bool IsContainedInUnsafeType { get; private set; } + public IList<IParameterSymbol> Parameters { get; private set; } + + // Just the name of the method. i.e. "Foo" in "Foo" or "X.Foo" + public SyntaxToken IdentifierToken { get; private set; } + public TSimpleNameSyntax SimpleNameOpt { get; private set; } + + // The entire expression containing the name. i.e. "X.Foo" + public TExpressionSyntax SimpleNameOrMemberAccessExpressionOpt { get; private set; } + + public ITypeSymbol TypeMemberType { get; private set; } + public ITypeSymbol LocalType { get; private set; } + + public bool IsWrittenTo { get; private set; } + public bool IsOnlyWrittenTo { get; private set; } + + public bool IsInConstructor { get; private set; } + public bool IsInRefContext { get; private set; } + public bool IsInOutContext { get; private set; } + public bool IsInMemberContext { get; private set; } + + public bool IsInExecutableBlock { get; private set; } + public bool IsInConditionalAccessExpression { get; private set; } + + public static async Task<State> GenerateAsync( + TService service, + SemanticDocument document, + SyntaxNode interfaceNode, + CancellationToken cancellationToken) + { + var state = new State(); + if (!await state.TryInitializeAsync(service, document, interfaceNode, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return state; + } + + private async Task<bool> TryInitializeAsync( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + if (service.IsIdentifierNameGeneration(node)) + { + // Cases that we deal with currently: + // + // 1) expr.Foo + // 2) expr->Foo + // 3) Foo + if (!TryInitializeSimpleName(service, document, (TSimpleNameSyntax)node, cancellationToken)) + { + return false; + } + } + else if (service.IsExplicitInterfaceGeneration(node)) + { + // 4) bool IFoo.NewProp + if (!TryInitializeExplicitInterface(service, document, node, cancellationToken)) + { + return false; + } + } + else + { + return false; + } + + // Ok. It either didn't bind to any symbols, or it bound to a symbol but with + // errors. In the former case we definitely want to offer to generate a field. In + // the latter case, we want to generate a field *unless* there's an existing member + // with the same name. Note: it's ok if there's a method with the same name. + var existingMembers = this.TypeToGenerateIn.GetMembers(this.IdentifierToken.ValueText) + .Where(m => m.Kind != SymbolKind.Method); + if (existingMembers.Any()) + { + // TODO: Code coverage + // There was an existing method that the new method would clash with. + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + return false; + } + + this.TypeToGenerateIn = await SymbolFinder.FindSourceDefinitionAsync(this.TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol; + + if (!service.ValidateTypeToGenerateIn( + document.Project.Solution, this.TypeToGenerateIn, this.IsStatic, ClassInterfaceModuleStructTypes, cancellationToken)) + { + return false; + } + + this.IsContainedInUnsafeType = service.ContainingTypesOrSelfHasUnsafeKeyword(this.TypeToGenerateIn); + + return CanGenerateLocal() || CodeGenerator.CanAdd(document.Project.Solution, this.TypeToGenerateIn, cancellationToken); + } + + internal bool CanGenerateLocal() + { + return !this.IsInMemberContext && this.IsInExecutableBlock; + } + + private bool TryInitializeExplicitInterface( + TService service, + SemanticDocument document, + SyntaxNode propertyDeclaration, + CancellationToken cancellationToken) + { + SyntaxToken identifierToken; + IPropertySymbol propertySymbol; + INamedTypeSymbol typeToGenerateIn; + if (!service.TryInitializeExplicitInterfaceState( + document, propertyDeclaration, cancellationToken, + out identifierToken, out propertySymbol, out typeToGenerateIn)) + { + return false; + } + + this.IdentifierToken = identifierToken; + this.TypeToGenerateIn = typeToGenerateIn; + + if (propertySymbol.ExplicitInterfaceImplementations.Any()) + { + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var semanticModel = document.SemanticModel; + this.ContainingType = semanticModel.GetEnclosingNamedType(this.IdentifierToken.SpanStart, cancellationToken); + if (this.ContainingType == null) + { + return false; + } + + if (!this.ContainingType.Interfaces.OfType<INamedTypeSymbol>().Contains(this.TypeToGenerateIn)) + { + return false; + } + + this.IsIndexer = propertySymbol.IsIndexer; + this.Parameters = propertySymbol.Parameters; + this.TypeMemberType = propertySymbol.Type; + + // By default, make it readonly, unless there's already an setter defined. + this.IsWrittenTo = propertySymbol.SetMethod != null; + + return true; + } + + private bool TryInitializeSimpleName( + TService service, + SemanticDocument document, + TSimpleNameSyntax simpleName, + CancellationToken cancellationToken) + { + SyntaxToken identifierToken; + TExpressionSyntax simpleNameOrMemberAccessExpression; + bool isInExecutableBlock; + bool isInConditionalAccessExpression; + if (!service.TryInitializeIdentifierNameState( + document, simpleName, cancellationToken, + out identifierToken, out simpleNameOrMemberAccessExpression, out isInExecutableBlock, out isInConditionalAccessExpression)) + { + return false; + } + + if (string.IsNullOrWhiteSpace(identifierToken.ValueText)) + { + return false; + } + + this.SimpleNameOpt = simpleName; + this.IdentifierToken = identifierToken; + this.SimpleNameOrMemberAccessExpressionOpt = simpleNameOrMemberAccessExpression; + this.IsInExecutableBlock = isInExecutableBlock; + this.IsInConditionalAccessExpression = isInConditionalAccessExpression; + + // If we're in a type context then we shouldn't offer to generate a field or + // property. + if (SimpleNameOrMemberAccessExpressionOpt.IsInNamespaceOrTypeContext()) + { + return false; + } + + var expr = SimpleNameOrMemberAccessExpressionOpt as ExpressionSyntax; + this.IsConstant = expr.IsInConstantContext(); + + // If we're not in a type, don't even bother. NOTE(cyrusn): We'll have to rethink this + // for C# Script. + cancellationToken.ThrowIfCancellationRequested(); + var semanticModel = document.SemanticModel; + this.ContainingType = semanticModel.GetEnclosingNamedType(this.IdentifierToken.SpanStart, cancellationToken); + if (this.ContainingType == null) + { + return false; + } + + // Now, try to bind the invocation and see if it succeeds or not. if it succeeds and + // binds uniquely, then we don't need to offer this quick fix. + cancellationToken.ThrowIfCancellationRequested(); + var semanticInfo = semanticModel.GetSymbolInfo(this.SimpleNameOrMemberAccessExpressionOpt, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + if (semanticInfo.Symbol != null) + { + return false; + } + + // Either we found no matches, or this was ambiguous. Either way, we might be able + // to generate a method here. Determine where the user wants to generate the method + // into, and if it's valid then proceed. + cancellationToken.ThrowIfCancellationRequested(); + INamedTypeSymbol typeToGenerateIn; + bool isStatic; + if (!service.TryDetermineTypeToGenerateIn(document, this.ContainingType, this.SimpleNameOrMemberAccessExpressionOpt, cancellationToken, + out typeToGenerateIn, out isStatic)) + { + return false; + } + + this.TypeToGenerateIn = typeToGenerateIn; + this.IsStatic = isStatic; + + DetermineFieldType(document, cancellationToken); + + this.IsInRefContext = expr.IsInRefContext(); + this.IsInOutContext = expr.IsInOutContext(); + this.IsWrittenTo = expr.IsWrittenTo(); + this.IsOnlyWrittenTo = expr.IsOnlyWrittenTo(); + this.IsInConstructor = DetermineIsInConstructor(document); + this.IsInMemberContext = this.SimpleNameOpt != this.SimpleNameOrMemberAccessExpressionOpt || + expr.IsObjectInitializerNamedAssignmentIdentifier(); + return true; + } + + private void DetermineFieldType( + SemanticDocument document, + CancellationToken cancellationToken) + { + var inferredType = TypeGuessing.typeInferenceService.InferType( + document.SemanticModel, this.SimpleNameOrMemberAccessExpressionOpt, + objectAsDefault: true, + cancellationToken: cancellationToken); + inferredType = inferredType.SpecialType == SpecialType.System_Void + ? document.SemanticModel.Compilation.ObjectType + : inferredType; + + if (this.IsInConditionalAccessExpression) + { + inferredType = inferredType.RemoveNullableIfPresent(); + } + + // Substitute 'object' for all captured method type parameters. Note: we may need to + // do this for things like anonymous types, as well as captured type parameters that + // aren't in scope in the destination type. + var capturedMethodTypeParameters = inferredType.GetReferencedMethodTypeParameters(); + var mapping = capturedMethodTypeParameters.ToDictionary(tp => tp, + tp => document.SemanticModel.Compilation.ObjectType); + + this.TypeMemberType = inferredType.SubstituteTypes(mapping, document.SemanticModel.Compilation); + var availableTypeParameters = this.TypeToGenerateIn.GetAllTypeParameters(); + this.TypeMemberType = TypeMemberType.RemoveUnavailableTypeParameters( + document.SemanticModel.Compilation, availableTypeParameters); + + var enclosingMethodSymbol = document.SemanticModel.GetEnclosingSymbol<IMethodSymbol>(this.SimpleNameOrMemberAccessExpressionOpt.SpanStart, cancellationToken); + if (enclosingMethodSymbol != null && enclosingMethodSymbol.TypeParameters != null && enclosingMethodSymbol.TypeParameters.Count() != 0) + { + var combinedTypeParameters = new List<ITypeParameterSymbol>(); + combinedTypeParameters.AddRange(availableTypeParameters); + combinedTypeParameters.AddRange(enclosingMethodSymbol.TypeParameters); + this.LocalType = inferredType.RemoveUnavailableTypeParameters( + document.SemanticModel.Compilation, combinedTypeParameters); + } + else + { + this.LocalType = this.TypeMemberType; + } + } + + private bool DetermineIsInConstructor(SemanticDocument document) + { + if (!this.ContainingType.OriginalDefinition.Equals(this.TypeToGenerateIn.OriginalDefinition)) + { + return false; + } + + return SimpleNameOpt.IsInConstructor(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs new file mode 100644 index 0000000000..e305451626 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs @@ -0,0 +1,167 @@ +// 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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.GenerateMember.GenerateVariable; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using ICSharpCode.NRefactory6.CSharp; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.CodeAnalysis.GenerateMember.GenerateVariable +{ + public partial class CSharpGenerateVariableService : + AbstractGenerateVariableService<CSharpGenerateVariableService, SimpleNameSyntax, ExpressionSyntax> + { + protected override bool IsExplicitInterfaceGeneration(SyntaxNode node) + { + return node is PropertyDeclarationSyntax; + } + + protected override bool IsIdentifierNameGeneration(SyntaxNode node) + { + return node is IdentifierNameSyntax; + } + + protected override bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType) + { + return containingType.ContainingTypesOrSelfHasUnsafeKeyword(); + } + + protected override bool TryInitializeExplicitInterfaceState( + SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken, + out SyntaxToken identifierToken, out IPropertySymbol propertySymbol, out INamedTypeSymbol typeToGenerateIn) + { + var propertyDeclaration = (PropertyDeclarationSyntax)node; + identifierToken = propertyDeclaration.Identifier; + + if (propertyDeclaration.ExplicitInterfaceSpecifier != null) + { + var semanticModel = document.SemanticModel; + propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken) as IPropertySymbol; + if (propertySymbol != null && !propertySymbol.ExplicitInterfaceImplementations.Any()) + { + var info = semanticModel.GetTypeInfo(propertyDeclaration.ExplicitInterfaceSpecifier.Name, cancellationToken); + typeToGenerateIn = info.Type as INamedTypeSymbol; + return typeToGenerateIn != null; + } + } + + identifierToken = default(SyntaxToken); + propertySymbol = null; + typeToGenerateIn = null; + return false; + } + + protected override bool TryInitializeIdentifierNameState( + SemanticDocument document, SimpleNameSyntax identifierName, CancellationToken cancellationToken, + out SyntaxToken identifierToken, out ExpressionSyntax simpleNameOrMemberAccessExpression, out bool isInExecutableBlock, out bool isConditionalAccessExpression) + { + identifierToken = identifierName.Identifier; + if (identifierToken.ValueText != string.Empty && + !identifierName.IsVar) + { + var memberAccess = identifierName.Parent as MemberAccessExpressionSyntax; + var conditionalMemberAccess = identifierName.Parent.Parent as ConditionalAccessExpressionSyntax; + if (memberAccess?.Name == identifierName) + { + simpleNameOrMemberAccessExpression = (ExpressionSyntax)memberAccess; + } + else if ((conditionalMemberAccess?.WhenNotNull as MemberBindingExpressionSyntax)?.Name == identifierName) + { + simpleNameOrMemberAccessExpression = conditionalMemberAccess; + } + else + { + simpleNameOrMemberAccessExpression = identifierName; + } + + // If we're being invoked, then don't offer this, offer generate method instead. + // Note: we could offer to generate a field with a delegate type. However, that's + // very esoteric and probably not what most users want. + if (!IsLegal(document, simpleNameOrMemberAccessExpression, cancellationToken)) + { + isInExecutableBlock = false; + isConditionalAccessExpression = false; + return false; + } + + var block = identifierName.GetAncestor<BlockSyntax>(); + isInExecutableBlock = block != null && !block.OverlapsHiddenPosition(cancellationToken); + isConditionalAccessExpression = conditionalMemberAccess != null; + return true; + } + + identifierToken = default(SyntaxToken); + simpleNameOrMemberAccessExpression = null; + isInExecutableBlock = false; + isConditionalAccessExpression = false; + return false; + } + + private bool IsLegal( + SemanticDocument document, + ExpressionSyntax expression, + CancellationToken cancellationToken) + { + // TODO(cyrusn): Consider supporting this at some point. It is difficult because we'd + // need to replace the identifier typed with the fully qualified name of the field we + // were generating. + if (expression.IsParentKind(SyntaxKind.AttributeArgument)) + { + return false; + } + + if (expression.IsParentKind(SyntaxKind.ConditionalAccessExpression)) + { + return true; + } + + return expression.CanReplaceWithLValue(document.SemanticModel, cancellationToken); + } + + protected override bool TryConvertToLocalDeclaration(ITypeSymbol type, SyntaxToken identifierToken, OptionSet options, out SyntaxNode newRoot) + { + var token = (SyntaxToken)identifierToken; + var node = identifierToken.Parent as IdentifierNameSyntax; + if (node.IsLeftSideOfAssignExpression() && node.Parent.IsParentKind(SyntaxKind.ExpressionStatement)) + { + var assignExpression = (AssignmentExpressionSyntax)node.Parent; + var expressionStatement = (StatementSyntax)assignExpression.Parent; + + var declarationStatement = SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration( + GenerateTypeSyntax(type, options), + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator(token, null, SyntaxFactory.EqualsValueClause( + assignExpression.OperatorToken, assignExpression.Right))))); + declarationStatement = declarationStatement.WithAdditionalAnnotations(Formatter.Annotation); + + var root = token.GetAncestor<CompilationUnitSyntax>(); + newRoot = root.ReplaceNode(expressionStatement, declarationStatement); + + return true; + } + + newRoot = null; + return false; + } + + private static TypeSyntax GenerateTypeSyntax(ITypeSymbol type, OptionSet options) + { + return type.ContainsAnonymousType() || + (true/*options.GetOption(CSharpCodeStyleOptions.UseVarWhenDeclaringLocals)*/ && type.TypeKind != TypeKind.Delegate) + ? SyntaxFactory.IdentifierName("var") + : type.GenerateTypeSyntax(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/AbstractGenerateTypeService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/AbstractGenerateTypeService.cs new file mode 100644 index 0000000000..0c89087fed --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/AbstractGenerateTypeService.cs @@ -0,0 +1,1812 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.FindSymbols; +using System.IO; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateType +{ + public abstract partial class AbstractGenerateTypeService<TService, TSimpleNameSyntax, TObjectCreationExpressionSyntax, TExpressionSyntax, TTypeDeclarationSyntax, TArgumentSyntax> + where TService : AbstractGenerateTypeService<TService, TSimpleNameSyntax, TObjectCreationExpressionSyntax, TExpressionSyntax, TTypeDeclarationSyntax, TArgumentSyntax> + where TSimpleNameSyntax : TExpressionSyntax + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TExpressionSyntax : SyntaxNode + where TTypeDeclarationSyntax : SyntaxNode + where TArgumentSyntax : SyntaxNode + { + protected AbstractGenerateTypeService () + { + } + + protected abstract bool TryInitializeState (SemanticDocument document, TSimpleNameSyntax simpleName, CancellationToken cancellationToken, out GenerateTypeServiceStateOptions generateTypeServiceStateOptions); + + protected abstract TExpressionSyntax GetLeftSideOfDot (TSimpleNameSyntax simpleName); + + protected abstract bool TryGetArgumentList (TObjectCreationExpressionSyntax objectCreationExpression, out IList<TArgumentSyntax> argumentList); + + protected abstract string DefaultFileExtension { get; } + + protected abstract IList<ITypeParameterSymbol> GetTypeParameters (State state, SemanticModel semanticModel, CancellationToken cancellationToken); + + protected abstract Accessibility GetAccessibility (State state, SemanticModel semanticModel, bool intoNamespace, CancellationToken cancellationToken); + + protected abstract IList<string> GenerateParameterNames (SemanticModel semanticModel, IList<TArgumentSyntax> arguments); + + protected abstract INamedTypeSymbol DetermineTypeToGenerateIn (SemanticModel semanticModel, TSimpleNameSyntax simpleName, CancellationToken cancellationToken); + + protected abstract ITypeSymbol DetermineArgumentType (SemanticModel semanticModel, TArgumentSyntax argument, CancellationToken cancellationToken); + + protected abstract bool IsInCatchDeclaration (TExpressionSyntax expression); + + protected abstract bool IsArrayElementType (TExpressionSyntax expression); + + protected abstract bool IsInVariableTypeContext (TExpressionSyntax expression); + + protected abstract bool IsInValueTypeConstraintContext (SemanticModel semanticModel, TExpressionSyntax expression, CancellationToken cancellationToken); + + protected abstract bool IsInInterfaceList (TExpressionSyntax expression); + + internal abstract bool TryGetBaseList (TExpressionSyntax expression, out TypeKindOptions returnValue); + + internal abstract bool IsPublicOnlyAccessibility (TExpressionSyntax expression, Project project); + + internal abstract bool IsGenericName (TSimpleNameSyntax simpleName); + + internal abstract bool IsSimpleName (TExpressionSyntax expression); + + internal abstract Solution TryAddUsingsOrImportToDocument (Solution updatedSolution, SyntaxNode modifiedRoot, Document document, TSimpleNameSyntax simpleName, string includeUsingsOrImports, CancellationToken cancellationToken); + + protected abstract bool TryGetNameParts (TExpressionSyntax expression, out IList<string> nameParts); + + public abstract string GetRootNamespace (CompilationOptions options); + + public abstract Task<Tuple<INamespaceSymbol, INamespaceOrTypeSymbol, Location>> GetOrGenerateEnclosingNamespaceSymbol (INamedTypeSymbol namedTypeSymbol, string[] containers, Document selectedDocument, SyntaxNode selectedDocumentRoot, CancellationToken cancellationToken); + + public async Task<IEnumerable<CodeAction>> GenerateTypeAsync ( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + //using (Logger.LogBlock (FunctionId.Refactoring_GenerateType, cancellationToken)) { + var semanticDocument = await SemanticDocument.CreateAsync (document, cancellationToken).ConfigureAwait (false); + + var state = State.Generate ((TService)this, semanticDocument, node, cancellationToken); + if (state != null) { + return GetActions (semanticDocument, node, state, cancellationToken); + } + + return SpecializedCollections.EmptyEnumerable<CodeAction> (); + //} + } + + private IEnumerable<CodeAction> GetActions ( + SemanticDocument document, + SyntaxNode node, + State state, + CancellationToken cancellationToken) + { + var generateNewTypeInDialog = false; + if (state.NamespaceToGenerateInOpt != null) { + var workspace = document.Project.Solution.Workspace; + if (workspace == null || workspace.CanApplyChange (ApplyChangesKind.AddDocument)) { + generateNewTypeInDialog = true; + yield return new GenerateTypeCodeAction ((TService)this, document.Document, state, intoNamespace: true, inNewFile: true); + } + + // If they just are generating "Foo" then we want to offer to generate it into the + // namespace in the same file. However, if they are generating "SomeNS.Foo", then we + // only want to allow them to generate if "SomeNS" is the namespace they are + // currently in. + var isSimpleName = state.SimpleName == state.NameOrMemberAccessExpression; + var generateIntoContaining = IsGeneratingIntoContainingNamespace (document, node, state, cancellationToken); + + if ((isSimpleName || generateIntoContaining) && + CanGenerateIntoContainingNamespace (document, node, state, cancellationToken)) { + yield return new GenerateTypeCodeAction ((TService)this, document.Document, state, intoNamespace: true, inNewFile: false); + } + } + + if (state.TypeToGenerateInOpt != null) { + yield return new GenerateTypeCodeAction ((TService)this, document.Document, state, intoNamespace: false, inNewFile: false); + } + + //if (generateNewTypeInDialog) { + // yield return new GenerateTypeCodeActionWithOption ((TService)this, document.Document, state); + //} + } + + private bool CanGenerateIntoContainingNamespace (SemanticDocument document, SyntaxNode node, State state, CancellationToken cancellationToken) + { + var containingNamespace = document.SemanticModel.GetEnclosingNamespace (node.SpanStart, cancellationToken); + + // Only allow if the containing namespace is one that can be generated + // into. + var decl = containingNamespace.GetDeclarations () + .Where (r => r.SyntaxTree == node.SyntaxTree) + .Select (r => r.GetSyntax (cancellationToken)) + .FirstOrDefault (node.GetAncestorsOrThis<SyntaxNode> ().Contains); + + return + decl != null && + new CSharpCodeGenerationService (document.Project.Solution.Workspace).CanAddTo (decl, document.Project.Solution, cancellationToken); + } + + private bool IsGeneratingIntoContainingNamespace ( + SemanticDocument document, + SyntaxNode node, + State state, + CancellationToken cancellationToken) + { + var containingNamespace = document.SemanticModel.GetEnclosingNamespace (node.SpanStart, cancellationToken); + if (containingNamespace != null) { + var containingNamespaceName = containingNamespace.ToDisplayString (); + return containingNamespaceName.Equals (state.NamespaceToGenerateInOpt); + } + + return false; + } + + protected static string GetTypeName (State state) + { + const string AttributeSuffix = "Attribute"; + + return state.IsAttribute && !state.NameIsVerbatim && !state.Name.EndsWith (AttributeSuffix, StringComparison.Ordinal) + ? state.Name + AttributeSuffix + : state.Name; + } + + protected IList<ITypeParameterSymbol> GetTypeParameters ( + State state, + SemanticModel semanticModel, + IEnumerable<SyntaxNode> typeArguments, + CancellationToken cancellationToken) + { + var arguments = typeArguments.ToList (); + var arity = arguments.Count; + var typeParameters = new List<ITypeParameterSymbol> (); + + // For anything that was a type parameter, just use the name (if we haven't already + // used it). Otherwise, synthesize new names for the parameters. + var names = new string[arity]; + var isFixed = new bool[arity]; + for (var i = 0; i < arity; i++) { + var argument = i < arguments.Count ? arguments [i] : null; + var type = argument == null ? null : semanticModel.GetTypeInfo (argument, cancellationToken).Type; + if (type is ITypeParameterSymbol) { + var name = type.Name; + + // If we haven't seen this type parameter already, then we can use this name + // and 'fix' it so that it doesn't change. Otherwise, use it, but allow it + // to be changed if it collides with anything else. + isFixed [i] = !names.Contains (name); + names [i] = name; + typeParameters.Add ((ITypeParameterSymbol)type); + } else { + names [i] = "T"; + typeParameters.Add (null); + } + } + + // We can use a type parameter as long as it hasn't been used in an outer type. + var canUse = state.TypeToGenerateInOpt == null + ? default(Func<string, bool>) + : s => state.TypeToGenerateInOpt.GetAllTypeParameters ().All (t => t.Name != s); + + var uniqueNames = NameGenerator.EnsureUniqueness (names, isFixed, canUse: canUse); + for (int i = 0; i < uniqueNames.Count; i++) { + if (typeParameters [i] == null || typeParameters [i].Name != uniqueNames [i]) { + typeParameters [i] = CodeGenerationSymbolFactory.CreateTypeParameterSymbol (uniqueNames [i]); + } + } + + return typeParameters; + } + + protected Accessibility DetermineDefaultAccessibility ( + State state, + SemanticModel semanticModel, + bool intoNamespace, + CancellationToken cancellationToken) + { + if (state.IsPublicAccessibilityForTypeGeneration) { + return Accessibility.Public; + } + + // If we're a nested type of the type being generated into, then the new type can be + // private. otherwise, it needs to be internal. + if (!intoNamespace && state.TypeToGenerateInOpt != null) { + var outerTypeSymbol = semanticModel.GetEnclosingNamedType (state.SimpleName.SpanStart, cancellationToken); + + if (outerTypeSymbol != null && outerTypeSymbol.IsContainedWithin (state.TypeToGenerateInOpt)) { + return Accessibility.Private; + } + } + + return Accessibility.Internal; + } + + protected IList<ITypeParameterSymbol> GetAvailableTypeParameters ( + State state, + SemanticModel semanticModel, + bool intoNamespace, + CancellationToken cancellationToken) + { + var availableInnerTypeParameters = GetTypeParameters (state, semanticModel, cancellationToken); + var availableOuterTypeParameters = !intoNamespace && state.TypeToGenerateInOpt != null + ? state.TypeToGenerateInOpt.GetAllTypeParameters () + : SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol> (); + + return availableOuterTypeParameters.Concat (availableInnerTypeParameters).ToList (); + } + + protected bool IsWithinTheImportingNamespace (Document document, int triggeringPosition, string includeUsingsOrImports, CancellationToken cancellationToken) + { + var semanticModel = document.GetSemanticModelAsync (cancellationToken).WaitAndGetResult (cancellationToken); + if (semanticModel != null) { + var namespaceSymbol = semanticModel.GetEnclosingNamespace (triggeringPosition, cancellationToken); + if (namespaceSymbol != null && namespaceSymbol.ToDisplayString ().StartsWith (includeUsingsOrImports, StringComparison.Ordinal)) { + return true; + } + } + + return false; + } + + protected bool GeneratedTypesMustBePublic (Project project) + { +// var projectInfoService = project.Solution.Workspace.Services.GetService<IProjectInfoService> (); +// if (projectInfoService != null) { +// return projectInfoService.GeneratedTypesMustBePublic (project); +// } + + return false; + } + + private class GenerateTypeCodeAction : CodeAction + { + private readonly bool _intoNamespace; + private readonly bool _inNewFile; + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + private readonly string _equivalenceKey; + + public GenerateTypeCodeAction ( + TService service, + Document document, + State state, + bool intoNamespace, + bool inNewFile) + { + _service = service; + _document = document; + _state = state; + _intoNamespace = intoNamespace; + _inNewFile = inNewFile; + _equivalenceKey = Title; + } + + private static string FormatDisplayText ( + State state, + bool inNewFile, + string destination) + { + var finalName = GetTypeName (state); + + if (inNewFile) { + return string.Format (Resources.GenerateForInNewFile, + state.IsStruct ? "struct" : state.IsInterface ? "interface" : "class", + state.Name, destination); + } else { + return string.Format (Resources.GenerateForIn, + state.IsStruct ? "struct" : state.IsInterface ? "interface" : "class", + state.Name, destination); + } + } + + protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync (CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync (_document, cancellationToken).ConfigureAwait (false); + + var editor = new Editor ( _service, semanticDocument, _state, _intoNamespace, _inNewFile, cancellationToken: cancellationToken); + + return await editor.GetOperationsAsync ().ConfigureAwait (false); + } + + public override string Title { + get { + if (_intoNamespace) { + var namespaceToGenerateIn = string.IsNullOrEmpty (_state.NamespaceToGenerateInOpt) ? Resources.GlobalNamespace : _state.NamespaceToGenerateInOpt; + return FormatDisplayText (_state, _inNewFile, namespaceToGenerateIn); + } else { + return FormatDisplayText (_state, inNewFile: false, destination: _state.TypeToGenerateInOpt.Name); + } + } + } + + public override string EquivalenceKey { + get { + return _equivalenceKey; + } + } + } + + private class GenerateTypeCodeActionWithOption : CodeActionWithOptions + { + private readonly TService _service; + private readonly Document _document; + private readonly State _state; + + internal GenerateTypeCodeActionWithOption (TService service, Document document, State state) + { + _service = service; + _document = document; + _state = state; + } + + public override string Title { + get { + return Resources.GenerateNewType; + } + } + + public override string EquivalenceKey { + get { + return _state.Name; + } + } + + public override object GetOptions (CancellationToken cancellationToken) + { + var typeKindValue = GetTypeKindOption (_state); + var isPublicOnlyAccessibility = IsPublicOnlyAccessibility (_state, _document.Project); + + // TODO : Callback + return new GenerateTypeOptionsResult ( + isPublicOnlyAccessibility ? Accessibility.Public : Accessibility.Internal, + TypeKind.Class, + _state.Name, + _document.Project, + true, + _state.Name + ".cs", + null, + null, + _document, + false + ); + /* + // return generateTypeOptionsService.GetGenerateTypeOptions ( +// _state.Name, + // new GenerateTypeDialogOptions (isPublicOnlyAccessibility, typeKindValue, _state.IsAttribute), +// _document, +// notificationService, +// projectManagementService, +// syntaxFactsService); + private class VisualStudioGenerateTypeOptionsService : IGenerateTypeOptionsService + { + private bool _isNewFile = false; + private string _accessSelectString = ""; + private string _typeKindSelectString = ""; + + private IGeneratedCodeRecognitionService _generatedCodeService; + + public VisualStudioGenerateTypeOptionsService(IGeneratedCodeRecognitionService generatedCodeService) + { + _generatedCodeService = generatedCodeService; + } + + public GenerateTypeOptionsResult GetGenerateTypeOptions( + string typeName, + GenerateTypeDialogOptions generateTypeDialogOptions, + Document document, + INotificationService notificationService, + IProjectManagementService projectManagementService, + ISyntaxFactsService syntaxFactsService) + { + var viewModel = new GenerateTypeDialogViewModel( + document, + notificationService, + projectManagementService, + syntaxFactsService, + _generatedCodeService, + generateTypeDialogOptions, + typeName, + document.Project.Language == LanguageNames.CSharp ? ".cs" : ".vb", + _isNewFile, + _accessSelectString, + _typeKindSelectString); + + var dialog = new GenerateTypeDialog(viewModel); + var result = dialog.ShowModal(); + + if (result.HasValue && result.Value) + { + // Retain choice + _isNewFile = viewModel.IsNewFile; + _accessSelectString = viewModel.SelectedAccessibilityString; + _typeKindSelectString = viewModel.SelectedTypeKindString; + + return new GenerateTypeOptionsResult( + accessibility: viewModel.SelectedAccessibility, + typeKind: viewModel.SelectedTypeKind, + typeName: viewModel.TypeName, + project: viewModel.SelectedProject, + isNewFile: viewModel.IsNewFile, + newFileName: viewModel.FileName.Trim(), + folders: viewModel.Folders, + fullFilePath: viewModel.FullFilePath, + existingDocument: viewModel.SelectedDocument, + areFoldersValidIdentifiers: viewModel.AreFoldersValidIdentifiers); + } + else + { + return GenerateTypeOptionsResult.Cancelled; + } + } + } + + */ + } + + private bool IsPublicOnlyAccessibility (State state, Project project) + { + return _service.IsPublicOnlyAccessibility (state.NameOrMemberAccessExpression, project) || _service.IsPublicOnlyAccessibility (state.SimpleName, project); + } + + private TypeKindOptions GetTypeKindOption (State state) + { + TypeKindOptions typeKindValue; + + var gotPreassignedTypeOptions = GetPredefinedTypeKindOption (state, out typeKindValue); + if (!gotPreassignedTypeOptions) { + typeKindValue = state.IsSimpleNameGeneric ? TypeKindOptionsHelper.RemoveOptions (typeKindValue, TypeKindOptions.GenericInCompatibleTypes) : typeKindValue; + typeKindValue = state.IsMembersWithModule ? TypeKindOptionsHelper.AddOption (typeKindValue, TypeKindOptions.Module) : typeKindValue; + typeKindValue = state.IsInterfaceOrEnumNotAllowedInTypeContext ? TypeKindOptionsHelper.RemoveOptions (typeKindValue, TypeKindOptions.Interface, TypeKindOptions.Enum) : typeKindValue; + typeKindValue = state.IsDelegateAllowed ? typeKindValue : TypeKindOptionsHelper.RemoveOptions (typeKindValue, TypeKindOptions.Delegate); + typeKindValue = state.IsEnumNotAllowed ? TypeKindOptionsHelper.RemoveOptions (typeKindValue, TypeKindOptions.Enum) : typeKindValue; + } + + return typeKindValue; + } + + private bool GetPredefinedTypeKindOption (State state, out TypeKindOptions typeKindValueFinal) + { + if (state.IsAttribute) { + typeKindValueFinal = TypeKindOptions.Attribute; + return true; + } + + TypeKindOptions typeKindValue = TypeKindOptions.None; + if (_service.TryGetBaseList (state.NameOrMemberAccessExpression, out typeKindValue) || _service.TryGetBaseList (state.SimpleName, out typeKindValue)) { + typeKindValueFinal = typeKindValue; + return true; + } + + if (state.IsClassInterfaceTypes) { + typeKindValueFinal = TypeKindOptions.BaseList; + return true; + } + + if (state.IsDelegateOnly) { + typeKindValueFinal = TypeKindOptions.Delegate; + return true; + } + + if (state.IsTypeGeneratedIntoNamespaceFromMemberAccess) { + typeKindValueFinal = state.IsSimpleNameGeneric ? TypeKindOptionsHelper.RemoveOptions (TypeKindOptions.MemberAccessWithNamespace, TypeKindOptions.GenericInCompatibleTypes) : TypeKindOptions.MemberAccessWithNamespace; + typeKindValueFinal = state.IsEnumNotAllowed ? TypeKindOptionsHelper.RemoveOptions (typeKindValueFinal, TypeKindOptions.Enum) : typeKindValueFinal; + return true; + } + + typeKindValueFinal = TypeKindOptions.AllOptions; + return false; + } + + protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync (object options, CancellationToken cancellationToken) + { + IEnumerable<CodeActionOperation> operations = null; + + var generateTypeOptions = options as GenerateTypeOptionsResult; + if (generateTypeOptions != null && !generateTypeOptions.IsCancelled) { + var semanticDocument = SemanticDocument.CreateAsync (_document, cancellationToken).WaitAndGetResult (cancellationToken); + var editor = new Editor (_service, semanticDocument, _state, true, generateTypeOptions, cancellationToken); + operations = await editor.GetOperationsAsync ().ConfigureAwait (false); + } + + return operations; + } + } + + protected abstract bool IsConversionImplicit (Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType); + + private partial class Editor + { + private TService _service; + private TargetProjectChangeInLanguage _targetProjectChangeInLanguage = TargetProjectChangeInLanguage.NoChange; + AbstractGenerateTypeService<TService, TSimpleNameSyntax, TObjectCreationExpressionSyntax, TExpressionSyntax, TTypeDeclarationSyntax, TArgumentSyntax> _targetLanguageService; + + private readonly SemanticDocument _document; + private readonly State _state; + private readonly bool _intoNamespace; + private readonly bool _inNewFile; + private readonly bool _fromDialog; + private readonly GenerateTypeOptionsResult _generateTypeOptionsResult; + private readonly CancellationToken _cancellationToken; + + + public Editor ( + TService service, + SemanticDocument document, + State state, + bool intoNamespace, + bool inNewFile, + CancellationToken cancellationToken) + { + _service = service; + _document = document; + _state = state; + _intoNamespace = intoNamespace; + _inNewFile = inNewFile; + _cancellationToken = cancellationToken; + } + + public Editor ( + TService service, + SemanticDocument document, + State state, + bool fromDialog, + GenerateTypeOptionsResult generateTypeOptionsResult, + CancellationToken cancellationToken) + { + _service = service; + _document = document; + _state = state; + _fromDialog = fromDialog; + _generateTypeOptionsResult = generateTypeOptionsResult; + _cancellationToken = cancellationToken; + } + + private enum TargetProjectChangeInLanguage + { + NoChange, + CSharpToVisualBasic, + VisualBasicToCSharp + } + + internal async Task<IEnumerable<CodeActionOperation>> GetOperationsAsync () + { + // Check to see if it is from GFU Dialog + if (!_fromDialog) { + // Generate the actual type declaration. + var namedType = GenerateNamedType (); + + if (_intoNamespace) { + if (_inNewFile) { + // Generating into a new file is somewhat complicated. + var documentName = GetTypeName (_state) + _service.DefaultFileExtension; + + return await GetGenerateInNewFileOperationsAsync ( + namedType, + documentName, + null, + true, + null, + _document.Project, + _document.Project, + isDialog: false).ConfigureAwait (false); + } else { + return await GetGenerateIntoContainingNamespaceOperationsAsync (namedType).ConfigureAwait (false); + } + } else { + return await GetGenerateIntoTypeOperationsAsync (namedType).ConfigureAwait (false); + } + } else { + var namedType = GenerateNamedType (_generateTypeOptionsResult); + +// // Honor the options from the dialog +// // Check to see if the type is requested to be generated in cross language Project +// // e.g.: C# -> VB or VB -> C# +// if (_document.Project.Language != _generateTypeOptionsResult.Project.Language) { +// _targetProjectChangeInLanguage = +// _generateTypeOptionsResult.Project.Language == LanguageNames.CSharp +// ? TargetProjectChangeInLanguage.VisualBasicToCSharp +// : TargetProjectChangeInLanguage.CSharpToVisualBasic; +// +// // Get the cross language service +// _targetLanguageService = _generateTypeOptionsResult.Project.LanguageServices.GetService<IGenerateTypeService> (); +// } + + if (_generateTypeOptionsResult.IsNewFile) { + return await GetGenerateInNewFileOperationsAsync ( + namedType, + _generateTypeOptionsResult.NewFileName, + _generateTypeOptionsResult.Folders, + _generateTypeOptionsResult.AreFoldersValidIdentifiers, + _generateTypeOptionsResult.FullFilePath, + _generateTypeOptionsResult.Project, + _document.Project, + isDialog: true).ConfigureAwait (false); + } else { + return await GetGenerateIntoExistingDocumentAsync ( + namedType, + _document.Project, + _generateTypeOptionsResult, + isDialog: true).ConfigureAwait (false); + } + } + } + + private string GetNamespaceToGenerateInto () + { + var namespaceToGenerateInto = _state.NamespaceToGenerateInOpt.Trim (); + var rootNamespace = _service.GetRootNamespace (_document.SemanticModel.Compilation.Options).Trim (); + if (!string.IsNullOrWhiteSpace (rootNamespace)) { + if (namespaceToGenerateInto == rootNamespace || + namespaceToGenerateInto.StartsWith (rootNamespace + ".", StringComparison.Ordinal)) { + namespaceToGenerateInto = namespaceToGenerateInto.Substring (rootNamespace.Length); + } + } + + return namespaceToGenerateInto; + } + + private string GetNamespaceToGenerateIntoForUsageWithNamespace (Project targetProject, Project triggeringProject) + { + var namespaceToGenerateInto = _state.NamespaceToGenerateInOpt.Trim (); + + if (targetProject.Language == LanguageNames.CSharp || + targetProject == triggeringProject) { + // If the target project is C# project then we don't have to make any modification to the namespace + // or + // This is a VB project generation into itself which requires no change as well + return namespaceToGenerateInto; + } + + // If the target Project is VB then we have to check if the RootNamespace of the VB project is the parent most namespace of the type being generated + // True, Remove the RootNamespace + // False, Add Global to the Namespace + //Contract.Assert (targetProject.Language == LanguageNames.VisualBasic); + var targetLanguageService = _targetLanguageService; +// if (_document.Project.Language == LanguageNames.VisualBasic) { +// targetLanguageService = _service; +// } else { +// targetLanguageService = _targetLanguageService; +// } + + var rootNamespace = targetLanguageService.GetRootNamespace (targetProject.CompilationOptions).Trim (); + if (!string.IsNullOrWhiteSpace (rootNamespace)) { + var rootNamespaceLength = CheckIfRootNamespacePresentInNamespace (namespaceToGenerateInto, rootNamespace); + if (rootNamespaceLength > -1) { + // True, Remove the RootNamespace + namespaceToGenerateInto = namespaceToGenerateInto.Substring (rootNamespaceLength); + } else { + // False, Add Global to the Namespace + namespaceToGenerateInto = AddGlobalDotToTheNamespace (namespaceToGenerateInto); + } + } else { + // False, Add Global to the Namespace + namespaceToGenerateInto = AddGlobalDotToTheNamespace (namespaceToGenerateInto); + } + + return namespaceToGenerateInto; + } + + private string AddGlobalDotToTheNamespace (string namespaceToBeGenerated) + { + return "Global." + namespaceToBeGenerated; + } + + // Returns the length of the meaningful rootNamespace substring part of namespaceToGenerateInto + private int CheckIfRootNamespacePresentInNamespace (string namespaceToGenerateInto, string rootNamespace) + { + if (namespaceToGenerateInto == rootNamespace) { + return rootNamespace.Length; + } + + if (namespaceToGenerateInto.StartsWith (rootNamespace + ".", StringComparison.Ordinal)) { + return rootNamespace.Length + 1; + } + + return -1; + } + + private void AddFoldersToNamespaceContainers (List<string> container, IList<string> folders) + { + // Add the folder as part of the namespace if there are not empty + if (folders != null && folders.Count != 0) { + // Remove the empty entries and replace the spaces in the folder name to '_' + var refinedFolders = folders.Where (n => n != null && !n.IsEmpty ()).Select (n => n.Replace (' ', '_')).ToArray (); + container.AddRange (refinedFolders); + } + } + + private async Task<IEnumerable<CodeActionOperation>> GetGenerateInNewFileOperationsAsync ( + INamedTypeSymbol namedType, + string documentName, + IList<string> folders, + bool areFoldersValidIdentifiers, + string fullFilePath, + Project projectToBeUpdated, + Project triggeringProject, + bool isDialog) + { + // First, we fork the solution with a new, empty, file in it. + var newDocumentId = DocumentId.CreateNewId (projectToBeUpdated.Id, debugName: documentName); + var newSolution = projectToBeUpdated.Solution.AddDocument (newDocumentId, documentName, string.Empty, folders, fullFilePath); + + // Now we get the semantic model for that file we just added. We do that to get the + // root namespace in that new document, along with location for that new namespace. + // That way, when we use the code gen service we can say "add this symbol to the + // root namespace" and it will pick the one in the new file. + var newDocument = newSolution.GetDocument (newDocumentId); + var newSemanticModel = await newDocument.GetSemanticModelAsync (_cancellationToken).ConfigureAwait (false); + var enclosingNamespace = newSemanticModel.GetEnclosingNamespace (0, _cancellationToken); + + var namespaceContainersAndUsings = GetNamespaceContainersAndAddUsingsOrImport (isDialog, folders, areFoldersValidIdentifiers, projectToBeUpdated, triggeringProject); + + var containers = namespaceContainersAndUsings.Item1; + var includeUsingsOrImports = namespaceContainersAndUsings.Item2; + + var rootNamespaceOrType = namedType.GenerateRootNamespaceOrType (containers); + + // Now, actually ask the code gen service to add this namespace or type to the root + // namespace in the new file. This will properly generate the code, and add any + // additional niceties like imports/usings. + var codeGenResult = await CodeGenerator.AddNamespaceOrTypeDeclarationAsync ( + newSolution, + enclosingNamespace, + rootNamespaceOrType, + new CodeGenerationOptions (newSemanticModel.SyntaxTree.GetLocation (new TextSpan ()), generateDefaultAccessibility: false), + _cancellationToken).ConfigureAwait (false); + + // containers is determined to be + // 1: folders -> if triggered from Dialog + // 2: containers -> if triggered not from a Dialog but from QualifiedName + // 3: triggering document folder structure -> if triggered not from a Dialog and a SimpleName + var adjustedContainer = isDialog ? folders : + _state.SimpleName != _state.NameOrMemberAccessExpression ? containers.ToList () : _document.Document.Folders.ToList (); + + // Now, take the code that would be generated and actually create an edit that would + // produce a document with that code in it. + + return CreateAddDocumentAndUpdateUsingsOrImportsOperations ( + projectToBeUpdated, + triggeringProject, + documentName, + await codeGenResult.GetSyntaxRootAsync (_cancellationToken).ConfigureAwait (false), + _document.Document, + includeUsingsOrImports, + adjustedContainer, + SourceCodeKind.Regular, + _cancellationToken); + } + + private IEnumerable<CodeActionOperation> CreateAddDocumentAndUpdateUsingsOrImportsOperations ( + Project projectToBeUpdated, + Project triggeringProject, + string documentName, + SyntaxNode root, + Document generatingDocument, + string includeUsingsOrImports, + IList<string> containers, + SourceCodeKind sourceCodeKind, + CancellationToken cancellationToken) + { + // TODO(cyrusn): make sure documentId is unique. + var documentId = DocumentId.CreateNewId (projectToBeUpdated.Id, documentName); + + var updatedSolution = projectToBeUpdated.Solution.AddDocument (DocumentInfo.Create ( + documentId, + documentName, + containers, + sourceCodeKind, + filePath: Path.Combine (Path.GetDirectoryName (generatingDocument.FilePath), documentName) + )); + + updatedSolution = updatedSolution.WithDocumentSyntaxRoot (documentId, root, PreservationMode.PreserveIdentity); + + // Update the Generating Document with a using if required + if (includeUsingsOrImports != null) { + updatedSolution = _service.TryAddUsingsOrImportToDocument (updatedSolution, null, _document.Document, _state.SimpleName, includeUsingsOrImports, cancellationToken); + } + + // Add reference of the updated project to the triggering Project if they are 2 different projects + updatedSolution = AddProjectReference (projectToBeUpdated, triggeringProject, updatedSolution); + + return new CodeActionOperation[] { + new ApplyChangesOperation (updatedSolution), + new OpenDocumentOperation (documentId) + }; + } + + private static Solution AddProjectReference (Project projectToBeUpdated, Project triggeringProject, Solution updatedSolution) + { + if (projectToBeUpdated != triggeringProject) { + if (!triggeringProject.ProjectReferences.Any (pr => pr.ProjectId == projectToBeUpdated.Id)) { + updatedSolution = updatedSolution.AddProjectReference (triggeringProject.Id, new ProjectReference (projectToBeUpdated.Id)); + } + } + + return updatedSolution; + } + + private async Task<IEnumerable<CodeActionOperation>> GetGenerateIntoContainingNamespaceOperationsAsync (INamedTypeSymbol namedType) + { + var enclosingNamespace = _document.SemanticModel.GetEnclosingNamespace ( + _state.SimpleName.SpanStart, _cancellationToken); + + var solution = _document.Project.Solution; + var codeGenResult = await CodeGenerator.AddNamedTypeDeclarationAsync ( + solution, + enclosingNamespace, + namedType, + new CodeGenerationOptions (afterThisLocation: _document.SyntaxTree.GetLocation (_state.SimpleName.Span), generateDefaultAccessibility: false), + _cancellationToken) + .ConfigureAwait (false); + + return new CodeActionOperation[] { new ApplyChangesOperation (codeGenResult.Project.Solution) }; + } + + private async Task<IEnumerable<CodeActionOperation>> GetGenerateIntoExistingDocumentAsync ( + INamedTypeSymbol namedType, + Project triggeringProject, + GenerateTypeOptionsResult generateTypeOptionsResult, + bool isDialog) + { + var root = await generateTypeOptionsResult.ExistingDocument.GetSyntaxRootAsync (_cancellationToken).ConfigureAwait (false); + var folders = generateTypeOptionsResult.ExistingDocument.Folders; + + var namespaceContainersAndUsings = GetNamespaceContainersAndAddUsingsOrImport (isDialog, new List<string> (folders), generateTypeOptionsResult.AreFoldersValidIdentifiers, generateTypeOptionsResult.Project, triggeringProject); + + var containers = namespaceContainersAndUsings.Item1; + var includeUsingsOrImports = namespaceContainersAndUsings.Item2; + + Tuple<INamespaceSymbol, INamespaceOrTypeSymbol, Location> enclosingNamespaceGeneratedTypeToAddAndLocation = null; + if (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange) { + enclosingNamespaceGeneratedTypeToAddAndLocation = _service.GetOrGenerateEnclosingNamespaceSymbol ( + namedType, + containers, + generateTypeOptionsResult.ExistingDocument, + root, + _cancellationToken).WaitAndGetResult (_cancellationToken); + } else { + enclosingNamespaceGeneratedTypeToAddAndLocation = _targetLanguageService.GetOrGenerateEnclosingNamespaceSymbol ( + namedType, + containers, + generateTypeOptionsResult.ExistingDocument, + root, + _cancellationToken).WaitAndGetResult (_cancellationToken); + } + + var solution = _document.Project.Solution; + var codeGenResult = await CodeGenerator.AddNamespaceOrTypeDeclarationAsync ( + solution, + enclosingNamespaceGeneratedTypeToAddAndLocation.Item1, + enclosingNamespaceGeneratedTypeToAddAndLocation.Item2, + new CodeGenerationOptions (afterThisLocation: enclosingNamespaceGeneratedTypeToAddAndLocation.Item3, generateDefaultAccessibility: false), + _cancellationToken) + .ConfigureAwait (false); + var newRoot = await codeGenResult.GetSyntaxRootAsync (_cancellationToken).ConfigureAwait (false); + var updatedSolution = solution.WithDocumentSyntaxRoot (generateTypeOptionsResult.ExistingDocument.Id, newRoot, PreservationMode.PreserveIdentity); + + // Update the Generating Document with a using if required + if (includeUsingsOrImports != null) { + updatedSolution = _service.TryAddUsingsOrImportToDocument ( + updatedSolution, + generateTypeOptionsResult.ExistingDocument.Id == _document.Document.Id ? newRoot : null, + _document.Document, + _state.SimpleName, + includeUsingsOrImports, + _cancellationToken); + } + + updatedSolution = AddProjectReference (generateTypeOptionsResult.Project, triggeringProject, updatedSolution); + + return new CodeActionOperation[] { new ApplyChangesOperation (updatedSolution) }; + } + + private Tuple<string[], string> GetNamespaceContainersAndAddUsingsOrImport ( + bool isDialog, + IList<string> folders, + bool areFoldersValidIdentifiers, + Project targetProject, + Project triggeringProject) + { + string includeUsingsOrImports = null; + if (!areFoldersValidIdentifiers) { + folders = SpecializedCollections.EmptyList<string> (); + } + + // Now actually create the symbol that we want to add to the root namespace. The + // symbol may either be a named type (if we're not generating into a namespace) or + // it may be a namespace symbol. + string[] containers = null; + if (!isDialog) { + // Not generated from the Dialog + containers = GetNamespaceToGenerateInto ().Split (new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + } else if (!_service.IsSimpleName (_state.NameOrMemberAccessExpression)) { + // If the usage was with a namespace + containers = GetNamespaceToGenerateIntoForUsageWithNamespace (targetProject, triggeringProject).Split (new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + } else { + // Generated from the Dialog + List<string> containerList = new List<string> (); + + string rootNamespaceOfTheProjectGeneratedInto; + + if (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange) { + rootNamespaceOfTheProjectGeneratedInto = _service.GetRootNamespace (_generateTypeOptionsResult.Project.CompilationOptions).Trim (); + } else { + rootNamespaceOfTheProjectGeneratedInto = _targetLanguageService.GetRootNamespace (_generateTypeOptionsResult.Project.CompilationOptions).Trim (); + } + + // TODO : Default namespace support + //var projectManagementService = _document.Project.Solution.Workspace.Services.GetService<IProjectManagementService> (); + var defaultNamespace = "";// projectManagementService.GetDefaultNamespace (targetProject, targetProject.Solution.Workspace); + + // Case 1 : If the type is generated into the same C# project or + // Case 2 : If the type is generated from a C# project to a C# Project + // Case 3 : If the Type is generated from a VB Project to a C# Project + // Using and Namespace will be the DefaultNamespace + Folder Structure + if ((_document.Project == _generateTypeOptionsResult.Project && _document.Project.Language == LanguageNames.CSharp) || + (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange && _generateTypeOptionsResult.Project.Language == LanguageNames.CSharp) || + _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.VisualBasicToCSharp) { + if (!string.IsNullOrWhiteSpace (defaultNamespace)) { + containerList.Add (defaultNamespace); + } + + // Populate the ContainerList + AddFoldersToNamespaceContainers (containerList, folders); + + containers = containerList.ToArray (); + includeUsingsOrImports = string.Join (".", containerList.ToArray ()); + } + + // Case 4 : If the type is generated into the same VB project or + // Case 5 : If Type is generated from a VB Project to VB Project + // Case 6 : If Type is generated from a C# Project to VB Project + // Namespace will be Folder Structure and Import will have the RootNamespace of the project generated into as part of the Imports + if ((_document.Project == _generateTypeOptionsResult.Project && _document.Project.Language == LanguageNames.VisualBasic) || + (_document.Project != _generateTypeOptionsResult.Project && _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange && _generateTypeOptionsResult.Project.Language == LanguageNames.VisualBasic) || + _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.CSharpToVisualBasic) { + // Populate the ContainerList + AddFoldersToNamespaceContainers (containerList, folders); + containers = containerList.ToArray (); + includeUsingsOrImports = string.Join (".", containerList.ToArray ()); + if (!string.IsNullOrWhiteSpace (rootNamespaceOfTheProjectGeneratedInto)) { + includeUsingsOrImports = string.IsNullOrEmpty (includeUsingsOrImports) ? + rootNamespaceOfTheProjectGeneratedInto : + rootNamespaceOfTheProjectGeneratedInto + "." + includeUsingsOrImports; + } + } + } + + return Tuple.Create (containers, includeUsingsOrImports); + } + + private async Task<IEnumerable<CodeActionOperation>> GetGenerateIntoTypeOperationsAsync (INamedTypeSymbol namedType) + { + var codeGenService = GetCodeGenerationService (); + var solution = _document.Project.Solution; + var codeGenResult = await CodeGenerator.AddNamedTypeDeclarationAsync ( + solution, + _state.TypeToGenerateInOpt, + namedType, + new CodeGenerationOptions (contextLocation: _state.SimpleName.GetLocation (), generateDefaultAccessibility: false), + _cancellationToken) + .ConfigureAwait (false); + + return new CodeActionOperation[] { new ApplyChangesOperation (codeGenResult.Project.Solution) }; + } + + private IList<ITypeSymbol> GetArgumentTypes (IList<TArgumentSyntax> argumentList) + { + var types = argumentList.Select (a => _service.DetermineArgumentType (_document.SemanticModel, a, _cancellationToken)); + return types.Select (FixType).ToList (); + } + + private ITypeSymbol FixType ( + ITypeSymbol typeSymbol) + { + var compilation = _document.SemanticModel.Compilation; + return typeSymbol.RemoveUnnamedErrorTypes (compilation); + } + + private CSharpCodeGenerationService GetCodeGenerationService () + { + var language = _state.TypeToGenerateInOpt == null + ? _state.SimpleName.Language + : _state.TypeToGenerateInOpt.Language; + return new CSharpCodeGenerationService(_document.Project.Solution.Workspace, language); + } + + private bool TryFindMatchingField ( + string parameterName, + ITypeSymbol parameterType, + Dictionary<string, ISymbol> parameterToFieldMap, + bool caseSensitive) + { + // If the base types have an accessible field or property with the same name and + // an acceptable type, then we should just defer to that. + if (_state.BaseTypeOrInterfaceOpt != null) { + var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + var query = + _state.BaseTypeOrInterfaceOpt + .GetBaseTypesAndThis () + .SelectMany (t => t.GetMembers ()) + .Where (s => s.Name.Equals (parameterName, comparison)); + var symbol = query.FirstOrDefault (IsSymbolAccessible); + + if (IsViableFieldOrProperty (parameterType, symbol)) { + parameterToFieldMap [parameterName] = symbol; + return true; + } + } + + return false; + } + + private bool IsViableFieldOrProperty ( + ITypeSymbol parameterType, + ISymbol symbol) + { + if (symbol != null && !symbol.IsStatic && parameterType.Language == symbol.Language) { + if (symbol is IFieldSymbol) { + var field = (IFieldSymbol)symbol; + return + !field.IsReadOnly && + _service.IsConversionImplicit (_document.SemanticModel.Compilation, parameterType, field.Type); + } else if (symbol is IPropertySymbol) { + var property = (IPropertySymbol)symbol; + return + property.Parameters.Length == 0 && + property.SetMethod != null && + IsSymbolAccessible (property.SetMethod) && + _service.IsConversionImplicit (_document.SemanticModel.Compilation, parameterType, property.Type); + } + } + + return false; + } + + private bool IsSymbolAccessible (ISymbol symbol) + { + // Public and protected constructors are accessible. Internal constructors are + // accessible if we have friend access. We can't call the normal accessibility + // checkers since they will think that a protected constructor isn't accessible + // (since we don't have the destination type that would have access to them yet). + switch (symbol.DeclaredAccessibility) { + case Accessibility.ProtectedOrInternal: + case Accessibility.Protected: + case Accessibility.Public: + return true; + case Accessibility.ProtectedAndInternal: + case Accessibility.Internal: + // TODO: Code coverage + return _document.SemanticModel.Compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo ( + symbol.ContainingAssembly); + + default: + return false; + } + } + } + + internal abstract IMethodSymbol GetDelegatingConstructor (TObjectCreationExpressionSyntax objectCreation, INamedTypeSymbol namedType, SemanticModel model, ISet<IMethodSymbol> candidates, CancellationToken cancellationToken); + + private partial class Editor + { + private INamedTypeSymbol GenerateNamedType () + { + return CodeGenerationSymbolFactory.CreateNamedTypeSymbol ( + DetermineAttributes (), + DetermineAccessibility (), + DetermineModifiers (), + DetermineTypeKind (), + DetermineName (), + DetermineTypeParameters (), + DetermineBaseType (), + DetermineInterfaces (), + members: DetermineMembers ()); + } + + private INamedTypeSymbol GenerateNamedType (GenerateTypeOptionsResult options) + { + if (options.TypeKind == TypeKind.Delegate) { + return CodeGenerationSymbolFactory.CreateDelegateTypeSymbol ( + DetermineAttributes (), + options.Accessibility, + DetermineModifiers (), + DetermineReturnType (options), + options.TypeName, + DetermineTypeParameters (options), + DetermineParameters (options)); + } + + return CodeGenerationSymbolFactory.CreateNamedTypeSymbol ( + DetermineAttributes (), + options.Accessibility, + DetermineModifiers (), + options.TypeKind, + options.TypeName, + DetermineTypeParameters (), + DetermineBaseType (), + DetermineInterfaces (), + members: DetermineMembers (options)); + } + + private ITypeSymbol DetermineReturnType (GenerateTypeOptionsResult options) + { + if (_state.DelegateMethodSymbol == null || + _state.DelegateMethodSymbol.ReturnType == null || + _state.DelegateMethodSymbol.ReturnType is IErrorTypeSymbol) { + // Since we cannot determine the return type, we are returning void + return _state.Compilation.GetSpecialType (SpecialType.System_Void); + } else { + return _state.DelegateMethodSymbol.ReturnType; + } + } + + private IList<ITypeParameterSymbol> DetermineTypeParameters (GenerateTypeOptionsResult options) + { + if (_state.DelegateMethodSymbol != null) { + return _state.DelegateMethodSymbol.TypeParameters; + } + + // If the delegate symbol cannot be determined then + return DetermineTypeParameters (); + } + + private IList<IParameterSymbol> DetermineParameters (GenerateTypeOptionsResult options) + { + if (_state.DelegateMethodSymbol != null) { + return _state.DelegateMethodSymbol.Parameters; + } + + return null; + } + + private IList<ISymbol> DetermineMembers (GenerateTypeOptionsResult options = null) + { + var members = new List<ISymbol> (); + AddMembers (members, options); + + if (_state.IsException) { + AddExceptionConstructors (members); + } + + return members; + } + + private void AddMembers (IList<ISymbol> members, GenerateTypeOptionsResult options = null) + { + AddProperties (members); + + IList<TArgumentSyntax> argumentList; + if (!_service.TryGetArgumentList (_state.ObjectCreationExpressionOpt, out argumentList)) { + return; + } + + var parameterTypes = GetArgumentTypes (argumentList); + + // Don't generate this constructor if it would conflict with a default exception + // constructor. Default exception constructors will be added automatically by our + // caller. + if (_state.IsException && + _state.BaseTypeOrInterfaceOpt.InstanceConstructors.Any ( + c => c.Parameters.Select (p => p.Type).SequenceEqual (parameterTypes))) { + return; + } + + // If there's an accessible base constructor that would accept these types, then + // just call into that instead of generating fields. + if (_state.BaseTypeOrInterfaceOpt != null) { + if (_state.BaseTypeOrInterfaceOpt.TypeKind == TypeKind.Interface && argumentList.Count == 0) { + // No need to add the default constructor if our base type is going to be + // 'object'. We get that constructor for free. + return; + } + + var accessibleInstanceConstructors = _state.BaseTypeOrInterfaceOpt.InstanceConstructors.Where ( + IsSymbolAccessible).ToSet (); + + if (accessibleInstanceConstructors.Any ()) { + var delegatedConstructor = _service.GetDelegatingConstructor (_state.ObjectCreationExpressionOpt, _state.BaseTypeOrInterfaceOpt, _document.SemanticModel, accessibleInstanceConstructors, _cancellationToken); + if (delegatedConstructor != null) { + // There was a best match. Call it directly. + AddBaseDelegatingConstructor (delegatedConstructor, members); + return; + } + } + } + + // Otherwise, just generate a normal constructor that assigns any provided + // parameters into fields. + AddFieldDelegatingConstructor (argumentList, members, options); + } + + private void AddProperties (IList<ISymbol> members) + { + foreach (var property in _state.PropertiesToGenerate) { + IPropertySymbol generatedProperty; + if (_service.TryGenerateProperty (property, _document.SemanticModel, _cancellationToken, out generatedProperty)) { + members.Add (generatedProperty); + } + } + } + + private void AddBaseDelegatingConstructor ( + IMethodSymbol methodSymbol, + IList<ISymbol> members) + { + // If we're generating a constructor to delegate into the no-param base constructor + // then we can just elide the constructor entirely. + if (methodSymbol.Parameters.Length == 0) { + return; + } + + var factory = _document.Project.LanguageServices.GetService<SyntaxGenerator> (); + members.Add (factory.CreateBaseDelegatingConstructor ( + methodSymbol, DetermineName ())); + } + + private void AddFieldDelegatingConstructor ( + IList<TArgumentSyntax> argumentList, IList<ISymbol> members, GenerateTypeOptionsResult options = null) + { + var factory = _document.Project.LanguageServices.GetService<SyntaxGenerator> (); + + var availableTypeParameters = _service.GetAvailableTypeParameters (_state, _document.SemanticModel, _intoNamespace, _cancellationToken); + var parameterTypes = GetArgumentTypes (argumentList); + var parameterNames = _service.GenerateParameterNames (_document.SemanticModel, argumentList); + var parameters = new List<IParameterSymbol> (); + + var parameterToExistingFieldMap = new Dictionary<string, ISymbol> (); + var parameterToNewFieldMap = new Dictionary<string, string> (); + + for (var i = 0; i < parameterNames.Count; i++) { + var refKind = argumentList [i].GetRefKindOfArgument (); + + var parameterName = parameterNames [i]; + var parameterType = (ITypeSymbol)parameterTypes [i]; + parameterType = parameterType.RemoveUnavailableTypeParameters ( + _document.SemanticModel.Compilation, availableTypeParameters); + + if (!TryFindMatchingField (parameterName, parameterType, parameterToExistingFieldMap, caseSensitive: true)) { + if (!TryFindMatchingField (parameterName, parameterType, parameterToExistingFieldMap, caseSensitive: false)) { + parameterToNewFieldMap [parameterName] = parameterName; + } + } + + parameters.Add (CodeGenerationSymbolFactory.CreateParameterSymbol ( + attributes: null, + refKind: refKind, + isParams: false, + type: parameterType, + name: parameterName)); + } + + // Empty Constructor for Struct is not allowed + if (!(parameters.Count == 0 && options != null && (options.TypeKind == TypeKind.Struct || options.TypeKind == TypeKind.Structure))) { + var symbols = factory.CreateFieldDelegatingConstructor (DetermineName (), null, parameters, parameterToExistingFieldMap, parameterToNewFieldMap, _cancellationToken); + foreach (var c in symbols) + members.Add (c); + } + } + + private void AddExceptionConstructors (IList<ISymbol> members) + { + var factory = _document.Project.LanguageServices.GetService<SyntaxGenerator> (); + var exceptionType = _document.SemanticModel.Compilation.ExceptionType (); + var constructors = + exceptionType.InstanceConstructors + .Where (c => c.DeclaredAccessibility == Accessibility.Public || c.DeclaredAccessibility == Accessibility.Protected) + .Select (c => CodeGenerationSymbolFactory.CreateConstructorSymbol ( + attributes: null, + accessibility: c.DeclaredAccessibility, + modifiers: default(DeclarationModifiers), + typeName: DetermineName (), + parameters: c.Parameters, + statements: null, + baseConstructorArguments: c.Parameters.Length == 0 ? null : factory.CreateArguments (c.Parameters))); + foreach (var c in constructors) + members.Add (c); + } + + private IList<AttributeData> DetermineAttributes () + { + if (_state.IsException) { + var serializableType = _document.SemanticModel.Compilation.SerializableAttributeType (); + if (serializableType != null) { + var attribute = CodeGenerationSymbolFactory.CreateAttributeData (serializableType); + return new[] { attribute }; + } + } + + return null; + } + + private Accessibility DetermineAccessibility () + { + return _service.GetAccessibility (_state, _document.SemanticModel, _intoNamespace, _cancellationToken); + } + + private DeclarationModifiers DetermineModifiers () + { + return default(DeclarationModifiers); + } + + private INamedTypeSymbol DetermineBaseType () + { + if (_state.BaseTypeOrInterfaceOpt == null || _state.BaseTypeOrInterfaceOpt.TypeKind == TypeKind.Interface) { + return null; + } + + return RemoveUnavailableTypeParameters (_state.BaseTypeOrInterfaceOpt); + } + + private IList<INamedTypeSymbol> DetermineInterfaces () + { + if (_state.BaseTypeOrInterfaceOpt != null && _state.BaseTypeOrInterfaceOpt.TypeKind == TypeKind.Interface) { + var type = RemoveUnavailableTypeParameters (_state.BaseTypeOrInterfaceOpt); + if (type != null) { + return new[] { type }; + } + } + + return SpecializedCollections.EmptyList<INamedTypeSymbol> (); + } + + private INamedTypeSymbol RemoveUnavailableTypeParameters (INamedTypeSymbol type) + { + return type.RemoveUnavailableTypeParameters ( + _document.SemanticModel.Compilation, GetAvailableTypeParameters ()) as INamedTypeSymbol; + } + + private string DetermineName () + { + return GetTypeName (_state); + } + + private IList<ITypeParameterSymbol> DetermineTypeParameters () + { + return _service.GetTypeParameters (_state, _document.SemanticModel, _cancellationToken); + } + + private TypeKind DetermineTypeKind () + { + return _state.IsStruct + ? TypeKind.Struct + : _state.IsInterface + ? TypeKind.Interface + : TypeKind.Class; + } + + protected IList<ITypeParameterSymbol> GetAvailableTypeParameters () + { + var availableInnerTypeParameters = _service.GetTypeParameters (_state, _document.SemanticModel, _cancellationToken); + var availableOuterTypeParameters = !_intoNamespace && _state.TypeToGenerateInOpt != null + ? _state.TypeToGenerateInOpt.GetAllTypeParameters () + : SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol> (); + + return availableOuterTypeParameters.Concat (availableInnerTypeParameters).ToList (); + } + } + + internal abstract bool TryGenerateProperty (TSimpleNameSyntax propertyName, SemanticModel semanticModel, CancellationToken cancellationToken, out IPropertySymbol property); + + protected class State + { + public string Name { get; private set; } + + public bool NameIsVerbatim { get; private set; } + + // The name node that we're on. Will be used to the name the type if it's + // generated. + public TSimpleNameSyntax SimpleName { get; private set; } + + // The entire expression containing the name, not including the creation. i.e. "X.Foo" + // in "new X.Foo()". + public TExpressionSyntax NameOrMemberAccessExpression { get; private set; } + + // The object creation node if we have one. i.e. if we're on the 'Foo' in "new X.Foo()". + public TObjectCreationExpressionSyntax ObjectCreationExpressionOpt { get; private set; } + + // One of these will be non null. It's also possible for both to be non null. For + // example, if you have "class C { Foo f; }", then "Foo" can be generated inside C or + // inside the global namespace. The namespace can be null or the type can be null if the + // user has something like "ExistingType.NewType" or "ExistingNamespace.NewType". In + // that case they're being explicit about what they want to generate into. + public INamedTypeSymbol TypeToGenerateInOpt { get; private set; } + + public string NamespaceToGenerateInOpt { get; private set; } + + // If we can infer a base type or interface for this type. + // + // i.e.: "IList<int> foo = new MyList();" + public INamedTypeSymbol BaseTypeOrInterfaceOpt { get; private set; } + + public bool IsInterface { get; private set; } + + public bool IsStruct { get; private set; } + + public bool IsAttribute { get; private set; } + + public bool IsException { get; private set; } + + public bool IsMembersWithModule { get; private set; } + + public bool IsTypeGeneratedIntoNamespaceFromMemberAccess { get; private set; } + + public bool IsSimpleNameGeneric { get; private set; } + + public bool IsPublicAccessibilityForTypeGeneration { get; private set; } + + public bool IsInterfaceOrEnumNotAllowedInTypeContext { get; private set; } + + public IMethodSymbol DelegateMethodSymbol { get; private set; } + + public bool IsDelegateAllowed { get; private set; } + + public bool IsEnumNotAllowed { get; private set; } + + public Compilation Compilation { get; } + + public bool IsDelegateOnly { get; private set; } + + public bool IsClassInterfaceTypes { get; private set; } + + public List<TSimpleNameSyntax> PropertiesToGenerate { get; private set; } + + private State (Compilation compilation) + { + this.Compilation = compilation; + } + + public static State Generate ( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var state = new State (document.SemanticModel.Compilation); + if (!state.TryInitialize (service, document, node, cancellationToken)) { + return null; + } + + return state; + } + + private bool TryInitialize ( + TService service, + SemanticDocument document, + SyntaxNode node, + CancellationToken cancellationToken) + { + if (!(node is TSimpleNameSyntax)) { + return false; + } + + this.SimpleName = (TSimpleNameSyntax)node; + string name; + int arity; + this.SimpleName.GetNameAndArityOfSimpleName (out name, out arity); + + this.Name = name; + this.NameIsVerbatim = this.SimpleName.GetFirstToken ().IsVerbatimIdentifier (); + if (string.IsNullOrWhiteSpace (this.Name)) { + return false; + } + + // We only support simple names or dotted names. i.e. "(some + expr).Foo" is not a + // valid place to generate a type for Foo. + GenerateTypeServiceStateOptions generateTypeServiceStateOptions; + if (!service.TryInitializeState (document, this.SimpleName, cancellationToken, out generateTypeServiceStateOptions)) { + return false; + } + + this.NameOrMemberAccessExpression = generateTypeServiceStateOptions.NameOrMemberAccessExpression; + this.ObjectCreationExpressionOpt = generateTypeServiceStateOptions.ObjectCreationExpressionOpt; + + var semanticModel = document.SemanticModel; + var info = semanticModel.GetSymbolInfo (this.SimpleName, cancellationToken); + if (info.Symbol != null) { + // This bound, so no need to generate anything. + return false; + } + + if (!semanticModel.IsTypeContext (this.NameOrMemberAccessExpression.SpanStart, cancellationToken) && + !semanticModel.IsExpressionContext (this.NameOrMemberAccessExpression.SpanStart, cancellationToken) && + !semanticModel.IsStatementContext (this.NameOrMemberAccessExpression.SpanStart, cancellationToken) && + !semanticModel.IsNameOfContext (this.NameOrMemberAccessExpression.SpanStart, cancellationToken) && + !semanticModel.IsNamespaceContext (this.NameOrMemberAccessExpression.SpanStart, cancellationToken)) { + return false; + } + + // If this isn't something that can be created, then don't bother offering to create + // it. + if (info.CandidateReason == CandidateReason.NotCreatable) { + return false; + } + + if (info.CandidateReason == CandidateReason.Inaccessible || + info.CandidateReason == CandidateReason.NotReferencable || + info.CandidateReason == CandidateReason.OverloadResolutionFailure) { + // We bound to something inaccessible, or overload resolution on a + // constructor call failed. Don't want to offer GenerateType here. + return false; + } + + if (this.ObjectCreationExpressionOpt != null) { + // If we're new'ing up something illegal, then don't offer generate type. + var typeInfo = semanticModel.GetTypeInfo (this.ObjectCreationExpressionOpt, cancellationToken); + if (typeInfo.Type.IsModuleType ()) { + return false; + } + } + + DetermineNamespaceOrTypeToGenerateIn (service, document, cancellationToken); + + // Now, try to infer a possible base type for this new class/interface. + this.InferBaseType (service, document, cancellationToken); + this.IsInterface = GenerateInterface (service, cancellationToken); + this.IsStruct = GenerateStruct (service, semanticModel, cancellationToken); + this.IsAttribute = this.BaseTypeOrInterfaceOpt != null && this.BaseTypeOrInterfaceOpt.Equals (semanticModel.Compilation.AttributeType ()); + this.IsException = this.BaseTypeOrInterfaceOpt != null && this.BaseTypeOrInterfaceOpt.Equals (semanticModel.Compilation.ExceptionType ()); + this.IsMembersWithModule = generateTypeServiceStateOptions.IsMembersWithModule; + this.IsTypeGeneratedIntoNamespaceFromMemberAccess = generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess; + this.IsInterfaceOrEnumNotAllowedInTypeContext = generateTypeServiceStateOptions.IsInterfaceOrEnumNotAllowedInTypeContext; + this.IsDelegateAllowed = generateTypeServiceStateOptions.IsDelegateAllowed; + this.IsDelegateOnly = generateTypeServiceStateOptions.IsDelegateOnly; + this.IsEnumNotAllowed = generateTypeServiceStateOptions.IsEnumNotAllowed; + this.DelegateMethodSymbol = generateTypeServiceStateOptions.DelegateCreationMethodSymbol; + this.IsClassInterfaceTypes = generateTypeServiceStateOptions.IsClassInterfaceTypes; + this.IsSimpleNameGeneric = service.IsGenericName (this.SimpleName); + this.PropertiesToGenerate = generateTypeServiceStateOptions.PropertiesToGenerate; + + if (this.IsAttribute && this.TypeToGenerateInOpt.GetAllTypeParameters ().Any ()) { + this.TypeToGenerateInOpt = null; + } + + return this.TypeToGenerateInOpt != null || this.NamespaceToGenerateInOpt != null; + } + + private void InferBaseType ( + TService service, + SemanticDocument document, + CancellationToken cancellationToken) + { + // See if we can find a possible base type for the type being generated. + // NOTE(cyrusn): I currently limit this to when we have an object creation node. + // That's because that's when we would have an expression that could be conerted to + // somethign else. i.e. if the user writes "IList<int> list = new Foo()" then we can + // infer a base interface for 'Foo'. However, if they write "IList<int> list = Foo" + // then we don't really want to infer a base type for 'Foo'. + + // However, there are a few other cases were we can infer a base type. + if (service.IsInCatchDeclaration (this.NameOrMemberAccessExpression)) { + this.BaseTypeOrInterfaceOpt = document.SemanticModel.Compilation.ExceptionType (); + } else if (NameOrMemberAccessExpression.IsAttributeName ()) { + this.BaseTypeOrInterfaceOpt = document.SemanticModel.Compilation.AttributeType (); + } else if ( + service.IsArrayElementType (this.NameOrMemberAccessExpression) || + service.IsInVariableTypeContext (this.NameOrMemberAccessExpression) || + this.ObjectCreationExpressionOpt != null) { + var expr = this.ObjectCreationExpressionOpt ?? this.NameOrMemberAccessExpression; + var baseType = TypeGuessing.typeInferenceService.InferType (document.SemanticModel, expr, objectAsDefault: true, cancellationToken: cancellationToken) as INamedTypeSymbol; + SetBaseType (baseType); + } + } + + private void SetBaseType (INamedTypeSymbol baseType) + { + if (baseType == null) { + return; + } + + // A base type need to be non class or interface type. Also, being 'object' is + // redundant as the base type. + if (baseType.IsSealed || baseType.IsStatic || baseType.SpecialType == SpecialType.System_Object) { + return; + } + + if (baseType.TypeKind != TypeKind.Class && baseType.TypeKind != TypeKind.Interface) { + return; + } + + this.BaseTypeOrInterfaceOpt = baseType; + } + + private bool GenerateStruct (TService service, SemanticModel semanticModel, CancellationToken cancellationToken) + { + return service.IsInValueTypeConstraintContext (semanticModel, this.NameOrMemberAccessExpression, cancellationToken); + } + + private bool GenerateInterface ( + TService service, + CancellationToken cancellationToken) + { + if (!this.IsAttribute && + !this.IsException && + this.Name.LooksLikeInterfaceName () && + this.ObjectCreationExpressionOpt == null && + (this.BaseTypeOrInterfaceOpt == null || this.BaseTypeOrInterfaceOpt.TypeKind == TypeKind.Interface)) { + return true; + } + + return service.IsInInterfaceList (this.NameOrMemberAccessExpression); + } + + private void DetermineNamespaceOrTypeToGenerateIn ( + TService service, + SemanticDocument document, + CancellationToken cancellationToken) + { + DetermineNamespaceOrTypeToGenerateInWorker (service, document.SemanticModel, cancellationToken); + + // Can only generate into a type if it's a class and it's from source. + if (this.TypeToGenerateInOpt != null) { + if (this.TypeToGenerateInOpt.TypeKind != TypeKind.Class && + this.TypeToGenerateInOpt.TypeKind != TypeKind.Module) { + this.TypeToGenerateInOpt = null; + } else { + var symbol = SymbolFinder.FindSourceDefinitionAsync (this.TypeToGenerateInOpt, document.Project.Solution, cancellationToken).WaitAndGetResult (cancellationToken); + if (symbol == null || + !symbol.IsKind (SymbolKind.NamedType) || + !symbol.Locations.Any (loc => loc.IsInSource)) { + this.TypeToGenerateInOpt = null; + return; + } + + var sourceTreeToBeGeneratedIn = symbol.Locations.First (loc => loc.IsInSource).SourceTree; + var documentToBeGeneratedIn = document.Project.Solution.GetDocument (sourceTreeToBeGeneratedIn); + + if (documentToBeGeneratedIn == null) { + this.TypeToGenerateInOpt = null; + return; + } + + // If the 2 documents are in different project then we must have Public Accessibility. + // If we are generating in a website project, we also want to type to be public so the + // designer files can access the type. + if (documentToBeGeneratedIn.Project != document.Project || + service.GeneratedTypesMustBePublic (documentToBeGeneratedIn.Project)) { + this.IsPublicAccessibilityForTypeGeneration = true; + } + + this.TypeToGenerateInOpt = (INamedTypeSymbol)symbol; + } + } + + if (this.TypeToGenerateInOpt != null) { + if (!CodeGenerator.CanAdd (document.Project.Solution, this.TypeToGenerateInOpt, cancellationToken)) { + this.TypeToGenerateInOpt = null; + } + } + } + + private bool DetermineNamespaceOrTypeToGenerateInWorker ( + TService service, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + // If we're on the right of a dot, see if we can figure out what's on the left. If + // it doesn't bind to a type or a namespace, then we can't proceed. + if (this.SimpleName != this.NameOrMemberAccessExpression) { + return DetermineNamespaceOrTypeToGenerateIn ( + service, semanticModel, + service.GetLeftSideOfDot (this.SimpleName), cancellationToken); + } else { + // The name is standing alone. We can either generate the type into our + // containing type, or into our containing namespace. + // + // TODO(cyrusn): We need to make this logic work if the type is in the + // base/interface list of a type. + var format = SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle (SymbolDisplayGlobalNamespaceStyle.Omitted); + this.TypeToGenerateInOpt = service.DetermineTypeToGenerateIn (semanticModel, this.SimpleName, cancellationToken); + if (this.TypeToGenerateInOpt != null) { + this.NamespaceToGenerateInOpt = this.TypeToGenerateInOpt.ContainingNamespace.ToDisplayString (format); + } else { + var namespaceSymbol = semanticModel.GetEnclosingNamespace (this.SimpleName.SpanStart, cancellationToken); + if (namespaceSymbol != null) { + this.NamespaceToGenerateInOpt = namespaceSymbol.ToDisplayString (format); + } + } + } + + return true; + } + + private bool DetermineNamespaceOrTypeToGenerateIn ( + TService service, + SemanticModel semanticModel, + TExpressionSyntax leftSide, + CancellationToken cancellationToken) + { + var leftSideInfo = semanticModel.GetSymbolInfo (leftSide, cancellationToken); + + if (leftSideInfo.Symbol != null) { + var symbol = leftSideInfo.Symbol; + + if (symbol is INamespaceSymbol) { + this.NamespaceToGenerateInOpt = symbol.ToDisplayString (ICSharpCode.NRefactory6.CSharp.Completion.DelegateCreationContextHandler.NameFormat); + return true; + } else if (symbol is INamedTypeSymbol) { + // TODO: Code coverage + this.TypeToGenerateInOpt = (INamedTypeSymbol)symbol.OriginalDefinition; + return true; + } + + // We bound to something other than a namespace or named type. Can't generate a + // type inside this. + return false; + } else { + // If it's a dotted name, then perhaps it's a namespace. i.e. the user wrote + // "new Foo.Bar.Baz()". In this case we want to generate a namespace for + // "Foo.Bar". + IList<string> nameParts; + if (service.TryGetNameParts (leftSide, out nameParts)) { + this.NamespaceToGenerateInOpt = string.Join (".", nameParts); + return true; + } + } + + return false; + } + } + + protected class GenerateTypeServiceStateOptions + { + public TExpressionSyntax NameOrMemberAccessExpression { get; set; } + + public TObjectCreationExpressionSyntax ObjectCreationExpressionOpt { get; set; } + + public IMethodSymbol DelegateCreationMethodSymbol { get; set; } + + public List<TSimpleNameSyntax> PropertiesToGenerate { get; } + + public bool IsMembersWithModule { get; set; } + + public bool IsTypeGeneratedIntoNamespaceFromMemberAccess { get; set; } + + public bool IsInterfaceOrEnumNotAllowedInTypeContext { get; set; } + + public bool IsDelegateAllowed { get; set; } + + public bool IsEnumNotAllowed { get; set; } + + public bool IsDelegateOnly { get; internal set; } + + public bool IsClassInterfaceTypes { get; internal set; } + + public GenerateTypeServiceStateOptions () + { + NameOrMemberAccessExpression = null; + ObjectCreationExpressionOpt = null; + DelegateCreationMethodSymbol = null; + IsMembersWithModule = false; + PropertiesToGenerate = new List<TSimpleNameSyntax> (); + IsTypeGeneratedIntoNamespaceFromMemberAccess = false; + IsInterfaceOrEnumNotAllowedInTypeContext = false; + IsDelegateAllowed = true; + IsEnumNotAllowed = false; + IsDelegateOnly = false; + } + } + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/CSharpGenerateTypeService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/CSharpGenerateTypeService.cs new file mode 100644 index 0000000000..72abb6eb2a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/CSharpGenerateTypeService.cs @@ -0,0 +1,940 @@ +// 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.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateConstructor; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateType +{ + public class CSharpGenerateTypeService : + AbstractGenerateTypeService<CSharpGenerateTypeService, SimpleNameSyntax, ObjectCreationExpressionSyntax, ExpressionSyntax, TypeDeclarationSyntax, ArgumentSyntax> + { + private static readonly SyntaxAnnotation s_annotation = new SyntaxAnnotation(); + + protected override string DefaultFileExtension + { + get + { + return ".cs"; + } + } + + protected override ExpressionSyntax GetLeftSideOfDot(SimpleNameSyntax simpleName) + { + return simpleName.GetLeftSideOfDot(); + } + + protected override bool IsInCatchDeclaration(ExpressionSyntax expression) + { + return expression.IsParentKind(SyntaxKind.CatchDeclaration); + } + + protected override bool IsArrayElementType(ExpressionSyntax expression) + { + return expression.IsParentKind(SyntaxKind.ArrayType) && + expression.Parent.IsParentKind(SyntaxKind.ArrayCreationExpression); + } + + protected override bool IsInValueTypeConstraintContext( + SemanticModel semanticModel, + ExpressionSyntax expression, + CancellationToken cancellationToken) + { + if (expression is TypeSyntax && expression.IsParentKind(SyntaxKind.TypeArgumentList)) + { + var typeArgumentList = (TypeArgumentListSyntax)expression.Parent; + var symbolInfo = semanticModel.GetSymbolInfo(typeArgumentList.Parent, cancellationToken); + var symbol = symbolInfo.GetAnySymbol(); + if (symbol.IsConstructor()) + { + symbol = symbol.ContainingType; + } + + var parameterIndex = typeArgumentList.Arguments.IndexOf((TypeSyntax)expression); + var type = symbol as INamedTypeSymbol; + if (type != null) + { + type = type.OriginalDefinition; + var typeParameter = parameterIndex < type.TypeParameters.Length ? type.TypeParameters[parameterIndex] : null; + return typeParameter != null && typeParameter.HasValueTypeConstraint; + } + + var method = symbol as IMethodSymbol; + if (method != null) + { + method = method.OriginalDefinition; + var typeParameter = parameterIndex < method.TypeParameters.Length ? method.TypeParameters[parameterIndex] : null; + return typeParameter != null && typeParameter.HasValueTypeConstraint; + } + } + + return false; + } + + protected override bool IsInInterfaceList(ExpressionSyntax expression) + { + if (expression is TypeSyntax && + expression.Parent is BaseTypeSyntax && + expression.Parent.IsParentKind(SyntaxKind.BaseList) && + ((BaseTypeSyntax)expression.Parent).Type == expression) + { + var baseList = (BaseListSyntax)expression.Parent.Parent; + + // If it's after the first item, then it's definitely an interface. + if (baseList.Types[0] != expression.Parent) + { + return true; + } + + // If it's in the base list of an interface or struct, then it's definitely an + // interface. + return + baseList.IsParentKind(SyntaxKind.InterfaceDeclaration) || + baseList.IsParentKind(SyntaxKind.StructDeclaration); + } + + if (expression is TypeSyntax && + expression.IsParentKind(SyntaxKind.TypeConstraint) && + expression.Parent.IsParentKind(SyntaxKind.TypeParameterConstraintClause)) + { + var typeConstraint = (TypeConstraintSyntax)expression.Parent; + var constraintClause = (TypeParameterConstraintClauseSyntax)typeConstraint.Parent; + var index = constraintClause.Constraints.IndexOf(typeConstraint); + + // If it's after the first item, then it's definitely an interface. + return index > 0; + } + + return false; + } + + protected override bool TryGetNameParts(ExpressionSyntax expression, out IList<string> nameParts) + { + nameParts = new List<string>(); + return expression.TryGetNameParts(out nameParts); + } + + protected override bool TryInitializeState( + SemanticDocument document, + SimpleNameSyntax simpleName, + CancellationToken cancellationToken, + out GenerateTypeServiceStateOptions generateTypeServiceStateOptions) + { + generateTypeServiceStateOptions = new GenerateTypeServiceStateOptions(); + + if (simpleName.IsVar) + { + return false; + } + + if (SyntaxFacts.IsAliasQualifier(simpleName)) + { + return false; + } + + // Never offer if we're in a using directive, unless its a static using. The feeling here is that it's highly + // unlikely that this would be a location where a user would be wanting to generate + // something. They're really just trying to reference something that exists but + // isn't available for some reason (i.e. a missing reference). + var usingDirectiveSyntax = simpleName.GetAncestorOrThis<UsingDirectiveSyntax>(); + if (usingDirectiveSyntax != null && usingDirectiveSyntax.StaticKeyword.Kind() != SyntaxKind.StaticKeyword) + { + return false; + } + + ExpressionSyntax nameOrMemberAccessExpression = null; + if (simpleName.IsRightSideOfDot()) + { + // This simplename comes from the cref + if (simpleName.IsParentKind(SyntaxKind.NameMemberCref)) + { + return false; + } + + nameOrMemberAccessExpression = generateTypeServiceStateOptions.NameOrMemberAccessExpression = (ExpressionSyntax)simpleName.Parent; + + // If we're on the right side of a dot, then the left side better be a name (and + // not an arbitrary expression). + var leftSideExpression = simpleName.GetLeftSideOfDot(); + if (!leftSideExpression.IsKind( + SyntaxKind.QualifiedName, + SyntaxKind.IdentifierName, + SyntaxKind.AliasQualifiedName, + SyntaxKind.GenericName, + SyntaxKind.SimpleMemberAccessExpression)) + { + return false; + } + } + else + { + nameOrMemberAccessExpression = generateTypeServiceStateOptions.NameOrMemberAccessExpression = simpleName; + } + + // BUG(5712): Don't offer generate type in an enum's base list. + if (nameOrMemberAccessExpression.Parent is BaseTypeSyntax && + nameOrMemberAccessExpression.Parent.IsParentKind(SyntaxKind.BaseList) && + ((BaseTypeSyntax)nameOrMemberAccessExpression.Parent).Type == nameOrMemberAccessExpression && + nameOrMemberAccessExpression.Parent.Parent.IsParentKind(SyntaxKind.EnumDeclaration)) + { + return false; + } + + // If we can guarantee it's a type only context, great. Otherwise, we may not want to + // provide this here. + var semanticModel = document.SemanticModel; + if (!SyntaxFacts.IsInNamespaceOrTypeContext(nameOrMemberAccessExpression)) + { + // Don't offer Generate Type in an expression context *unless* we're on the left + // side of a dot. In that case the user might be making a type that they're + // accessing a static off of. + var syntaxTree = semanticModel.SyntaxTree; + var start = nameOrMemberAccessExpression.SpanStart; + var tokenOnLeftOfStart = syntaxTree.FindTokenOnLeftOfPosition(start, cancellationToken); + var isExpressionContext = syntaxTree.IsExpressionContext(start, tokenOnLeftOfStart, attributes: true, cancellationToken: cancellationToken, semanticModelOpt: semanticModel); + var isStatementContext = syntaxTree.IsStatementContext(start, tokenOnLeftOfStart, cancellationToken); + var isExpressionOrStatementContext = isExpressionContext || isStatementContext; + + // Delegate Type Creation is not allowed in Non Type Namespace Context + generateTypeServiceStateOptions.IsDelegateAllowed = false; + + if (!isExpressionOrStatementContext) + { + return false; + } + + if (!simpleName.IsLeftSideOfDot() && !simpleName.IsInsideNameOf()) + { + if (nameOrMemberAccessExpression == null || !nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression) || !simpleName.IsRightSideOfDot()) + { + return false; + } + + var leftSymbol = semanticModel.GetSymbolInfo(((MemberAccessExpressionSyntax)nameOrMemberAccessExpression).Expression, cancellationToken).Symbol; + var token = simpleName.GetLastToken().GetNextToken(); + + // We let only the Namespace to be left of the Dot + if (leftSymbol == null || + !leftSymbol.IsKind(SymbolKind.Namespace) || + !token.IsKind(SyntaxKind.DotToken)) + { + return false; + } + else + { + generateTypeServiceStateOptions.IsMembersWithModule = true; + generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess = true; + } + } + + // Global Namespace + if (!generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess && + !SyntaxFacts.IsInNamespaceOrTypeContext(simpleName)) + { + var token = simpleName.GetLastToken().GetNextToken(); + if (token.IsKind(SyntaxKind.DotToken) && + simpleName.Parent == token.Parent) + { + generateTypeServiceStateOptions.IsMembersWithModule = true; + generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess = true; + } + } + } + + var fieldDeclaration = simpleName.GetAncestor<FieldDeclarationSyntax>(); + if (fieldDeclaration != null && + fieldDeclaration.Parent is CompilationUnitSyntax && + document.Document.SourceCodeKind == SourceCodeKind.Regular) + { + return false; + } + + // Check to see if Module could be an option in the Type Generation in Cross Language Generation + var nextToken = simpleName.GetLastToken().GetNextToken(); + if (simpleName.IsLeftSideOfDot() || + nextToken.IsKind(SyntaxKind.DotToken)) + { + if (simpleName.IsRightSideOfDot()) + { + var parent = simpleName.Parent as QualifiedNameSyntax; + if (parent != null) + { + var leftSymbol = semanticModel.GetSymbolInfo(parent.Left, cancellationToken).Symbol; + + if (leftSymbol != null && leftSymbol.IsKind(SymbolKind.Namespace)) + { + generateTypeServiceStateOptions.IsMembersWithModule = true; + } + } + } + } + + if (SyntaxFacts.IsInNamespaceOrTypeContext(nameOrMemberAccessExpression)) + { + if (nextToken.IsKind(SyntaxKind.DotToken)) + { + // In Namespace or Type Context we cannot have Interface, Enum, Delegate as part of the Left Expression of a QualifiedName + generateTypeServiceStateOptions.IsDelegateAllowed = false; + generateTypeServiceStateOptions.IsInterfaceOrEnumNotAllowedInTypeContext = true; + generateTypeServiceStateOptions.IsMembersWithModule = true; + } + + // case: class Foo<T> where T: MyType + if (nameOrMemberAccessExpression.GetAncestors<TypeConstraintSyntax>().Any()) + { + generateTypeServiceStateOptions.IsClassInterfaceTypes = true; + return true; + } + + // Events + if (nameOrMemberAccessExpression.GetAncestors<EventFieldDeclarationSyntax>().Any() || + nameOrMemberAccessExpression.GetAncestors<EventDeclarationSyntax>().Any()) + { + // Case : event foo name11 + // Only Delegate + if (simpleName.Parent != null && !(simpleName.Parent is QualifiedNameSyntax)) + { + generateTypeServiceStateOptions.IsDelegateOnly = true; + return true; + } + + // Case : event SomeSymbol.foo name11 + if (nameOrMemberAccessExpression is QualifiedNameSyntax) + { + // Only Namespace, Class, Struct and Module are allowed to contain Delegate + // Case : event Something.Mytype.<Delegate> Identifier + if (nextToken.IsKind(SyntaxKind.DotToken)) + { + if (nameOrMemberAccessExpression.Parent != null && nameOrMemberAccessExpression.Parent is QualifiedNameSyntax) + { + return true; + } + else + { + //Contract.Fail("Cannot reach this point"); + } + } + else + { + // Case : event Something.<Delegate> Identifier + generateTypeServiceStateOptions.IsDelegateOnly = true; + return true; + } + } + } + } + else + { + // MemberAccessExpression + if ((nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression) || (nameOrMemberAccessExpression.Parent != null && nameOrMemberAccessExpression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression))) + && nameOrMemberAccessExpression.IsLeftSideOfDot()) + { + // Check to see if the expression is part of Invocation Expression + ExpressionSyntax outerMostMemberAccessExpression = null; + if (nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + outerMostMemberAccessExpression = nameOrMemberAccessExpression; + } + else + { + Debug.Assert(nameOrMemberAccessExpression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression)); + outerMostMemberAccessExpression = (ExpressionSyntax)nameOrMemberAccessExpression.Parent; + } + + outerMostMemberAccessExpression = outerMostMemberAccessExpression.GetAncestorsOrThis<ExpressionSyntax>().SkipWhile((n) => n != null && n.IsKind(SyntaxKind.SimpleMemberAccessExpression)).FirstOrDefault(); + if (outerMostMemberAccessExpression != null && outerMostMemberAccessExpression is InvocationExpressionSyntax) + { + generateTypeServiceStateOptions.IsEnumNotAllowed = true; + } + } + } + + // Cases: + // // 1 - Function Address + // var s2 = new MyD2(foo); + + // // 2 - Delegate + // MyD1 d = null; + // var s1 = new MyD2(d); + + // // 3 - Action + // Action action1 = null; + // var s3 = new MyD2(action1); + + // // 4 - Func + // Func<int> lambda = () => { return 0; }; + // var s4 = new MyD3(lambda); + + if (nameOrMemberAccessExpression.Parent is ObjectCreationExpressionSyntax) + { + var objectCreationExpressionOpt = generateTypeServiceStateOptions.ObjectCreationExpressionOpt = (ObjectCreationExpressionSyntax)nameOrMemberAccessExpression.Parent; + + // Enum and Interface not Allowed in Object Creation Expression + generateTypeServiceStateOptions.IsInterfaceOrEnumNotAllowedInTypeContext = true; + + if (objectCreationExpressionOpt.ArgumentList != null) + { + if (objectCreationExpressionOpt.ArgumentList.CloseParenToken.IsMissing) + { + return false; + } + + // Get the Method symbol for the Delegate to be created + if (generateTypeServiceStateOptions.IsDelegateAllowed && + objectCreationExpressionOpt.ArgumentList.Arguments.Count == 1) + { + generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, objectCreationExpressionOpt.ArgumentList.Arguments[0].Expression, cancellationToken); + } + else + { + generateTypeServiceStateOptions.IsDelegateAllowed = false; + } + } + + if (objectCreationExpressionOpt.Initializer != null) + { + foreach (var expression in objectCreationExpressionOpt.Initializer.Expressions) + { + var simpleAssignmentExpression = expression as AssignmentExpressionSyntax; + if (simpleAssignmentExpression == null) + { + continue; + } + + var name = simpleAssignmentExpression.Left as SimpleNameSyntax; + if (name == null) + { + continue; + } + + generateTypeServiceStateOptions.PropertiesToGenerate.Add(name); + } + } + } + + if (generateTypeServiceStateOptions.IsDelegateAllowed) + { + // MyD1 z1 = foo; + if (nameOrMemberAccessExpression.Parent.IsKind(SyntaxKind.VariableDeclaration)) + { + var variableDeclaration = (VariableDeclarationSyntax)nameOrMemberAccessExpression.Parent; + if (variableDeclaration.Variables.Count != 0) + { + var firstVarDeclWithInitializer = variableDeclaration.Variables.FirstOrDefault(var => var.Initializer != null && var.Initializer.Value != null); + if (firstVarDeclWithInitializer != null && firstVarDeclWithInitializer.Initializer != null && firstVarDeclWithInitializer.Initializer.Value != null) + { + generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, firstVarDeclWithInitializer.Initializer.Value, cancellationToken); + } + } + } + + // var w1 = (MyD1)foo; + if (nameOrMemberAccessExpression.Parent.IsKind(SyntaxKind.CastExpression)) + { + var castExpression = (CastExpressionSyntax)nameOrMemberAccessExpression.Parent; + if (castExpression.Expression != null) + { + generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, castExpression.Expression, cancellationToken); + } + } + } + + return true; + } + + private IMethodSymbol GetMethodSymbolIfPresent(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) + { + if (expression == null) + { + return null; + } + + var memberGroup = semanticModel.GetMemberGroup(expression, cancellationToken); + if (memberGroup.Count() != 0) + { + return memberGroup.ElementAt(0).IsKind(SymbolKind.Method) ? (IMethodSymbol)memberGroup.ElementAt(0) : null; + } + + var expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type; + if (expressionType.IsDelegateType()) + { + return ((INamedTypeSymbol)expressionType).DelegateInvokeMethod; + } + + var expressionSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol; + if (expressionSymbol.IsKind(SymbolKind.Method)) + { + return (IMethodSymbol)expressionSymbol; + } + + return null; + } + + private Accessibility DetermineAccessibilityConstraint( + State state, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + return semanticModel.DetermineAccessibilityConstraint( + state.NameOrMemberAccessExpression as TypeSyntax, cancellationToken); + } + + protected override IList<ITypeParameterSymbol> GetTypeParameters( + State state, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + if (state.SimpleName is GenericNameSyntax) + { + var genericName = (GenericNameSyntax)state.SimpleName; + var typeArguments = state.SimpleName.Arity == genericName.TypeArgumentList.Arguments.Count + ? genericName.TypeArgumentList.Arguments.OfType<SyntaxNode>().ToList() + : Enumerable.Repeat<SyntaxNode>(null, state.SimpleName.Arity); + return this.GetTypeParameters(state, semanticModel, typeArguments, cancellationToken); + } + + return SpecializedCollections.EmptyList<ITypeParameterSymbol>(); + } + + protected override bool TryGetArgumentList(ObjectCreationExpressionSyntax objectCreationExpression, out IList<ArgumentSyntax> argumentList) + { + if (objectCreationExpression != null && objectCreationExpression.ArgumentList != null) + { + argumentList = objectCreationExpression.ArgumentList.Arguments.ToList(); + return true; + } + + argumentList = null; + return false; + } + + protected override IList<string> GenerateParameterNames( + SemanticModel semanticModel, IList<ArgumentSyntax> arguments) + { + return semanticModel.GenerateParameterNames(arguments); + } + + public override string GetRootNamespace(CompilationOptions options) + { + return string.Empty; + } + + protected override bool IsInVariableTypeContext(ExpressionSyntax expression) + { + return false; + } + + protected override INamedTypeSymbol DetermineTypeToGenerateIn(SemanticModel semanticModel, SimpleNameSyntax simpleName, CancellationToken cancellationToken) + { + return semanticModel.GetEnclosingNamedType(simpleName.SpanStart, cancellationToken); + } + + protected override Accessibility GetAccessibility(State state, SemanticModel semanticModel, bool intoNamespace, CancellationToken cancellationToken) + { + var accessibility = DetermineDefaultAccessibility(state, semanticModel, intoNamespace, cancellationToken); + if (!state.IsTypeGeneratedIntoNamespaceFromMemberAccess) + { + var accessibilityConstraint = DetermineAccessibilityConstraint(state, semanticModel, cancellationToken); + + if (accessibilityConstraint == Accessibility.Public || + accessibilityConstraint == Accessibility.Internal) + { + accessibility = accessibilityConstraint; + } + } + + return accessibility; + } + + protected override ITypeSymbol DetermineArgumentType(SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken) + { + return argument.DetermineParameterType(semanticModel, cancellationToken); + } + + protected override bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType) + { + return compilation.ClassifyConversion(sourceType, targetType).IsImplicit; + } + + public override async Task<Tuple<INamespaceSymbol, INamespaceOrTypeSymbol, Location>> GetOrGenerateEnclosingNamespaceSymbol(INamedTypeSymbol namedTypeSymbol, string[] containers, Document selectedDocument, SyntaxNode selectedDocumentRoot, CancellationToken cancellationToken) + { + var compilationUnit = (CompilationUnitSyntax)selectedDocumentRoot; + var semanticModel = await selectedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (containers.Length != 0) + { + // Search the NS declaration in the root + var containerList = new List<string>(containers); + var enclosingNamespace = GetDeclaringNamespace(containerList, 0, compilationUnit); + if (enclosingNamespace != null) + { + var enclosingNamespaceSymbol = semanticModel.GetSymbolInfo(enclosingNamespace.Name, cancellationToken); + if (enclosingNamespaceSymbol.Symbol != null) + { + return Tuple.Create((INamespaceSymbol)enclosingNamespaceSymbol.Symbol, + (INamespaceOrTypeSymbol)namedTypeSymbol, + enclosingNamespace.CloseBraceToken.GetLocation()); + } + } + } + + var globalNamespace = semanticModel.GetEnclosingNamespace(0, cancellationToken); + var rootNamespaceOrType = namedTypeSymbol.GenerateRootNamespaceOrType(containers); + var lastMember = compilationUnit.Members.LastOrDefault(); + Location afterThisLocation = null; + if (lastMember != null) + { + afterThisLocation = semanticModel.SyntaxTree.GetLocation(new TextSpan(lastMember.Span.End, 0)); + } + else + { + afterThisLocation = semanticModel.SyntaxTree.GetLocation(new TextSpan()); + } + + return Tuple.Create(globalNamespace, + rootNamespaceOrType, + afterThisLocation); + } + + private NamespaceDeclarationSyntax GetDeclaringNamespace(List<string> containers, int indexDone, CompilationUnitSyntax compilationUnit) + { + foreach (var member in compilationUnit.Members) + { + var namespaceDeclaration = GetDeclaringNamespace(containers, 0, member); + if (namespaceDeclaration != null) + { + return namespaceDeclaration; + } + } + + return null; + } + + private NamespaceDeclarationSyntax GetDeclaringNamespace(List<string> containers, int indexDone, SyntaxNode localRoot) + { + var namespaceDecl = localRoot as NamespaceDeclarationSyntax; + if (namespaceDecl == null || namespaceDecl.Name is AliasQualifiedNameSyntax) + { + return null; + } + + List<string> namespaceContainers = new List<string>(); + GetNamespaceContainers(namespaceDecl.Name, namespaceContainers); + + if (namespaceContainers.Count + indexDone > containers.Count || + !IdentifierMatches(indexDone, namespaceContainers, containers)) + { + return null; + } + + indexDone = indexDone + namespaceContainers.Count; + if (indexDone == containers.Count) + { + return namespaceDecl; + } + + foreach (var member in namespaceDecl.Members) + { + var resultant = GetDeclaringNamespace(containers, indexDone, member); + if (resultant != null) + { + return resultant; + } + } + + return null; + } + + private bool IdentifierMatches(int indexDone, List<string> namespaceContainers, List<string> containers) + { + for (int i = 0; i < namespaceContainers.Count; ++i) + { + if (namespaceContainers[i] != containers[indexDone + i]) + { + return false; + } + } + + return true; + } + + private void GetNamespaceContainers(NameSyntax name, List<string> namespaceContainers) + { + if (name is QualifiedNameSyntax) + { + GetNamespaceContainers(((QualifiedNameSyntax)name).Left, namespaceContainers); + namespaceContainers.Add(((QualifiedNameSyntax)name).Right.Identifier.ValueText); + } + else + { + Debug.Assert(name is SimpleNameSyntax); + namespaceContainers.Add(((SimpleNameSyntax)name).Identifier.ValueText); + } + } + + internal override bool TryGetBaseList(ExpressionSyntax expression, out TypeKindOptions typeKindValue) + { + typeKindValue = TypeKindOptions.AllOptions; + + if (expression == null) + { + return false; + } + + var node = expression as SyntaxNode; + + while (node != null) + { + if (node is BaseListSyntax) + { + if (node.Parent != null && (node.Parent is InterfaceDeclarationSyntax || node.Parent is StructDeclarationSyntax)) + { + typeKindValue = TypeKindOptions.Interface; + return true; + } + + typeKindValue = TypeKindOptions.BaseList; + return true; + } + + node = node.Parent; + } + + return false; + } + + internal override bool IsPublicOnlyAccessibility(ExpressionSyntax expression, Project project) + { + if (expression == null) + { + return false; + } + + if (GeneratedTypesMustBePublic(project)) + { + return true; + } + + var node = expression as SyntaxNode; + SyntaxNode previousNode = null; + + while (node != null) + { + // Types in BaseList, Type Constraint or Member Types cannot be of restricter accessibility than the declaring type + if ((node is BaseListSyntax || node is TypeParameterConstraintClauseSyntax) && + node.Parent != null && + node.Parent is TypeDeclarationSyntax) + { + var typeDecl = node.Parent as TypeDeclarationSyntax; + if (typeDecl != null) + { + if (typeDecl.GetModifiers().Any(m => m.Kind() == SyntaxKind.PublicKeyword)) + { + return IsAllContainingTypeDeclsPublic(typeDecl); + } + else + { + // The Type Decl which contains the BaseList does not contain Public + return false; + } + } + + //Contract.Fail("Cannot reach this point"); + } + + if ((node is EventDeclarationSyntax || node is EventFieldDeclarationSyntax) && + node.Parent != null && + node.Parent is TypeDeclarationSyntax) + { + // Make sure the GFU is not inside the Accessors + if (previousNode != null && previousNode is AccessorListSyntax) + { + return false; + } + + // Make sure that Event Declaration themselves are Public in the first place + if (!node.GetModifiers().Any(m => m.Kind() == SyntaxKind.PublicKeyword)) + { + return false; + } + + return IsAllContainingTypeDeclsPublic(node); + } + + previousNode = node; + node = node.Parent; + } + + return false; + } + + private bool IsAllContainingTypeDeclsPublic(SyntaxNode node) + { + // Make sure that all the containing Type Declarations are also Public + var containingTypeDeclarations = node.GetAncestors<TypeDeclarationSyntax>(); + if (containingTypeDeclarations.Count() == 0) + { + return true; + } + else + { + return containingTypeDeclarations.All(typedecl => typedecl.GetModifiers().Any(m => m.Kind() == SyntaxKind.PublicKeyword)); + } + } + + internal override bool IsGenericName(SimpleNameSyntax simpleName) + { + if (simpleName == null) + { + return false; + } + + var genericName = simpleName as GenericNameSyntax; + return genericName != null; + } + + internal override bool IsSimpleName(ExpressionSyntax expression) + { + return expression is SimpleNameSyntax; + } + + internal override Solution TryAddUsingsOrImportToDocument(Solution updatedSolution, SyntaxNode modifiedRoot, Document document, SimpleNameSyntax simpleName, string includeUsingsOrImports, CancellationToken cancellationToken) + { + // Nothing to include + if (string.IsNullOrWhiteSpace(includeUsingsOrImports)) + { + return updatedSolution; + } + + var placeSystemNamespaceFirst = true;//document.Project.Solution.Workspace.Options.GetOption(OrganizerOptions.PlaceSystemNamespaceFirst, document.Project.Language); + + SyntaxNode root = null; + if (modifiedRoot == null) + { + root = document.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken); + } + else + { + root = modifiedRoot; + } + + if (root is CompilationUnitSyntax) + { + var compilationRoot = (CompilationUnitSyntax)root; + var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(includeUsingsOrImports)); + + // Check if the usings is already present + if (compilationRoot.Usings.Where(n => n != null && n.Alias == null) + .Select(n => n.Name.ToString()) + .Any(n => n.Equals(includeUsingsOrImports))) + { + return updatedSolution; + } + + // Check if the GFU is triggered from the namespace same as the usings namespace + if (IsWithinTheImportingNamespace(document, simpleName.SpanStart, includeUsingsOrImports, cancellationToken)) + { + return updatedSolution; + } + + var addedCompilationRoot = compilationRoot.AddUsingDirectives(new[] { usingDirective }, placeSystemNamespaceFirst, Formatter.Annotation); + updatedSolution = updatedSolution.WithDocumentSyntaxRoot(document.Id, addedCompilationRoot, PreservationMode.PreserveIdentity); + } + + return updatedSolution; + } + + private ITypeSymbol GetPropertyType( + SimpleNameSyntax property, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + var parent = property.Parent as AssignmentExpressionSyntax; + if (parent != null) + { + return TypeGuessing.typeInferenceService.InferType(semanticModel, parent.Left, true, cancellationToken); + } + + return null; + } + + private IPropertySymbol CreatePropertySymbol(SimpleNameSyntax propertyName, ITypeSymbol propertyType) + { + return CodeGenerationSymbolFactory.CreatePropertySymbol( + attributes: SpecializedCollections.EmptyList<AttributeData>(), + accessibility: Accessibility.Public, + modifiers: new DeclarationModifiers(), + explicitInterfaceSymbol: null, + name: propertyName.ToString(), + type: propertyType, + parameters: null, + getMethod: s_accessor, + setMethod: s_accessor, + isIndexer: false); + } + + private static readonly IMethodSymbol s_accessor = CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: null, + accessibility: Accessibility.Public, + statements: null); + + internal override bool TryGenerateProperty( + SimpleNameSyntax propertyName, + SemanticModel semanticModel, + CancellationToken cancellationToken, + out IPropertySymbol property) + { + property = null; + var propertyType = GetPropertyType(propertyName, semanticModel, cancellationToken); + if (propertyType == null || propertyType is IErrorTypeSymbol) + { + property = CreatePropertySymbol(propertyName, semanticModel.Compilation.ObjectType); + return true; + } + + property = CreatePropertySymbol(propertyName, propertyType); + return true; + } + + internal override IMethodSymbol GetDelegatingConstructor(ObjectCreationExpressionSyntax objectCreation, INamedTypeSymbol namedType, SemanticModel model, ISet<IMethodSymbol> candidates, CancellationToken cancellationToken) + { + var oldNode = objectCreation + .AncestorsAndSelf(ascendOutOfTrivia: false) + .Where(node => SpeculationAnalyzer.CanSpeculateOnNode(node)) + .LastOrDefault(); + + var typeNameToReplace = objectCreation.Type; + var newTypeName = namedType.GenerateTypeSyntax(); + var newObjectCreation = objectCreation.WithType(newTypeName).WithAdditionalAnnotations(s_annotation); + var newNode = oldNode.ReplaceNode(objectCreation, newObjectCreation); + + var speculativeModel = SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(oldNode, newNode, model); + if (speculativeModel != null) + { + newObjectCreation = (ObjectCreationExpressionSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); + var symbolInfo = speculativeModel.GetSymbolInfo(newObjectCreation, cancellationToken); + return GenerateConstructorHelpers.GetDelegatingConstructor(symbolInfo, candidates, namedType); + } + + return null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeDialogOptions.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeDialogOptions.cs new file mode 100644 index 0000000000..ad969f88b3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeDialogOptions.cs @@ -0,0 +1,27 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateType +{ + public class GenerateTypeDialogOptions + { + public bool IsPublicOnlyAccessibility { get; } + public TypeKindOptions TypeKindOptions { get; } + public bool IsAttribute { get; } + + public GenerateTypeDialogOptions( + bool isPublicOnlyAccessibility = false, + TypeKindOptions typeKindOptions = TypeKindOptions.AllOptions, + bool isAttribute = false) + { + IsPublicOnlyAccessibility = isPublicOnlyAccessibility; + this.TypeKindOptions = typeKindOptions; + IsAttribute = isAttribute; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeOptionsResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeOptionsResult.cs new file mode 100644 index 0000000000..17a4e6b4cc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/GenerateTypeOptionsResult.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateType +{ + public class GenerateTypeOptionsResult + { + public static readonly GenerateTypeOptionsResult Cancelled = new GenerateTypeOptionsResult(isCancelled: true); + + public Accessibility Accessibility { get; } + public Document ExistingDocument { get; } + public bool IsCancelled { get; } + public bool IsNewFile { get; } + public IList<string> Folders { get; } + public string NewFileName { get; } + public Project Project { get; } + public TypeKind TypeKind { get; } + public string FullFilePath { get; } + public string TypeName { get; } + public bool AreFoldersValidIdentifiers { get; } + + public GenerateTypeOptionsResult( + Accessibility accessibility, + TypeKind typeKind, + string typeName, + Project project, + bool isNewFile, + string newFileName, + IList<string> folders, + string fullFilePath, + Document existingDocument, + bool areFoldersValidIdentifiers, + bool isCancelled = false) + { + this.Accessibility = accessibility; + this.TypeKind = typeKind; + this.TypeName = typeName; + this.Project = project; + this.IsNewFile = isNewFile; + this.NewFileName = newFileName; + this.Folders = folders; + this.FullFilePath = fullFilePath; + this.ExistingDocument = existingDocument; + this.AreFoldersValidIdentifiers = areFoldersValidIdentifiers; + this.IsCancelled = isCancelled; + } + + private GenerateTypeOptionsResult(bool isCancelled) + { + this.IsCancelled = isCancelled; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/TypeKindOptions.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/TypeKindOptions.cs new file mode 100644 index 0000000000..3bfdc96dfb --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GenerateType/TypeKindOptions.cs @@ -0,0 +1,89 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.NRefactory6.CSharp.GenerateType +{ + [Flags] + public enum TypeKindOptions + { + None = 0x0, + + Class = 0x1, + Structure = 0x2, + Interface = 0x4, + Enum = 0x8, + Delegate = 0x10, + Module = 0x20, + + // Enables class, struct, interface, enum and delegate + AllOptions = Class | Structure | Interface | Enum | Delegate, + + // Only class is valid with Attribute + Attribute = Class, + + // Only class, struct and interface are allowed. No Enums + BaseList = Class | Interface, + + AllOptionsWithModule = AllOptions | Module, + + // Only Interface and Delegate cannot be part of the member access with Namespace as Left expression + MemberAccessWithNamespace = Class | Structure | Enum | Module, + + // Enum and Modules are incompatible with Generics + GenericInCompatibleTypes = Enum | Module + } + + internal class TypeKindOptionsHelper + { + public static bool IsClass(TypeKindOptions option) + { + return (option & TypeKindOptions.Class) != 0 ? true : false; + } + + public static bool IsStructure(TypeKindOptions option) + { + return (option & TypeKindOptions.Structure) != 0 ? true : false; + } + + public static bool IsInterface(TypeKindOptions option) + { + return (option & TypeKindOptions.Interface) != 0 ? true : false; + } + + public static bool IsEnum(TypeKindOptions option) + { + return (option & TypeKindOptions.Enum) != 0 ? true : false; + } + + public static bool IsDelegate(TypeKindOptions option) + { + return (option & TypeKindOptions.Delegate) != 0 ? true : false; + } + + public static bool IsModule(TypeKindOptions option) + { + return (option & TypeKindOptions.Module) != 0 ? true : false; + } + + public static TypeKindOptions RemoveOptions(TypeKindOptions fromValue, params TypeKindOptions[] removeValues) + { + var tempReturnValue = fromValue; + foreach (var removeValue in removeValues) + { + tempReturnValue = tempReturnValue & ~removeValue; + } + + return tempReturnValue; + } + + internal static TypeKindOptions AddOption(TypeKindOptions toValue, TypeKindOptions addValue) + { + return toValue | addValue; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionHelpers.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionHelpers.cs new file mode 100644 index 0000000000..11f39afa91 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionHelpers.cs @@ -0,0 +1,130 @@ +// 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.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.GotoDefinition +{ + static class GoToDefinitionHelpers + { + + public static bool TryGoToDefinition( + ISymbol symbol, + Project project, + ITypeSymbol containingTypeSymbol, + bool throwOnHiddenDefinition, + CancellationToken cancellationToken) + { + var alias = symbol as IAliasSymbol; + if (alias != null) + { + var ns = alias.Target as INamespaceSymbol; + if (ns != null && ns.IsGlobalNamespace) + { + return false; + } + } + + // VB global import aliases have a synthesized SyntaxTree. + // We can't go to the definition of the alias, so use the target type. + + var solution = project.Solution; + if (symbol is IAliasSymbol && + GeneratedCodeRecognitionService.GetPreferredSourceLocations(solution, symbol).All(l => project.Solution.GetDocument(l.SourceTree) == null)) + { + symbol = ((IAliasSymbol)symbol).Target; + } + + var definition = SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).Result; + cancellationToken.ThrowIfCancellationRequested(); + + symbol = definition ?? symbol; + + if (TryThirdPartyNavigation(symbol, solution, containingTypeSymbol)) + { + return true; + } + + // If it is a partial method declaration with no body, choose to go to the implementation + // that has a method body. + if (symbol is IMethodSymbol) + { + symbol = ((IMethodSymbol)symbol).PartialImplementationPart ?? symbol; + } + + var preferredSourceLocations = GeneratedCodeRecognitionService.GetPreferredSourceLocations(solution, symbol).ToArray(); + if (!preferredSourceLocations.Any()) + { + // If there are no visible source locations, then tell the host about the symbol and + // allow it to navigate to it. THis will either navigate to any non-visible source + // locations, or it can appropriately deal with metadata symbols for hosts that can go + // to a metadata-as-source view. + return GoToDefinitionService.TryNavigateToSymbol(symbol, project, true); + } + + // If we have a single location, then just navigate to it. + if (preferredSourceLocations.Length == 1) + { + var firstItem = preferredSourceLocations[0]; + var workspace = project.Solution.Workspace; + if (GoToDefinitionService.CanNavigateToSpan(workspace, solution.GetDocument(firstItem.SourceTree).Id, firstItem.SourceSpan)) + { + return GoToDefinitionService.TryNavigateToSpan(workspace, solution.GetDocument(firstItem.SourceTree).Id, firstItem.SourceSpan, true); + } + else + { + if (throwOnHiddenDefinition) + { + const int E_FAIL = -2147467259; + throw new COMException("The definition of the object is hidden.", E_FAIL); + } + else + { + return false; + } + } + } + else + { + // We have multiple viable source locations, so ask the host what to do. Most hosts + // will simply display the results to the user and allow them to choose where to + // go. + GoToDefinitionService.DisplayMultiple (preferredSourceLocations.Select (location => Tuple.Create (solution, symbol, location)).ToList ()); + + return false; + } + } + + private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution, ITypeSymbol containingTypeSymbol) + { + // Allow third parties to navigate to all symbols except types/constructors + // if we are navigating from the corresponding type. + + if (containingTypeSymbol != null && + (symbol is ITypeSymbol || symbol.IsConstructor())) + { + var candidateTypeSymbol = symbol is ITypeSymbol + ? symbol + : symbol.ContainingType; + + if (containingTypeSymbol == candidateTypeSymbol) + { + // We are navigating from the same type, so don't allow third parties to perform the navigation. + // This ensures that if we navigate to a class from within that class, we'll stay in the same file + // rather than navigate to, say, XAML. + return false; + } + } + + // Notify of navigation so third parties can intercept the navigation + return GoToDefinitionService.TrySymbolNavigationNotify(symbol, solution); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionService.cs new file mode 100644 index 0000000000..70d90023a6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/GotoDefinition/GotoDefinitionService.cs @@ -0,0 +1,114 @@ +// 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.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp.Features.GotoDefinition +{ + public static class GoToDefinitionService + { + /// <summary> + /// Navigate to the first source location of a given symbol. + /// bool TryNavigateToSymbol(ISymbol symbol, Project project, bool usePreviewTab = false); + /// </summary> + public static Func<ISymbol, Project, bool, bool> TryNavigateToSymbol = delegate { + return true; + }; + + /// <summary> + /// Navigates to the given position in the specified document, opening it if necessary. + /// bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool usePreviewTab = false); + /// </summary> + public static Func<Workspace, DocumentId, TextSpan, bool, bool> TryNavigateToSpan = delegate { + return true; + }; + + /// <summary> + /// Determines whether it is possible to navigate to the given position in the specified document. + /// bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan); + /// </summary> + public static Func<Workspace, DocumentId, TextSpan, bool> CanNavigateToSpan = delegate { + return true; + }; + + public static Action<IEnumerable<Tuple<Solution, ISymbol, Location>>> DisplayMultiple = delegate { + }; + + /// <summary> + /// bool TrySymbolNavigationNotify(ISymbol symbol, Solution solution); + /// </summary> + /// <returns>True if the navigation was handled, indicating that the caller should not + /// perform the navigation. + /// + /// </returns> + public static Func<ISymbol, Solution, bool> TrySymbolNavigationNotify = delegate { + return false; + }; + + static ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation) + { + return symbol; + } + + public static async Task<ISymbol> FindSymbolAsync(Document document, int position, CancellationToken cancellationToken) + { + var workspace = document.Project.Solution.Workspace; + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + //var symbol = SymbolFinder.FindSymbolAtPosition(semanticModel, position, workspace, bindLiteralsToUnderlyingType: true, cancellationToken: cancellationToken); + var symbol = SymbolFinder.FindSymbolAtPosition(semanticModel, position, workspace, cancellationToken: cancellationToken); + + return FindRelatedExplicitlyDeclaredSymbol(symbol, semanticModel.Compilation); + } + +// public async Task<IEnumerable<INavigableItem>> FindDefinitionsAsync(Document document, int position, CancellationToken cancellationToken) +// { +// var symbol = await FindSymbolAsync(document, position, cancellationToken).ConfigureAwait(false); +// +// // realize the list here so that the consumer await'ing the result doesn't lazily cause +// // them to be created on an inappropriate thread. +// return NavigableItemFactory.GetItemsfromPreferredSourceLocations(document.Project.Solution, symbol).ToList(); +// } + + public static bool TryGoToDefinition(Document document, int position, CancellationToken cancellationToken) + { + var symbol = FindSymbolAsync(document, position, cancellationToken).Result; + + if (symbol != null) + { + var containingTypeSymbol = GetContainingTypeSymbol(position, document, cancellationToken); + + if (GoToDefinitionHelpers.TryGoToDefinition(symbol, document.Project, containingTypeSymbol, throwOnHiddenDefinition: true, cancellationToken: cancellationToken)) + { + return true; + } + } + + return false; + } + + private static ITypeSymbol GetContainingTypeSymbol(int caretPosition, Document document, CancellationToken cancellationToken) + { + var syntaxRoot = document.GetSyntaxRootAsync(cancellationToken).Result; + var containingTypeDeclaration = syntaxRoot.GetContainingTypeDeclaration(caretPosition); + + if (containingTypeDeclaration != null) + { + var semanticModel = document.GetSemanticModelAsync(cancellationToken).Result; + return semanticModel.GetDeclaredSymbol(containingTypeDeclaration, cancellationToken) as ITypeSymbol; + } + + return null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.Editor.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.Editor.cs new file mode 100644 index 0000000000..8c7f9384b9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.Editor.cs @@ -0,0 +1,166 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass +{ + public partial class AbstractImplementAbstractClassService + { + private partial class Editor + { + private readonly Document _document; + private readonly SemanticModel _model; + private readonly State _state; + + public Editor( + Document document, + SemanticModel model, + State state) + { + _document = document; + _model = model; + _state = state; + } + + public async Task<Document> GetEditAsync(CancellationToken cancellationToken) + { + var unimplementedMembers = _state.UnimplementedMembers; + + var memberDefinitions = GenerateMembers( + unimplementedMembers, + cancellationToken); + + var result = await CodeGenerator.AddMemberDeclarationsAsync( + _document.Project.Solution, + _state.ClassType, + memberDefinitions, + new CodeGenerationOptions(_state.Location.GetLocation(), generateDefaultAccessibility: false), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + + private IList<ISymbol> GenerateMembers( + IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> unimplementedMembers, + CancellationToken cancellationToken) + { + return + unimplementedMembers.SelectMany(t => t.Item2) + .Select(m => GenerateMember(m, cancellationToken)) + .WhereNotNull() + .ToList(); + } + + private ISymbol GenerateMember( + ISymbol member, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Check if we need to add 'unsafe' to the signature we're generating. + var addUnsafe = member.IsUnsafe() && !_state.Location.IsUnsafeContext(); + + return GenerateMember(member, addUnsafe, cancellationToken); + } + + private ISymbol GenerateMember( + ISymbol member, + bool addUnsafe, + CancellationToken cancellationToken) + { + var modifiers = DeclarationModifiers.None.WithIsOverride(true).WithIsUnsafe (addUnsafe); + var accessibility = member.ComputeResultantAccessibility(_state.ClassType); + + if (member.Kind == SymbolKind.Method) + { + return GenerateMethod((IMethodSymbol)member, modifiers, accessibility, cancellationToken); + } + else if (member.Kind == SymbolKind.Property) + { + return GenerateProperty((IPropertySymbol)member, modifiers, accessibility, cancellationToken); + } + else if (member.Kind == SymbolKind.Event) + { + var @event = (IEventSymbol)member; + return CodeGenerationSymbolFactory.CreateEventSymbol( + @event, + accessibility: accessibility, + modifiers: modifiers); + } + + return null; + } + + private ISymbol GenerateMethod( + IMethodSymbol method, DeclarationModifiers modifiers, Accessibility accessibility, CancellationToken cancellationToken) + { + var syntaxFactory = _document.Project.LanguageServices.GetService<SyntaxGenerator>(); + + var throwingBody = syntaxFactory.CreateThrowNotImplementStatement (_model.Compilation); + + method = method.EnsureNonConflictingNames(_state.ClassType, cancellationToken); + + return CodeGenerationSymbolFactory.CreateMethodSymbol( + method, + accessibility: accessibility, + modifiers: modifiers, + statements: new [] { throwingBody }); + } + + private IPropertySymbol GenerateProperty( + IPropertySymbol property, + DeclarationModifiers modifiers, + Accessibility accessibility, + CancellationToken cancellationToken) + { + var syntaxFactory = _document.Project.LanguageServices.GetService<SyntaxGenerator>(); + + var throwingBody = syntaxFactory.CreateThrowNotImplementedStatementBlock( + _model.Compilation); + + var getMethod = ShouldGenerateAccessor(property.GetMethod) + ? CodeGenerationSymbolFactory.CreateAccessorSymbol( + property.GetMethod, + attributes: null, + accessibility: property.GetMethod.ComputeResultantAccessibility(_state.ClassType), + statements: throwingBody) + : null; + + var setMethod = ShouldGenerateAccessor(property.SetMethod) + ? CodeGenerationSymbolFactory.CreateAccessorSymbol( + property.SetMethod, + attributes: null, + accessibility: property.SetMethod.ComputeResultantAccessibility(_state.ClassType), + statements: throwingBody) + : null; + + return CodeGenerationSymbolFactory.CreatePropertySymbol( + property, + accessibility: accessibility, + modifiers: modifiers, + getMethod: getMethod, + setMethod: setMethod); + } + + private bool ShouldGenerateAccessor(IMethodSymbol method) + { + return + method != null && + method.IsAccessibleWithin(_state.ClassType) && + _state.ClassType.FindImplementationForAbstractMember(method) == null; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.State.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.State.cs new file mode 100644 index 0000000000..f2d52104af --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.State.cs @@ -0,0 +1,70 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass +{ + public partial class AbstractImplementAbstractClassService + { + private class State + { + public SyntaxNode Location { get; } + public INamedTypeSymbol ClassType { get; } + public INamedTypeSymbol AbstractClassType { get; } + + // The members that are not implemented at all. + public IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> UnimplementedMembers { get; } + + private State(SyntaxNode node, INamedTypeSymbol classType, INamedTypeSymbol abstractClassType, IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> unimplementedMembers) + { + this.Location = node; + this.ClassType = classType; + this.AbstractClassType = abstractClassType; + this.UnimplementedMembers = unimplementedMembers; + } + + public static State Generate( + AbstractImplementAbstractClassService service, + Document document, + SemanticModel model, + SyntaxNode node, + CancellationToken cancellationToken) + { + INamedTypeSymbol classType, abstractClassType; + if (!service.TryInitializeState(document, model, node, cancellationToken, + out classType, out abstractClassType)) + { + return null; + } + + if (!CodeGenerator.CanAdd(document.Project.Solution, classType, cancellationToken)) + { + return null; + } + + if (classType.IsAbstract) + { + return null; + } + + var unimplementedMembers = classType.GetAllUnimplementedMembers( + SpecializedCollections.SingletonEnumerable(abstractClassType), cancellationToken); + + if (unimplementedMembers != null && unimplementedMembers.Count >= 1) + { + return new State(node, classType, abstractClassType, unimplementedMembers); + } + else + { + return null; + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.cs new file mode 100644 index 0000000000..88603c1b64 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/AbstractImplementAbstractClassService.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 System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass +{ + public abstract partial class AbstractImplementAbstractClassService + { + protected AbstractImplementAbstractClassService() + { + } + + protected abstract bool TryInitializeState(Document document, SemanticModel model, SyntaxNode classNode, CancellationToken cancellationToken, out INamedTypeSymbol classType, out INamedTypeSymbol abstractClassType); + + public Task<Document> ImplementAbstractClassAsync(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) + { + var state = State.Generate(this, document, model, node, cancellationToken); + if (state == null) + { + return Task.FromResult (default(Document)); + } + + return new Editor(document, model, state).GetEditAsync(cancellationToken); + } + + public bool CanImplementAbstractClass(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) + { + return State.Generate(this, document, model, node, cancellationToken) != null; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/CSharpImplementAbstractClassService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/CSharpImplementAbstractClassService.cs new file mode 100644 index 0000000000..6a6042ca1e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementAbstractClass/CSharpImplementAbstractClassService.cs @@ -0,0 +1,46 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass +{ + public class CSharpImplementAbstractClassService : AbstractImplementAbstractClassService + { + protected override bool TryInitializeState( + Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken, + out INamedTypeSymbol classType, out INamedTypeSymbol abstractClassType) + { + var baseClassNode = node as TypeSyntax; + if (baseClassNode != null && baseClassNode.Parent is BaseTypeSyntax && + baseClassNode.Parent.IsParentKind(SyntaxKind.BaseList) && + ((BaseTypeSyntax)baseClassNode.Parent).Type == baseClassNode) + { + if (baseClassNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration)) + { + abstractClassType = model.GetTypeInfo(baseClassNode, cancellationToken).Type as INamedTypeSymbol; + cancellationToken.ThrowIfCancellationRequested(); + + if (abstractClassType.IsAbstractClass()) + { + var classDecl = baseClassNode.Parent.Parent.Parent as ClassDeclarationSyntax; + classType = model.GetDeclaredSymbol(classDecl, cancellationToken) as INamedTypeSymbol; + + return classType != null && abstractClassType != null; + } + } + } + + classType = null; + abstractClassType = null; + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs new file mode 100644 index 0000000000..8baba57a93 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -0,0 +1,559 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp.CodeGeneration; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + internal partial class ImplementInterfaceCodeAction : CodeAction + { + protected readonly bool Explicitly; + protected readonly bool Abstractly; + protected readonly ISymbol ThroughMember; + protected readonly Document Document; + protected readonly State State; + protected readonly AbstractImplementInterfaceService Service; + private readonly string _equivalenceKey; + + internal ImplementInterfaceCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state, + bool explicitly, + bool abstractly, + ISymbol throughMember) + { + this.Service = service; + this.Document = document; + this.State = state; + this.Abstractly = abstractly; + this.Explicitly = explicitly; + this.ThroughMember = throughMember; + _equivalenceKey = ComputeEquivalenceKey(state, explicitly, abstractly, throughMember, this.GetType().FullName); + } + + public static ImplementInterfaceCodeAction CreateImplementAbstractlyCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state) + { + return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: true, throughMember: null); + } + + public static ImplementInterfaceCodeAction CreateImplementCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state) + { + return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, throughMember: null); + } + + public static ImplementInterfaceCodeAction CreateImplementExplicitlyCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state) + { + return new ImplementInterfaceCodeAction(service, document, state, explicitly: true, abstractly: false, throughMember: null); + } + + public static ImplementInterfaceCodeAction CreateImplementThroughMemberCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state, + ISymbol throughMember) + { + return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, throughMember: throughMember); + } + + public override string Title + { + get + { + if (Explicitly) + { + return Resources.ImplementInterfaceExplicitly; + } + else if (Abstractly) + { + return Resources.ImplementInterfaceAbstractly; + } + else if (ThroughMember != null) + { + return string.Format(Resources.ImplementInterfaceThrough, GetDescription(ThroughMember)); + } + else + { + return Resources.ImplementInterface; + } + } + } + + private static string ComputeEquivalenceKey( + State state, + bool explicitly, + bool abstractly, + ISymbol throughMember, + string codeActionTypeName) + { + var interfaceType = state.InterfaceTypes.First(); + var typeName = interfaceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var assemblyName = interfaceType.ContainingAssembly.Name; + + return GetCodeActionEquivalenceKey(assemblyName, typeName, explicitly, abstractly, throughMember, codeActionTypeName); + } + + // internal for testing purposes. + internal static string GetCodeActionEquivalenceKey( + string interfaceTypeAssemblyName, + string interfaceTypeFullyQualifiedName, + bool explicitly, + bool abstractly, + ISymbol throughMember, + string codeActionTypeName) + { + if (throughMember != null) + { + return null; + } + + return explicitly.ToString() + ";" + + abstractly.ToString() + ";" + + interfaceTypeAssemblyName + ";" + + interfaceTypeFullyQualifiedName + ";" + + codeActionTypeName; + } + + public override string EquivalenceKey + { + get + { + return _equivalenceKey; + } + } + + private static string GetDescription(ISymbol throughMember) + { + return throughMember.TypeSwitch( + (IFieldSymbol field) => field.Name, + (IPropertySymbol property) => property.Name); + } + + protected override Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + return GetUpdatedDocumentAsync(cancellationToken); + } + + public Task<Document> GetUpdatedDocumentAsync(CancellationToken cancellationToken) + { + var unimplementedMembers = Explicitly ? State.UnimplementedExplicitMembers : State.UnimplementedMembers; + return GetUpdatedDocumentAsync(Document, unimplementedMembers, State.ClassOrStructType, State.ClassOrStructDecl, cancellationToken); + } + + public virtual async Task<Document> GetUpdatedDocumentAsync( + Document document, + IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> unimplementedMembers, + INamedTypeSymbol classOrStructType, + SyntaxNode classOrStructDecl, + CancellationToken cancellationToken) + { + var result = document; + var compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + var memberDefinitions = GenerateMembers( + compilation, + unimplementedMembers, + cancellationToken); + + result = await CodeGenerator.AddMemberDeclarationsAsync( + result.Project.Solution, classOrStructType, memberDefinitions, + new CodeGenerationOptions(contextLocation: classOrStructDecl.GetLocation(), generateDefaultAccessibility: false), + cancellationToken).ConfigureAwait(false); + + return result; + } + + private IList<ISymbol> GenerateMembers( + Compilation compilation, + IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> unimplementedMembers, + CancellationToken cancellationToken) + { + // As we go along generating members we may end up with conflicts. For example, say + // you have "interface IFoo { string Bar { get; } }" and "interface IQuux { int Bar + // { get; } }" and we need to implement both 'Bar' methods. The second will have to + // be explicitly implemented as it will conflict with the first. So we need to keep + // track of what we've actually implemented so that we can check further interface + // members against both the actual type and that list. + // + // Similarly, if you have two interfaces with the same member, then we don't want to + // implement that member twice. + // + // Note: if we implement a method explicitly then we do *not* add it to this list. + // That's because later members won't conflict with it even if they have the same + // signature otherwise. i.e. if we chose to implement IFoo.Bar explicitly, then we + // could implement IQuux.Bar implicitly (and vice versa). + var implementedVisibleMembers = new List<ISymbol>(); + var implementedMembers = new List<ISymbol>(); + + foreach (var tuple in unimplementedMembers) + { + var interfaceType = tuple.Item1; + var unimplementedInterfaceMembers = tuple.Item2; + + foreach (var unimplementedInterfaceMember in unimplementedInterfaceMembers) + { + var member = GenerateMember(compilation, unimplementedInterfaceMember, implementedVisibleMembers, cancellationToken); + if (member != null) + { + implementedMembers.Add(member); + + if (!(member.ExplicitInterfaceImplementations().Any() && Service.HasHiddenExplicitImplementation)) + { + implementedVisibleMembers.Add(member); + } + } + } + } + + return implementedMembers; + } + + private bool IsReservedName(string name) + { + return + IdentifiersMatch(State.ClassOrStructType.Name, name) || + State.ClassOrStructType.TypeParameters.Any(t => IdentifiersMatch(t.Name, name)); + } + + private string DetermineMemberName(ISymbol member, List<ISymbol> implementedVisibleMembers) + { + if (HasConflictingMember(member, implementedVisibleMembers)) + { + var memberNames = State.ClassOrStructType.GetAccessibleMembersInThisAndBaseTypes<ISymbol>(State.ClassOrStructType).Select(m => m.Name); + + return NameGenerator.GenerateUniqueName( + string.Format("{0}_{1}", member.ContainingType.Name, member.Name), + n => !memberNames.Contains(n) && + !implementedVisibleMembers.Any(m => IdentifiersMatch(m.Name, n)) && + !IsReservedName(n)); + } + + return member.Name; + } + + private ISymbol GenerateMember( + Compilation compilation, + ISymbol member, + List<ISymbol> implementedVisibleMembers, + CancellationToken cancellationToken) + { + // First check if we already generate a member that matches the member we want to + // generate. This can happen in C# when you have interfaces that have the same + // method, and you are implementing implicitly. For example: + // + // interface IFoo { void Foo(); } + // + // interface IBar : IFoo { new void Foo(); } + // + // class C : IBar + // + // In this case we only want to generate 'Foo' once. + if (HasMatchingMember(implementedVisibleMembers, member)) + { + return null; + } + + var memberName = DetermineMemberName(member, implementedVisibleMembers); + + // See if we need to generate an invisible member. If we do, then reset the name + // back to what then member wants it to be. + var generateInvisibleMember = GenerateInvisibleMember(member, memberName); + memberName = generateInvisibleMember ? member.Name : memberName; + + var generateAbstractly = !generateInvisibleMember && Abstractly; + + // Check if we need to add 'new' to the signature we're adding. We only need to do this + // if we're not generating something explicit and we have a naming conflict with + // something in our base class hierarchy. + var addNew = !generateInvisibleMember && HasNameConflict(member, memberName, State.ClassOrStructType.GetBaseTypes()); + + // Check if we need to add 'unsafe' to the signature we're generating. + var addUnsafe = member.IsUnsafe() && !State.Location.IsUnsafeContext(); + + return GenerateMember(compilation, member, memberName, generateInvisibleMember, generateAbstractly, addNew, addUnsafe, cancellationToken); + } + + private bool GenerateInvisibleMember(ISymbol member, string memberName) + { + if (Service.HasHiddenExplicitImplementation) + { + // User asked for an explicit (i.e. invisible) member. + if (Explicitly) + { + return true; + } + + // Have to create an invisible member if we have constraints we can't express + // with a visible member. + if (HasUnexpressableConstraint(member)) + { + return true; + } + + // If we had a conflict with a member of the same name, then we have to generate + // as an invisible member. + if (member.Name != memberName) + { + return true; + } + } + + // Can't generate an invisible member if the lanugage doesn't support it. + return false; + } + + private bool HasUnexpressableConstraint(ISymbol member) + { + // interface IFoo<T> { void Bar<U>() where U : T; } + // + // class A : IFoo<int> { } + // + // In this case we cannot generate an implement method for Bar. That's because we'd + // need to say "where U : int" and that's disallowed by the language. So we must + // generate something explicit here. + if (member.Kind != SymbolKind.Method) + { + return false; + } + + var method = member as IMethodSymbol; + + return method.TypeParameters.Any(IsUnexpressableTypeParameter); + } + + private static bool IsUnexpressableTypeParameter(ITypeParameterSymbol typeParameter) + { + var condition1 = typeParameter.ConstraintTypes.Count(t => t.TypeKind == TypeKind.Class) >= 2; + var condition2 = typeParameter.ConstraintTypes.Any(ts => ts.IsUnexpressableTypeParameterConstraint()); + var condition3 = typeParameter.HasReferenceTypeConstraint && typeParameter.ConstraintTypes.Any(ts => ts.IsReferenceType && ts.SpecialType != SpecialType.System_Object); + + return condition1 || condition2 || condition3; + } + + private ISymbol GenerateMember( + Compilation compilation, + ISymbol member, + string memberName, + bool generateInvisibly, + bool generateAbstractly, + bool addNew, + bool addUnsafe, + CancellationToken cancellationToken) + { + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + var modifiers = DeclarationModifiers.None.WithIsAbstract(generateAbstractly).WithIsNew (addNew).WithIsUnsafe (addUnsafe); + + var useExplicitInterfaceSymbol = generateInvisibly || !Service.CanImplementImplicitly; + var accessibility = member.Name == memberName ? Accessibility.Public : Accessibility.Private; + + if (member.Kind == SymbolKind.Method) + { + var method = (IMethodSymbol)member; + + return GenerateMethod(compilation, method, accessibility, modifiers, generateAbstractly, useExplicitInterfaceSymbol, memberName, cancellationToken); + } + else if (member.Kind == SymbolKind.Property) + { + var property = (IPropertySymbol)member; + + return GenerateProperty(compilation, property, accessibility, modifiers, generateAbstractly, useExplicitInterfaceSymbol, memberName, cancellationToken); + } + else if (member.Kind == SymbolKind.Event) + { + var @event = (IEventSymbol)member; + + var accessor = CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: null, + accessibility: Accessibility.NotApplicable, + statements: factory.CreateThrowNotImplementedStatementBlock(compilation)); + + return CodeGenerationSymbolFactory.CreateEventSymbol( + @event, + accessibility: accessibility, + modifiers: modifiers, + explicitInterfaceSymbol: useExplicitInterfaceSymbol ? @event : null, + name: memberName, + addMethod: generateInvisibly ? accessor : null, + removeMethod: generateInvisibly ? accessor : null); + } + + return null; + } + + private SyntaxNode CreateThroughExpression(SyntaxGenerator factory) + { + var through = ThroughMember.IsStatic + ? factory.IdentifierName(State.ClassOrStructType.Name) + : factory.ThisExpression(); + + through = factory.MemberAccessExpression( + through, factory.IdentifierName(ThroughMember.Name)); + + var throughMemberType = ThroughMember.GetMemberType(); + if ((State.InterfaceTypes != null) && (throughMemberType != null)) + { + // In the case of 'implement interface through field / property' , we need to know what + // interface we are implementing so that we can insert casts to this interface on every + // usage of the field in the generated code. Without these casts we would end up generating + // code that fails compilation in certain situations. + // + // For example consider the following code. + // class C : IReadOnlyList<int> { int[] field; } + // When applying the 'implement interface through field' code fix in the above example, + // we need to generate the following code to implement the Count property on IReadOnlyList<int> + // class C : IReadOnlyList<int> { int[] field; int Count { get { ((IReadOnlyList<int>)field).Count; } ...} + // as opposed to the following code which will fail to compile (because the array field + // doesn't have a property named .Count) - + // class C : IReadOnlyList<int> { int[] field; int Count { get { field.Count; } ...} + // + // The 'InterfaceTypes' property on the state object always contains only one item + // in the case of C# i.e. it will contain exactly the interface we are trying to implement. + // This is also the case most of the time in the case of VB, except in certain error conditions + // (recursive / circular cases) where the span of the squiggle for the corresponding + // diagnostic (BC30149) changes and 'InterfaceTypes' ends up including all interfaces + // in the Implements clause. For the purposes of inserting the above cast, we ignore the + // 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))) + { + through = factory.CastExpression(interfaceBeingImplemented, + through.WithAdditionalAnnotations(Simplifier.Annotation)); + + through = through.Parenthesize(); + } + } + + return through.WithAdditionalAnnotations(Simplifier.Annotation); + } + + private bool HasNameConflict( + ISymbol member, + string memberName, + IEnumerable<INamedTypeSymbol> baseTypes) + { + // There's a naming conflict if any member in the base types chain is accessible to + // us, has our name. Note: a simple name won't conflict with a generic name (and + // vice versa). A method only conflicts with another method if they have the same + // parameter signature (return type is irrelevant). + return + baseTypes.Any(ts => ts.GetMembers(memberName) + .Where(m => m.IsAccessibleWithin(State.ClassOrStructType)) + .Any(m => HasNameConflict(member, memberName, m))); + } + + private static bool HasNameConflict( + ISymbol member, + string memberName, + ISymbol baseMember) + { + //Contract.Requires(memberName == baseMember.Name); + + if (member.Kind == SymbolKind.Method && baseMember.Kind == SymbolKind.Method) + { + // A method only conflicts with another method if htey have the same parameter + // signature (return type is irrelevant). + var method1 = (IMethodSymbol)member; + var method2 = (IMethodSymbol)baseMember; + + if (method1.MethodKind == MethodKind.Ordinary && + method2.MethodKind == MethodKind.Ordinary && + method1.TypeParameters.Length == method2.TypeParameters.Length) + { + return method1.Parameters.Select(p => p.Type) + .SequenceEqual(method2.Parameters.Select(p => p.Type)); + } + } + + // Any non method members with the same name simple name conflict. + return true; + } + + private bool IdentifiersMatch(string identifier1, string identifier2) + { + return this.IsCaseSensitive + ? identifier1 == identifier2 + : StringComparer.OrdinalIgnoreCase.Equals(identifier1, identifier2); + } + + private bool IsCaseSensitive + { + get + { + return true;//this.Document.GetLanguageService<ISyntaxFactsService>().IsCaseSensitive; + } + } + + private bool HasMatchingMember(List<ISymbol> implementedVisibleMembers, ISymbol member) + { + // If this is a language that doesn't support implicit implementation then no + // implemented members will ever match. For example, if you have: + // + // Interface IFoo : sub Foo() : End Interface + // + // Interface IBar : Inherits IFoo : Shadows Sub Foo() : End Interface + // + // Class C : Implements IBar + // + // We'll first end up generating: + // + // Public Sub Foo() Implements IFoo.Foo + // + // However, that same method won't be viable for IBar.Foo (unlike C#) because it + // explicitly specifies its interface). + if (!Service.CanImplementImplicitly) + { + return false; + } + + return implementedVisibleMembers.Any(m => MembersMatch(m, member)); + } + + private bool MembersMatch(ISymbol member1, ISymbol member2) + { + if (member1.Kind != member2.Kind) + { + return false; + } + + if (member1.DeclaredAccessibility != member1.DeclaredAccessibility || + member1.IsStatic != member1.IsStatic) + { + return false; + } + + if (member1.ExplicitInterfaceImplementations().Any() || member2.ExplicitInterfaceImplementations().Any()) + { + return false; + } + + return SignatureComparer.HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors( + member1, member2, this.IsCaseSensitive); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Conflicts.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Conflicts.cs new file mode 100644 index 0000000000..caee4b4f5f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Conflicts.cs @@ -0,0 +1,89 @@ +// 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.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + internal partial class ImplementInterfaceCodeAction + { + private bool HasConflictingMember(ISymbol member, List<ISymbol> implementedVisibleMembers) + { + // Checks if this member conflicts with an existing member in classOrStructType or with + // a method we've already implemented. If so, we'll need to implement this one + // explicitly. + + var allMembers = State.ClassOrStructType.GetAccessibleMembersInThisAndBaseTypes<ISymbol>(State.ClassOrStructType).Concat(implementedVisibleMembers); + + var conflict1 = allMembers.Any(m => HasConflict(m, member)); + var conflict2 = IsReservedName(member.Name); + + return conflict1 || conflict2; + } + + private bool HasConflict(ISymbol member1, ISymbol member2) + { + // If either of these members are invisible explicit, then there is no conflict. + if (Service.HasHiddenExplicitImplementation) + { + if (member1.ExplicitInterfaceImplementations().Any() || member2.ExplicitInterfaceImplementations().Any()) + { + // explicit methods don't conflict with anything. + return false; + } + } + + // Members normally conflict if they have the same name. The exceptions are methods + // and parameterized properties (which conflict if htey have the same signature). + if (!IdentifiersMatch(member1.Name, member2.Name)) + { + return false; + } + + // If they differ in type, then it's almost always a conflict. There may be + // exceptions to this, but i don't know of any. + if (member1.Kind != member2.Kind) + { + return true; + } + + // At this point, we have two members of the same type with the same name. If they + // have a different signature (for example, methods, or parameterized properties), + // then they do not conflict. + if (!SignatureComparer.HaveSameSignature(member1, member2, this.IsCaseSensitive)) + { + return false; + } + + // Now we have to members with the same name, type and signature. If the language + // doesn't support implicit implementation, then these members are definitely in + // conflict. + if (!Service.CanImplementImplicitly) + { + return true; + } + + // two members conflict if they have the same signature and have + // + // a) different return types + // b) different accessibility + // c) different constraints + if (member1.DeclaredAccessibility != member2.DeclaredAccessibility || + !SignatureComparer.HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors(member1, member2, this.IsCaseSensitive)) + { + return true; + } + + // Same name, type, accessibility, return type, *and* the services can implement + // implicitly. These are not in conflict. + return false; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs new file mode 100644 index 0000000000..fa1bd6dcfe --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + internal partial class ImplementInterfaceCodeAction + { + private ISymbol GenerateMethod( + Compilation compilation, + IMethodSymbol method, + Accessibility accessibility, + DeclarationModifiers modifiers, + bool generateAbstractly, + bool useExplicitInterfaceSymbol, + string memberName, + CancellationToken cancellationToken) + { + + var updatedMethod = method.EnsureNonConflictingNames( + this.State.ClassOrStructType, cancellationToken); + + updatedMethod = updatedMethod.RemoveAttributeFromParametersAndReturnType(compilation.ComAliasNameAttributeType()); + + return CodeGenerationSymbolFactory.CreateMethodSymbol( + updatedMethod, + accessibility: accessibility, + modifiers: modifiers, + explicitInterfaceSymbol: useExplicitInterfaceSymbol ? updatedMethod : null, + name: memberName, + statements: generateAbstractly ? null : new[] { CreateStatement(compilation, updatedMethod, cancellationToken) }); + } + + private SyntaxNode CreateStatement( + Compilation compilation, + IMethodSymbol method, + CancellationToken cancellationToken) + { + if (ThroughMember == null) + { + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + return factory.CreateThrowNotImplementStatement(compilation); + } + else + { + return CreateDelegationStatement(method); + } + } + + private SyntaxNode CreateDelegationStatement( + IMethodSymbol method) + { + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + var through = CreateThroughExpression(factory); + + var memberName = method.IsGenericMethod + ? factory.GenericName(method.Name, method.TypeArguments.OfType<ITypeSymbol>().ToList()) + : factory.IdentifierName(method.Name); + + through = factory.MemberAccessExpression( + through, memberName); + + var arguments = factory.CreateArguments(method.Parameters.As<IParameterSymbol>()); + var invocationExpression = factory.InvocationExpression(through, arguments); + + return method.ReturnsVoid + ? factory.ExpressionStatement(invocationExpression) + : factory.ReturnStatement(invocationExpression); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Property.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Property.cs new file mode 100644 index 0000000000..1cd6f18d37 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Property.cs @@ -0,0 +1,150 @@ +// 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.CodeGeneration; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + internal partial class ImplementInterfaceCodeAction + { + private ISymbol GenerateProperty( + Compilation compilation, + IPropertySymbol property, + Accessibility accessibility, + DeclarationModifiers modifiers, + bool generateAbstractly, + bool useExplicitInterfaceSymbol, + string memberName, + CancellationToken cancellationToken) + { + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + var comAliasNameAttribute = compilation.ComAliasNameAttributeType(); + + var getAccessor = property.GetMethod == null + ? null + : CodeGenerationSymbolFactory.CreateAccessorSymbol( + property.GetMethod.RemoveAttributeFromParametersAndReturnType(comAliasNameAttribute), + attributes: null, + accessibility: accessibility, + explicitInterfaceSymbol: useExplicitInterfaceSymbol ? property.GetMethod : null, + statements: GetGetAccessorStatements(compilation, property, generateAbstractly, cancellationToken)); + + var setAccessor = property.SetMethod == null + ? null + : CodeGenerationSymbolFactory.CreateAccessorSymbol( + property.SetMethod.RemoveAttributeFromParametersAndReturnType(comAliasNameAttribute), + attributes: null, + accessibility: accessibility, + explicitInterfaceSymbol: useExplicitInterfaceSymbol ? property.SetMethod : null, + statements: GetSetAccessorStatements(compilation, property, generateAbstractly, cancellationToken)); + + var parameterNames = NameGenerator.EnsureUniqueness( + property.Parameters.Select(p => p.Name).ToList(), isCaseSensitive: true); + + var updatedProperty = property.RenameParameters(parameterNames); + + updatedProperty = updatedProperty.RemoveAttributeFromParameters(comAliasNameAttribute); + + // TODO(cyrusn): Delegate through throughMember if it's non-null. + return CodeGenerationSymbolFactory.CreatePropertySymbol( + updatedProperty, + accessibility: accessibility, + modifiers: modifiers, + explicitInterfaceSymbol: useExplicitInterfaceSymbol ? property : null, + name: memberName, + getMethod: getAccessor, + setMethod: setAccessor); + } + + private IList<SyntaxNode> GetSetAccessorStatements( + Compilation compilation, + IPropertySymbol property, + bool generateAbstractly, + CancellationToken cancellationToken) + { + if (generateAbstractly) + { + return null; + } + + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + if (ThroughMember != null) + { + var throughExpression = CreateThroughExpression(factory); + SyntaxNode expression; + + if (property.IsIndexer) + { + expression = throughExpression; + } + else + { + expression = factory.MemberAccessExpression( + throughExpression, factory.IdentifierName(property.Name)); + } + + if (property.Parameters.Length > 0) + { + var arguments = factory.CreateArguments(property.Parameters.As<IParameterSymbol>()); + expression = factory.ElementAccessExpression(expression, arguments); + } + + expression = factory.AssignmentStatement(expression, factory.IdentifierName("value")); + + return new[] { factory.ExpressionStatement(expression) }; + } + + return factory.CreateThrowNotImplementedStatementBlock(compilation); + } + + private IList<SyntaxNode> GetGetAccessorStatements( + Compilation compilation, + IPropertySymbol property, + bool generateAbstractly, + CancellationToken cancellationToken) + { + if (generateAbstractly) + { + return null; + } + + var factory = this.Document.GetLanguageService<SyntaxGenerator>(); + if (ThroughMember != null) + { + var throughExpression = CreateThroughExpression(factory); + SyntaxNode expression; + + if (property.IsIndexer) + { + expression = throughExpression; + } + else + { + expression = factory.MemberAccessExpression( + throughExpression, factory.IdentifierName(property.Name)); + } + + if (property.Parameters.Length > 0) + { + var arguments = factory.CreateArguments(property.Parameters.As<IParameterSymbol>()); + expression = factory.ElementAccessExpression(expression, arguments); + } + + return new[] { factory.ReturnStatement(expression) }; + } + + return factory.CreateThrowNotImplementedStatementBlock(compilation); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs new file mode 100644 index 0000000000..2830aec492 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs @@ -0,0 +1,150 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + private static INamedTypeSymbol TryGetSymbolForIDisposable(Compilation compilation) + { + // Get symbol for 'System.IDisposable'. + var idisposable = compilation.GetSpecialType(SpecialType.System_IDisposable); + if ((idisposable != null) && (idisposable.TypeKind == TypeKind.Interface)) + { + var idisposableMembers = idisposable.GetMembers().ToArray(); + + // Get symbol for 'System.IDisposable.Dispose()'. + IMethodSymbol disposeMethod = null; + if ((idisposableMembers.Length == 1) && (idisposableMembers[0].Kind == SymbolKind.Method) && + (idisposableMembers[0].Name == "Dispose")) + { + disposeMethod = idisposableMembers[0] as IMethodSymbol; + if ((disposeMethod != null) && (!disposeMethod.IsStatic) && disposeMethod.ReturnsVoid && + (disposeMethod.Arity == 0) && (disposeMethod.Parameters.Length == 0)) + { + return idisposable; + } + } + } + + return null; + } + + private bool ShouldImplementDisposePattern(Document document, State state, bool explicitly) + { + // Dispose pattern should be implemented only if - + // 1. An interface named 'System.IDisposable' is unimplemented. + // 2. This interface has one and only one member - a non-generic method named 'Dispose' that takes no arguments and returns 'void'. + // 3. The implementing type is a class that does not already declare any conflicting members named 'disposedValue' or 'Dispose' + // (because we will be generating a 'disposedValue' field and a couple of methods named 'Dispose' as part of implementing + // the dispose pattern). + var unimplementedMembers = explicitly ? state.UnimplementedExplicitMembers : state.UnimplementedMembers; + var idisposable = TryGetSymbolForIDisposable(state.Model.Compilation); + return (idisposable != null) && + unimplementedMembers.Any(m => m.Item1.Equals(idisposable)) && + this.CanImplementDisposePattern(state.ClassOrStructType, state.ClassOrStructDecl); + } + + internal class ImplementInterfaceWithDisposePatternCodeAction : ImplementInterfaceCodeAction + { + internal ImplementInterfaceWithDisposePatternCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state, + bool explicitly, + bool abstractly, + ISymbol throughMember) : base(service, document, state, explicitly, abstractly, throughMember) + { + } + + public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementWithDisposePatternCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state) + { + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: false, abstractly: false, throughMember: null); + } + + public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementExplicitlyWithDisposePatternCodeAction( + AbstractImplementInterfaceService service, + Document document, + State state) + { + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: true, abstractly: false, throughMember: null); + } + + public override string Title + { + get + { + if (Explicitly) + { + return Resources.ImplementInterfaceExplicitlyWithDisposePattern; + } + else + { + return Resources.ImplementInterfaceWithDisposePattern; + } + } + } + + private static readonly SyntaxAnnotation s_implementingTypeAnnotation = new SyntaxAnnotation("ImplementingType"); + public override async Task<Document> GetUpdatedDocumentAsync( + Document document, + IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> unimplementedMembers, + INamedTypeSymbol classOrStructType, + SyntaxNode classOrStructDecl, + CancellationToken cancellationToken) + { + var result = document; + var compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Add an annotation to the type declaration node so that we can find it again to append the dispose pattern implementation below. + result = await result.ReplaceNodeAsync( + classOrStructDecl, + classOrStructDecl.WithAdditionalAnnotations(s_implementingTypeAnnotation), + cancellationToken).ConfigureAwait(false); + var root = await result.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + classOrStructDecl = root.GetAnnotatedNodes(s_implementingTypeAnnotation).Single(); + compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + classOrStructType = classOrStructType.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol as INamedTypeSymbol; + + // Use the code generation service to generate all unimplemented members except those that are + // part of the dispose pattern. We can't use the code generation service to implement the dispose + // pattern since the code generation service doesn't support injection of the custom boiler + // plate code required for implementing the dispose pattern. + var idisposable = TryGetSymbolForIDisposable(compilation); + result = await base.GetUpdatedDocumentAsync( + result, + unimplementedMembers.Where(m => !m.Item1.Equals(idisposable)).ToList(), + classOrStructType, + classOrStructDecl, + cancellationToken).ConfigureAwait(false); + + // Now append the dispose pattern implementation. + root = await result.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + classOrStructDecl = root.GetAnnotatedNodes(s_implementingTypeAnnotation).Single(); + compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + classOrStructType = classOrStructType.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol as INamedTypeSymbol; + result = Service.ImplementDisposePattern(result, root, classOrStructType, classOrStructDecl.SpanStart, Explicitly); + + // Remove the annotation since we don't need it anymore. + root = await result.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + classOrStructDecl = root.GetAnnotatedNodes(s_implementingTypeAnnotation).Single(); + result = await result.ReplaceNodeAsync( + classOrStructDecl, + classOrStructDecl.WithoutAnnotations(s_implementingTypeAnnotation), + cancellationToken).ConfigureAwait(false); + + return result; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.State.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.State.cs new file mode 100644 index 0000000000..61b468fc9f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.State.cs @@ -0,0 +1,84 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + internal protected class State + { + public SyntaxNode Location { get; } + public SyntaxNode ClassOrStructDecl { get; } + public INamedTypeSymbol ClassOrStructType { get; } + public IEnumerable<INamedTypeSymbol> InterfaceTypes { get; } + public SemanticModel Model { get; } + + // The members that are not implemented at all. + public IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> UnimplementedMembers { get; private set; } + + // The members that have no explicit implementation. + public IList<Tuple<INamedTypeSymbol, IList<ISymbol>>> UnimplementedExplicitMembers { get; private set; } + + public State(SyntaxNode interfaceNode, SyntaxNode classOrStructDecl, INamedTypeSymbol classOrStructType, IEnumerable<INamedTypeSymbol> interfaceTypes, SemanticModel model) + { + this.Location = interfaceNode; + this.ClassOrStructDecl = classOrStructDecl; + this.ClassOrStructType = classOrStructType; + this.InterfaceTypes = interfaceTypes; + this.Model = model; + } + + public static State Generate( + AbstractImplementInterfaceService service, + Document document, + SemanticModel model, + SyntaxNode interfaceNode, + CancellationToken cancellationToken) + { + SyntaxNode classOrStructDecl; + INamedTypeSymbol classOrStructType; + IEnumerable<INamedTypeSymbol> interfaceTypes; + if (!service.TryInitializeState(document, model, interfaceNode, cancellationToken, + out classOrStructDecl, out classOrStructType, out interfaceTypes)) + { + return null; + } + + if (!CodeGenerator.CanAdd(document.Project.Solution, classOrStructType, cancellationToken)) + { + return null; + } + + var state = new State(interfaceNode, classOrStructDecl, classOrStructType, interfaceTypes, model); + + if (service.CanImplementImplicitly) + { + state.UnimplementedMembers = state.ClassOrStructType.GetAllUnimplementedMembers( + interfaceTypes, cancellationToken); + + state.UnimplementedExplicitMembers = state.ClassOrStructType.GetAllUnimplementedExplicitMembers( + interfaceTypes, cancellationToken); + + var allMembersImplemented = state.UnimplementedMembers == null || state.UnimplementedMembers.Count == 0; + var allMembersImplementedExplicitly = state.UnimplementedExplicitMembers == null || state.UnimplementedExplicitMembers.Count == 0; + + return !allMembersImplementedExplicitly && !allMembersImplemented ? state : null; + } + else + { + state.UnimplementedMembers = state.ClassOrStructType.GetAllUnimplementedExplicitMembers( + interfaceTypes, cancellationToken); + + var allMembersImplemented = state.UnimplementedMembers == null || state.UnimplementedMembers.Count == 0; + return !allMembersImplemented ? state : null; + } + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.cs new file mode 100644 index 0000000000..bce7ae13ae --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/AbstractImplementInterfaceService.cs @@ -0,0 +1,109 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public abstract partial class AbstractImplementInterfaceService + { + protected AbstractImplementInterfaceService() + { + } + + protected abstract bool CanImplementImplicitly { get; } + protected abstract bool HasHiddenExplicitImplementation { get; } + protected abstract bool TryInitializeState(Document document, SemanticModel model, SyntaxNode interfaceNode, CancellationToken cancellationToken, out SyntaxNode classOrStructDecl, out INamedTypeSymbol classOrStructType, out IEnumerable<INamedTypeSymbol> interfaceTypes); + protected abstract bool CanImplementDisposePattern(INamedTypeSymbol symbol, SyntaxNode classDecl); + protected abstract Document ImplementDisposePattern(Document document, SyntaxNode root, INamedTypeSymbol symbol, int position, bool explicitly); + + public async Task<Document> ImplementInterfaceAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var state = State.Generate(this, document, model, node, cancellationToken); + if (state == null) + { + return document; + } + + // While implementing just one default action, like in the case of pressing enter after interface name in VB, + // choose to implement with the dispose pattern as that's the Dev12 behavior. + var action = ShouldImplementDisposePattern(document, state, explicitly: false) ? + ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state) : + ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + + return await action.GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); + } + + public IEnumerable<CodeAction> GetCodeActions(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) + { + var state = State.Generate(this, document, model, node, cancellationToken); + return GetActions(document, state); + } + + private IEnumerable<CodeAction> GetActions(Document document, State state) + { + if (state == null) + { + yield break; + } + + if (state.UnimplementedMembers != null && state.UnimplementedMembers.Count > 0) + { + yield return ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + + if (ShouldImplementDisposePattern(document, state, explicitly: false)) + { + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state); + } + + var delegatableMembers = GetDelegatableMembers(state); + foreach (var member in delegatableMembers) + { + yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, state, member); + } + + if (state.ClassOrStructType.IsAbstract) + { + yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, state); + } + } + + if (state.UnimplementedExplicitMembers != null && state.UnimplementedExplicitMembers.Count > 0) + { + yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, state); + + if (ShouldImplementDisposePattern(document, state, explicitly: true)) + { + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, state); + } + } + } + + private IList<ISymbol> GetDelegatableMembers(State state) + { + var fields = + state.ClassOrStructType.GetMembers() + .OfType<IFieldSymbol>() + .Where(f => !f.IsImplicitlyDeclared) + .Where(f => f.Type.GetAllInterfacesIncludingThis().Contains(state.InterfaceTypes.First())) + .OfType<ISymbol>(); + + // Select all properties with zero parameters that also have a getter + var properties = + state.ClassOrStructType.GetMembers() + .OfType<IPropertySymbol>() + .Where(p => (!p.IsImplicitlyDeclared) && (p.Parameters.Length == 0) && (p.GetMethod != null)) + .Where(p => p.Type.GetAllInterfacesIncludingThis().Contains(state.InterfaceTypes.First())) + .OfType<ISymbol>(); + + return fields.Concat(properties).ToList(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/CSharpImplementInterfaceService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/CSharpImplementInterfaceService.cs new file mode 100644 index 0000000000..74c5693047 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ImplementInterface/CSharpImplementInterfaceService.cs @@ -0,0 +1,170 @@ +// 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.Composition; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface +{ + public class CSharpImplementInterfaceService : AbstractImplementInterfaceService + { + protected override bool TryInitializeState( + Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken, + out SyntaxNode classOrStructDecl, out INamedTypeSymbol classOrStructType, out IEnumerable<INamedTypeSymbol> interfaceTypes) + { + if (!cancellationToken.IsCancellationRequested) + { + var interfaceNode = node as TypeSyntax; + if (interfaceNode != null && interfaceNode.Parent is BaseTypeSyntax && + interfaceNode.Parent.IsParentKind(SyntaxKind.BaseList) && + ((BaseTypeSyntax)interfaceNode.Parent).Type == interfaceNode) + { + if (interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration) || + interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.StructDeclaration)) + { + var interfaceSymbolInfo = model.GetSymbolInfo(interfaceNode, cancellationToken); + if (interfaceSymbolInfo.CandidateReason != CandidateReason.WrongArity) + { + var interfaceType = interfaceSymbolInfo.GetAnySymbol() as INamedTypeSymbol; + cancellationToken.ThrowIfCancellationRequested(); + + if (interfaceType != null && interfaceType.TypeKind == TypeKind.Interface) + { + classOrStructDecl = interfaceNode.Parent.Parent.Parent as TypeDeclarationSyntax; + classOrStructType = model.GetDeclaredSymbol(classOrStructDecl, cancellationToken) as INamedTypeSymbol; + interfaceTypes = SpecializedCollections.SingletonEnumerable(interfaceType); + + return interfaceTypes != null && classOrStructType != null; + } + } + } + } + } + + classOrStructDecl = null; + classOrStructType = null; + interfaceTypes = null; + return false; + } + + protected override bool CanImplementImplicitly + { + get + { + return true; + } + } + + protected override bool HasHiddenExplicitImplementation + { + get + { + return true; + } + } + + private static ClassDeclarationSyntax GetClassDeclarationAt(SyntaxNode root, int position) + { + var node = root.FindToken(position).Parent.FirstAncestorOrSelf((SyntaxNode n) => n.IsKind(SyntaxKind.ClassDeclaration)); + return node as ClassDeclarationSyntax; + } + + protected override bool CanImplementDisposePattern(INamedTypeSymbol symbol, SyntaxNode classDecl) + { + // The dispose pattern is only applicable if the implementing type is a class that does not already declare any conflicting + // members named 'disposedValue' or 'Dispose' (because we will be generating a 'disposedValue' field and a couple of methods + // named 'Dispose' as part of implementing the dispose pattern). + return (classDecl != null) && + classDecl.IsKind(SyntaxKind.ClassDeclaration) && + (symbol != null) && + !symbol.GetMembers().Any(m => (m.MetadataName == "Dispose") || (m.MetadataName == "disposedValue")); + } + + protected override Document ImplementDisposePattern(Document document, SyntaxNode root, INamedTypeSymbol symbol, int position, bool explicitly) + { + var classDecl = GetClassDeclarationAt(root, position); + Debug.Assert(CanImplementDisposePattern(symbol, classDecl), "ImplementDisposePattern called with bad inputs"); + + // Generate the IDisposable boilerplate code. The generated code cannot be one giant resource string + // because of the need to parse, format, and simplify the result; during pseudo-localized builds, resource + // strings are given a special prefix and suffix that will break the parser, hence the requirement to + // localize the comments individually. + var code = string.Format (@" + #region IDisposable Support + private bool disposedValue = false; // {0} + + {1}void Dispose(bool disposing) + {{ + if (!disposedValue) + {{ + if (disposing) + {{ + // {2} + }} + + // {3} + // {4} + + disposedValue = true; + }} + }} + + // {5} + // ~{6}() {{ + // // {7} + // Dispose(false); + // }} + + // {8} + {9}Dispose() + {{ + // {10} + Dispose(true); + // {11} + // GC.SuppressFinalize(this); + }} + #endregion + ", + Resources.ToDetectRedundantCalls, + (symbol.IsSealed ? "" : "protected virtual "), + Resources.DisposeManagedStateTodo, + Resources.FreeUnmanagedResourcesTodo, + Resources.SetLargeFieldsToNullTodo, + Resources.OverrideAFinalizerTodo, + classDecl.Identifier.Value, + Resources.DoNotChangeThisCodeUseDispose, + Resources.ThisCodeAddedToCorrectlyImplementDisposable, + (explicitly ? "void System.IDisposable." : "public void "), + Resources.DoNotChangeThisCodeUseDispose, + Resources.UncommentTheFollowingIfFinalizerOverriddenTodo + ); + + var decls = SyntaxFactory.ParseSyntaxTree(code) + .GetRoot().DescendantNodes().OfType<MemberDeclarationSyntax>() + .Select(decl => decl.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)) + .ToArray(); + + // Append #endregion to the trailing trivia of the last declaration being generated. + decls[decls.Length - 1] = decls[decls.Length - 1].WithAppendedTrailingTrivia( + SyntaxFactory.TriviaList( + SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)), + SyntaxFactory.CarriageReturnLineFeed)); + + // Ensure that open and close brace tokens are generated in case they are missing. + var newNode = classDecl.EnsureOpenAndCloseBraceTokens().AddMembers(decls); + + return document.WithSyntaxRoot(root.ReplaceNode(classDecl, newNode)); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CSharpIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CSharpIndentEngine.cs new file mode 100644 index 0000000000..fc0f170a7f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CSharpIndentEngine.cs @@ -0,0 +1,537 @@ +// +// CSharpIndentEngine.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// Indentation engine based on a state machine. + /// Supports only pushing new chars to the end. + /// </summary> + /// <remarks> + /// Represents the context for transitions between <see cref="IndentState"/>. + /// Delegates the responsibility for pushing a new char to the current + /// state and changes between states depending on the pushed chars. + /// </remarks> + public class CSharpIndentEngine : IStateMachineIndentEngine + { + #region Properties + + /// <summary> + /// Formatting options. + /// </summary> + internal readonly OptionSet options; + + /// <summary> + /// Represents the new line character. + /// </summary> + internal readonly char newLineChar; + + /// <summary> + /// The current indentation state. + /// </summary> + internal IndentState currentState; + + /// <summary> + /// Stores conditional symbols of #define directives. + /// </summary> + internal HashSet<string> conditionalSymbols; + + /// <summary> + /// Stores custom conditional symbols. + /// </summary> + internal HashSet<string> customConditionalSymbols; + + /// <summary> + /// Stores the results of evaluations of the preprocessor if/elif directives + /// in the current block (between #if and #endif). + /// </summary> + internal CloneableStack<bool> ifDirectiveEvalResults = new CloneableStack<bool> (); + + /// <summary> + /// Stores the indentation levels of the if directives in the current block. + /// </summary> + internal CloneableStack<Indent> ifDirectiveIndents = new CloneableStack<Indent>(); + + /// <summary> + /// Stores the last sequence of characters that can form a + /// valid keyword or variable name. + /// </summary> + internal StringBuilder wordToken; + + /// <summary> + /// Stores the previous sequence of chars that formed a + /// valid keyword or variable name. + /// </summary> + internal string previousKeyword; + + #endregion + + #region IDocumentIndentEngine + + /// <inheritdoc /> + public string ThisLineIndent + { + get + { + // OPTION: IndentBlankLines + // remove the indentation of this line if isLineStart is true +// if (!textEditorOptions.IndentBlankLines && isLineStart) +// { +// return string.Empty; +// } + + return currentState.ThisLineIndent.IndentString; + } + } + + /// <inheritdoc /> + public string NextLineIndent + { + get + { + return currentState.NextLineIndent.IndentString; + } + } + + /// <inheritdoc /> + public string CurrentIndent + { + get + { + return currentIndent.ToString(); + } + } + + /// <inheritdoc /> + /// <remarks> + /// This is set depending on the current <see cref="Location"/> and + /// can change its value until the <see cref="newLineChar"/> char is + /// pushed. If this is true, that doesn't necessarily mean that the + /// current line has an incorrect indent (this can be determined + /// only at the end of the current line). + /// </remarks> + public bool NeedsReindent + { + get + { + // return true if it's the first column of the line and it has an indent + if (previousChar == newLineChar) + { + return ThisLineIndent.Length > 0; + } + + // ignore incorrect indentations when there's only ws on this line + if (isLineStart) + { + return false; + } + + return ThisLineIndent != CurrentIndent.ToString(); + } + } + + /// <inheritdoc /> + public int Offset + { + get + { + return offset; + } + } + +// /// <inheritdoc /> +// public TextLocation Location +// { +// get +// { +// return new TextLocation(line, column); +// } +// } + + /// <inheritdoc /> + public bool EnableCustomIndentLevels + { + get; + set; + } + + #endregion + + #region Fields + + /// <summary> + /// Represents the number of pushed chars. + /// </summary> + internal int offset = 0; + + /// <summary> + /// The current line number. + /// </summary> + internal int line = 1; + + /// <summary> + /// The current column number. + /// </summary> + /// <remarks> + /// One char can take up multiple columns (e.g. \t). + /// </remarks> + internal int column = 1; + + /// <summary> + /// True if <see cref="char.IsWhiteSpace(char)"/> is true for all + /// chars at the current line. + /// </summary> + internal bool isLineStart = true; + + /// <summary> + /// True if <see cref="isLineStart"/> was true before the current + /// <see cref="wordToken"/>. + /// </summary> + internal bool isLineStartBeforeWordToken = true; + + /// <summary> + /// Current char that's being pushed. + /// </summary> + internal char currentChar = '\0'; + + /// <summary> + /// Last non-whitespace char that has been pushed. + /// </summary> + internal char previousChar = '\0'; + + /// <summary> + /// Previous new line char + /// </summary> + internal char previousNewline = '\0'; + + /// <summary> + /// Current indent level on this line. + /// </summary> + internal StringBuilder currentIndent = new StringBuilder(); + + /// <summary> + /// True if this line began in <see cref="VerbatimStringState"/>. + /// </summary> + internal bool lineBeganInsideVerbatimString = false; + + /// <summary> + /// True if this line began in <see cref="MultiLineCommentState"/>. + /// </summary> + internal bool lineBeganInsideMultiLineComment = false; + + #endregion + + #region Constructors + + /// <summary> + /// Creates a new CSharpIndentEngine instance. + /// </summary> + /// <param name="document"> + /// An instance of <see cref="SourceText"/> which is being parsed. + /// </param> + /// <param name="formattingOptions"> + /// C# formatting options. + /// </param> + public CSharpIndentEngine(OptionSet formattingOptions) + { + this.options = formattingOptions; + + this.currentState = new GlobalBodyState(this); + + this.conditionalSymbols = new HashSet<string>(); + this.customConditionalSymbols = new HashSet<string>(); + this.wordToken = new StringBuilder(); + this.previousKeyword = string.Empty; + this.newLineChar = formattingOptions.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp)[0]; + } + + /// <summary> + /// Creates a new CSharpIndentEngine instance from the given prototype. + /// </summary> + /// <param name="prototype"> + /// An CSharpIndentEngine instance. + /// </param> + public CSharpIndentEngine(CSharpIndentEngine prototype) + { + this.options = prototype.options; + + this.newLineChar = prototype.newLineChar; + this.currentState = prototype.currentState.Clone(this); + this.conditionalSymbols = new HashSet<string>(prototype.conditionalSymbols); + this.customConditionalSymbols = new HashSet<string>(prototype.customConditionalSymbols); + + this.wordToken = new StringBuilder(prototype.wordToken.ToString()); + this.previousKeyword = string.Copy(prototype.previousKeyword); + + this.offset = prototype.offset; + this.line = prototype.line; + this.column = prototype.column; + this.isLineStart = prototype.isLineStart; + this.isLineStartBeforeWordToken = prototype.isLineStartBeforeWordToken; + this.currentChar = prototype.currentChar; + this.previousChar = prototype.previousChar; + this.previousNewline = prototype.previousNewline; + this.currentIndent = new StringBuilder(prototype.CurrentIndent.ToString()); + this.lineBeganInsideMultiLineComment = prototype.lineBeganInsideMultiLineComment; + this.lineBeganInsideVerbatimString = prototype.lineBeganInsideVerbatimString; + this.ifDirectiveEvalResults = prototype.ifDirectiveEvalResults.Clone(); + this.ifDirectiveIndents = prototype.ifDirectiveIndents.Clone(); + + this.EnableCustomIndentLevels = prototype.EnableCustomIndentLevels; + } + + #endregion + + #region IClonable + + object ICloneable.Clone() + { + return Clone(); + } + + /// <inheritdoc /> + IDocumentIndentEngine IDocumentIndentEngine.Clone() + { + return Clone(); + } + + public IStateMachineIndentEngine Clone() + { + return new CSharpIndentEngine(this); + } + + #endregion + + #region Methods + + /// <inheritdoc /> + public void Push(char ch) + { + // append this char to the wordbuf if it can form a valid keyword, otherwise check + // if the last sequence of chars form a valid keyword and reset the wordbuf. + if ((wordToken.Length == 0 ? char.IsLetter(ch) : char.IsLetterOrDigit(ch)) || ch == '_') + { + wordToken.Append(ch); + } + else if (wordToken.Length > 0) + { + currentState.CheckKeyword(wordToken.ToString()); + previousKeyword = wordToken.ToString(); + wordToken.Length = 0; + isLineStartBeforeWordToken = false; + } + + var isNewLine = NewLine.IsNewLine(ch); + if (!isNewLine) { + currentState.Push(currentChar = ch); + offset++; + previousNewline = '\0'; + // ignore whitespace and newline chars + var isWhitespace = currentChar == ' ' || currentChar == '\t'; + if (!isWhitespace) + { + previousChar = currentChar; + isLineStart = false; + } + + if (isLineStart) + { + currentIndent.Append(ch); + } + + if (ch == '\t') + { + var indentSize = options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp); + var nextTabStop = (column - 1 + indentSize) / indentSize; + column = 1 + nextTabStop * indentSize; + } + else + { + column++; + } + } else { + if (ch == NewLine.LF && previousNewline == NewLine.CR) { + offset++; + return; + } + currentState.Push(currentChar = newLineChar); + offset++; + + previousNewline = ch; + // there can be more than one chars that determine the EOL, + // the engine uses only one of them defined with newLineChar + if (currentChar != newLineChar) + { + return; + } + currentIndent.Length = 0; + isLineStart = true; + isLineStartBeforeWordToken = true; + column = 1; + line++; + + lineBeganInsideMultiLineComment = IsInsideMultiLineComment; + lineBeganInsideVerbatimString = IsInsideVerbatimString; + } + } + + /// <inheritdoc /> + public void Reset() + { + currentState = new GlobalBodyState(this); + conditionalSymbols.Clear(); + ifDirectiveEvalResults.Clear(); + ifDirectiveIndents.Clear(); + + offset = 0; + line = 1; + column = 1; + isLineStart = true; + currentChar = '\0'; + previousChar = '\0'; + currentIndent.Length = 0; + lineBeganInsideMultiLineComment = false; + lineBeganInsideVerbatimString = false; + } + + /// <inheritdoc /> + public void Update(SourceText sourceText, int offset) + { + if (Offset > offset) + { + Reset(); + } + + while (Offset < offset) + { + Push(sourceText[Offset]); + } + } + + /// <summary> + /// Defines the conditional symbol. + /// </summary> + /// <param name="defineSymbol">The symbol to define.</param> + public void DefineSymbol(string defineSymbol) + { + if (!customConditionalSymbols.Contains(defineSymbol)) + customConditionalSymbols.Add(defineSymbol); + } + + /// <summary> + /// Removes the symbol. + /// </summary> + /// <param name="undefineSymbol">The symbol to undefine.</param> + public void RemoveSymbol(string undefineSymbol) + { + if (customConditionalSymbols.Contains(undefineSymbol)) + customConditionalSymbols.Remove(undefineSymbol); + } + #endregion + + #region IStateMachineIndentEngine + + public bool IsInsidePreprocessorDirective + { + get { return currentState is PreProcessorState; } + } + + public bool IsInsidePreprocessorComment + { + get { return currentState is PreProcessorCommentState; } + } + + public bool IsInsideStringLiteral + { + get { return currentState is StringLiteralState; } + } + + public bool IsInsideVerbatimString + { + get { return currentState is VerbatimStringState; } + } + + public bool IsInsideCharacter + { + get { return currentState is CharacterState; } + } + + public bool IsInsideString + { + get { return IsInsideStringLiteral || IsInsideVerbatimString || IsInsideCharacter; } + } + + public bool IsInsideLineComment + { + get { return currentState is LineCommentState; } + } + + public bool IsInsideMultiLineComment + { + get { return currentState is MultiLineCommentState; } + } + + public bool IsInsideDocLineComment + { + get { return currentState is DocCommentState; } + } + + public bool IsInsideComment + { + get { return IsInsideLineComment || IsInsideMultiLineComment || IsInsideDocLineComment; } + } + + public bool IsInsideOrdinaryComment + { + get { return IsInsideLineComment || IsInsideMultiLineComment; } + } + + public bool IsInsideOrdinaryCommentOrString + { + get { return IsInsideOrdinaryComment || IsInsideString; } + } + + public bool LineBeganInsideVerbatimString + { + get { return lineBeganInsideVerbatimString; } + } + + public bool LineBeganInsideMultiLineComment + { + get { return lineBeganInsideMultiLineComment; } + } + + #endregion + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CacheIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CacheIndentEngine.cs new file mode 100644 index 0000000000..9cb2abb6dc --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/CacheIndentEngine.cs @@ -0,0 +1,623 @@ +// +// CacheIndentEngine.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// Represents a decorator of an IStateMachineIndentEngine instance that provides + /// logic for reseting and updating the engine on text changed events. + /// </summary> + /// <remarks> + /// The decorator is based on periodical caching of the engine's state and + /// delegating all logic behind indentation to the currently active engine. + /// </remarks> + public class CacheIndentEngine : IStateMachineIndentEngine + { + + #region Properties + + IStateMachineIndentEngine currentEngine; + Stack<IStateMachineIndentEngine> cachedEngines = new Stack<IStateMachineIndentEngine>(); + + #endregion + + #region Constructors + + /// <summary> + /// Creates a new CacheIndentEngine instance. + /// </summary> + /// <param name="decoratedEngine"> + /// An instance of <see cref="IStateMachineIndentEngine"/> to which the + /// logic for indentation will be delegated. + /// </param> + /// <param name="cacheRate"> + /// The number of chars between caching. + /// </param> + public CacheIndentEngine(IStateMachineIndentEngine decoratedEngine, int cacheRate = 2000) + { + this.currentEngine = decoratedEngine; + } + + /// <summary> + /// Creates a new CacheIndentEngine instance from the given prototype. + /// </summary> + /// <param name="prototype"> + /// A CacheIndentEngine instance. + /// </param> + public CacheIndentEngine(CacheIndentEngine prototype) + { + this.currentEngine = prototype.currentEngine.Clone(); + } + + #endregion + + #region IDocumentIndentEngine + + /// <inheritdoc /> + public string ThisLineIndent { + get { return currentEngine.ThisLineIndent; } + } + + /// <inheritdoc /> + public string NextLineIndent { + get { return currentEngine.NextLineIndent; } + } + + /// <inheritdoc /> + public string CurrentIndent { + get { return currentEngine.CurrentIndent; } + } + + /// <inheritdoc /> + public bool NeedsReindent { + get { return currentEngine.NeedsReindent; } + } + + /// <inheritdoc /> + public int Offset { + get { return currentEngine.Offset; } + } + +// /// <inheritdoc /> +// public TextLocation Location { +// get { return currentEngine.Location; } +// } + + /// <inheritdoc /> + public bool EnableCustomIndentLevels + { + get { return currentEngine.EnableCustomIndentLevels; } + set { currentEngine.EnableCustomIndentLevels = value; } + } + + /// <inheritdoc /> + public void Push(char ch) + { + currentEngine.Push(ch); + } + + /// <inheritdoc /> + public void Reset() + { + currentEngine.Reset(); + cachedEngines.Clear(); + } + + /// <summary> + /// Resets the engine to offset. Clears all cached engines after the given offset. + /// </summary> + public void ResetEngineToPosition(SourceText sourceText, int offset) + { + // We are already there + if (currentEngine.Offset <= offset) + return; + + bool gotCachedEngine = false; + while (cachedEngines.Count > 0) { + var topEngine = cachedEngines.Peek(); + if (topEngine.Offset <= offset) { + currentEngine = topEngine.Clone(); + gotCachedEngine = true; + break; + } else { + cachedEngines.Pop(); + } + } + if (!gotCachedEngine) + currentEngine.Reset(); + } + + /// <inheritdoc /> + /// <remarks> + /// If the <paramref name="position"/> is negative, the engine will + /// update to: document.TextLength + (offset % document.TextLength+1) + /// Otherwise it will update to: offset % document.TextLength+1 + /// </remarks> + public void Update(SourceText sourceText, int position) + { + const int BUFFER_SIZE = 2000; + + if (currentEngine.Offset == position) { + //positions match, nothing to be done + return; + } else if (currentEngine.Offset > position) { + //moving backwards, so reset from previous saved location + ResetEngineToPosition(sourceText, position); + } + + // get the engine caught up + int nextSave = (cachedEngines.Count == 0) ? BUFFER_SIZE : cachedEngines.Peek().Offset + BUFFER_SIZE; + if (currentEngine.Offset + 1 == position) { + char ch = sourceText[currentEngine.Offset]; + currentEngine.Push(ch); + if (currentEngine.Offset == nextSave) + cachedEngines.Push(currentEngine.Clone()); + } else { + //bulk copy characters in case buffer is unmanaged + //(faster if we reduce managed/unmanaged transitions) + while (currentEngine.Offset < position) { + int endCut = currentEngine.Offset + BUFFER_SIZE; + if (endCut > position) + endCut = position; + string buffer = sourceText.GetSubText(TextSpan.FromBounds(currentEngine.Offset, endCut)).ToString(); + foreach (char ch in buffer) { + currentEngine.Push(ch); + //ConsoleWrite ("pushing character '{0}'", ch); + if (currentEngine.Offset == nextSave) { + cachedEngines.Push(currentEngine.Clone()); + nextSave += BUFFER_SIZE; + } + } + } + } + } + + //public IStateMachineIndentEngine GetEngine(int offset) + //{ + // ResetEngineToPosition(offset); + // return currentEngine; + //} + + #endregion + + #region IClonable + + /// <inheritdoc /> + public IStateMachineIndentEngine Clone() + { + return new CacheIndentEngine(this); + } + + /// <inheritdoc /> + IDocumentIndentEngine IDocumentIndentEngine.Clone() + { + return Clone(); + } + + object ICloneable.Clone() + { + return Clone(); + } + + #endregion + + #region IStateMachineIndentEngine + + public bool IsInsidePreprocessorDirective { + get { return currentEngine.IsInsidePreprocessorDirective; } + } + + public bool IsInsidePreprocessorComment { + get { return currentEngine.IsInsidePreprocessorComment; } + } + + public bool IsInsideStringLiteral { + get { return currentEngine.IsInsideStringLiteral; } + } + + public bool IsInsideVerbatimString { + get { return currentEngine.IsInsideVerbatimString; } + } + + public bool IsInsideCharacter { + get { return currentEngine.IsInsideCharacter; } + } + + public bool IsInsideString { + get { return currentEngine.IsInsideString; } + } + + public bool IsInsideLineComment { + get { return currentEngine.IsInsideLineComment; } + } + + public bool IsInsideMultiLineComment { + get { return currentEngine.IsInsideMultiLineComment; } + } + + public bool IsInsideDocLineComment { + get { return currentEngine.IsInsideDocLineComment; } + } + + public bool IsInsideComment { + get { return currentEngine.IsInsideComment; } + } + + public bool IsInsideOrdinaryComment { + get { return currentEngine.IsInsideOrdinaryComment; } + } + + public bool IsInsideOrdinaryCommentOrString { + get { return currentEngine.IsInsideOrdinaryCommentOrString; } + } + + public bool LineBeganInsideVerbatimString { + get { return currentEngine.LineBeganInsideVerbatimString; } + } + + public bool LineBeganInsideMultiLineComment { + get { return currentEngine.LineBeganInsideMultiLineComment; } + } + + #endregion + + } + /* +/ // <summary> + /// Represents a decorator of an IStateMachineIndentEngine instance that provides + /// logic for reseting and updating the engine on text changed events. + /// </summary> + /// <remarks> + /// The decorator is based on periodical caching of the engine's state and + /// delegating all logic behind indentation to the currently active engine. + /// </remarks> + public class CacheIndentEngine : IStateMachineIndentEngine + { + #region Properties + + /// <summary> + /// Represents the cache interval in number of chars pushed to the engine. + /// </summary> + /// <remarks> + /// When this many new chars are pushed to the engine, the currently active + /// engine gets cloned and added to the end of <see cref="cachedEngines"/>. + /// </remarks> + readonly int cacheRate; + + /// <summary> + /// Determines how much memory to reserve on initialization for the + /// cached engines. + /// </summary> + const int cacheCapacity = 25; + + /// <summary> + /// Currently active engine. + /// </summary> + /// <remarks> + /// Should be equal to the last engine in <see cref="cachedEngines"/>. + /// </remarks> + IStateMachineIndentEngine currentEngine; + + /// <summary> + /// List of cached engines sorted ascending by + /// <see cref="IDocumentIndentEngine.Offset"/>. + /// </summary> + IStateMachineIndentEngine[] cachedEngines; + + /// <summary> + /// The index of the last cached engine in cachedEngines. + /// </summary> + /// <remarks> + /// Should be equal to: currentEngine.Offset / CacheRate + /// </remarks> + int lastCachedEngine; + + #endregion + + #region Constructors + + /// <summary> + /// Creates a new CacheIndentEngine instance. + /// </summary> + /// <param name="decoratedEngine"> + /// An instance of <see cref="IStateMachineIndentEngine"/> to which the + /// logic for indentation will be delegated. + /// </param> + /// <param name="cacheRate"> + /// The number of chars between caching. + /// </param> + public CacheIndentEngine(IStateMachineIndentEngine decoratedEngine, int cacheRate = 2000) + { + this.cachedEngines = new IStateMachineIndentEngine[cacheCapacity]; + + this.cachedEngines[0] = decoratedEngine.Clone(); + this.currentEngine = this.cachedEngines[0].Clone(); + this.cacheRate = cacheRate; + } + + /// <summary> + /// Creates a new CacheIndentEngine instance from the given prototype. + /// </summary> + /// <param name="prototype"> + /// A CacheIndentEngine instance. + /// </param> + public CacheIndentEngine(CacheIndentEngine prototype) + { + this.cachedEngines = new IStateMachineIndentEngine[prototype.cachedEngines.Length]; + Array.Copy(prototype.cachedEngines, this.cachedEngines, prototype.cachedEngines.Length); + + this.lastCachedEngine = prototype.lastCachedEngine; + this.currentEngine = prototype.currentEngine.Clone(); + this.cacheRate = prototype.cacheRate; + } + + #endregion + + #region Methods + + /// <summary> + /// Performs caching of the <see cref="CacheIndentEngine.currentEngine"/>. + /// </summary> + void cache() + { + if (currentEngine.Offset % cacheRate != 0) + { + throw new Exception("The current engine's offset is not divisable with the cacheRate."); + } + + // determine the new current engine from cachedEngines + lastCachedEngine = currentEngine.Offset / cacheRate; + + if (cachedEngines.Length < lastCachedEngine + 1) + { + Array.Resize(ref cachedEngines, lastCachedEngine * 2); + } + + cachedEngines[lastCachedEngine] = currentEngine.Clone(); + } + + #endregion + + #region IDocumentIndentEngine + + /// <inheritdoc /> + public IDocument Document + { + get { return currentEngine.Document; } + } + + /// <inheritdoc /> + public string ThisLineIndent + { + get { return currentEngine.ThisLineIndent; } + } + + /// <inheritdoc /> + public string NextLineIndent + { + get { return currentEngine.NextLineIndent; } + } + + /// <inheritdoc /> + public string CurrentIndent + { + get { return currentEngine.CurrentIndent; } + } + + /// <inheritdoc /> + public bool NeedsReindent + { + get { return currentEngine.NeedsReindent; } + } + + /// <inheritdoc /> + public int Offset + { + get { return currentEngine.Offset; } + } + + /// <inheritdoc /> + public TextLocation Location + { + get { return currentEngine.Location; } + } + + /// <inheritdoc /> + public void Push(char ch) + { + currentEngine.Push(ch); + + if (currentEngine.Offset % cacheRate == 0) + { + cache(); + } + } + + /// <inheritdoc /> + public void Reset() + { + currentEngine = cachedEngines[lastCachedEngine = 0]; + } + + /// <inheritdoc /> + /// <remarks> + /// If the <paramref name="offset"/> is negative, the engine will + /// update to: document.TextLength + (offset % document.TextLength+1) + /// Otherwise it will update to: offset % document.TextLength+1 + /// </remarks> + public void Update(int offset) + { + // map the given offset to the [0, document.TextLength] interval + // using modulo arithmetics + offset %= Document.TextLength + 1; + if (offset < 0) + { + offset += Document.TextLength + 1; + } + + // check if the engine has to be updated to some previous offset + if (currentEngine.Offset > offset) + { + // replace the currentEngine with the first one whose offset + // is less then the given <paramref name="offset"/> + lastCachedEngine = offset / cacheRate; + currentEngine = cachedEngines[lastCachedEngine].Clone(); + } + + // update the engine to the given offset + while (Offset < offset) + { + Push(Document.GetCharAt(Offset)); + } + } + + public IStateMachineIndentEngine GetEngine(int offset) + { + // map the given offset to the [0, document.TextLength] interval + // using modulo arithmetics + offset %= Document.TextLength + 1; + if (offset < 0) + { + offset += Document.TextLength + 1; + } + + // check if the engine has to be updated to some previous offset + if (currentEngine.Offset > offset) + { + // replace the currentEngine with the first one whose offset + // is less then the given <paramref name="offset"/> + lastCachedEngine = offset / cacheRate; + return cachedEngines[lastCachedEngine].Clone(); + } + + return currentEngine; + } + + #endregion + + #region IClonable + + /// <inheritdoc /> + public IStateMachineIndentEngine Clone() + { + return new CacheIndentEngine(this); + } + + /// <inheritdoc /> + IDocumentIndentEngine IDocumentIndentEngine.Clone() + { + return Clone(); + } + + object ICloneable.Clone() + { + return Clone(); + } + + #endregion + + #region IStateMachineIndentEngine + + public bool IsInsidePreprocessorDirective + { + get { return currentEngine.IsInsidePreprocessorDirective; } + } + + public bool IsInsidePreprocessorComment + { + get { return currentEngine.IsInsidePreprocessorComment; } + } + + public bool IsInsideStringLiteral + { + get { return currentEngine.IsInsideStringLiteral; } + } + + public bool IsInsideVerbatimString + { + get { return currentEngine.IsInsideVerbatimString; } + } + + public bool IsInsideCharacter + { + get { return currentEngine.IsInsideCharacter; } + } + + public bool IsInsideString + { + get { return currentEngine.IsInsideString; } + } + + public bool IsInsideLineComment + { + get { return currentEngine.IsInsideLineComment; } + } + + public bool IsInsideMultiLineComment + { + get { return currentEngine.IsInsideMultiLineComment; } + } + + public bool IsInsideDocLineComment + { + get { return currentEngine.IsInsideDocLineComment; } + } + + public bool IsInsideComment + { + get { return currentEngine.IsInsideComment; } + } + + public bool IsInsideOrdinaryComment + { + get { return currentEngine.IsInsideOrdinaryComment; } + } + + public bool IsInsideOrdinaryCommentOrString + { + get { return currentEngine.IsInsideOrdinaryCommentOrString; } + } + + public bool LineBeganInsideVerbatimString + { + get { return currentEngine.LineBeganInsideVerbatimString; } + } + + public bool LineBeganInsideMultiLineComment + { + get { return currentEngine.LineBeganInsideMultiLineComment; } + } + + #endregion + } + + */ +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IDocumentIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IDocumentIndentEngine.cs new file mode 100644 index 0000000000..793ff67397 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IDocumentIndentEngine.cs @@ -0,0 +1,99 @@ +// +// IDocumentIndentEngine.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// The base interface for all indent engines. + /// </summary> + public interface IDocumentIndentEngine : ICloneable + { + /// <summary> + /// The indentation string of the current line. + /// </summary> + string ThisLineIndent { get; } + + /// <summary> + /// The indentation string of the next line. + /// </summary> + string NextLineIndent { get; } + + /// <summary> + /// The indent string on the beginning of the current line. + /// </summary> + string CurrentIndent { get; } + + /// <summary> + /// True if the current line needs to be reindented. + /// </summary> + bool NeedsReindent { get; } + + /// <summary> + /// The current offset of the engine. + /// </summary> + int Offset { get; } + + /// <summary> + /// If this is true, the engine should try to adjust its indent + /// levels to manual user's corrections, even if they are wrong. + /// </summary> + bool EnableCustomIndentLevels { get; set; } + + /// <summary> + /// Pushes a new char into the engine which calculates the new + /// indentation levels. + /// </summary> + /// <param name="ch"> + /// A new character. + /// </param> + void Push(char ch); + + /// <summary> + /// Resets the engine. + /// </summary> + void Reset(); + + /// <summary> + /// Updates the engine to the given offset. + /// </summary> + /// <param name="offset"> + /// Valid offset in <see cref="Document"/>. + /// </param> + void Update(SourceText sourceText, int offset); + + /// <summary> + /// Clones the engine and preserves the current state. + /// </summary> + /// <returns> + /// An indentical clone which can operate without interference + /// with this engine. + /// </returns> + new IDocumentIndentEngine Clone(); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IStateMachineIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IStateMachineIndentEngine.cs new file mode 100644 index 0000000000..fa6a456f41 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IStateMachineIndentEngine.cs @@ -0,0 +1,60 @@ +// +// IStateMachineIndentEngine.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +namespace ICSharpCode.NRefactory6.CSharp +{ + public interface IStateMachineIndentEngine : IDocumentIndentEngine + { + bool IsInsidePreprocessorDirective { get; } + + bool IsInsidePreprocessorComment { get; } + + bool IsInsideStringLiteral { get; } + + bool IsInsideVerbatimString { get; } + + bool IsInsideCharacter { get; } + + bool IsInsideString { get; } + + bool IsInsideLineComment { get; } + + bool IsInsideMultiLineComment { get; } + + bool IsInsideDocLineComment { get; } + + bool IsInsideComment { get; } + + bool IsInsideOrdinaryComment { get; } + + bool IsInsideOrdinaryCommentOrString { get; } + + bool LineBeganInsideVerbatimString { get; } + + bool LineBeganInsideMultiLineComment { get; } + + new IStateMachineIndentEngine Clone(); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/ITextPasteHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/ITextPasteHandler.cs new file mode 100644 index 0000000000..c93541e68c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/ITextPasteHandler.cs @@ -0,0 +1,52 @@ +// ITextPasteHandler.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// The text paste handler can do formattings to a text that is about to be pasted + /// into the text document. + /// </summary> + public interface ITextPasteHandler + { + /// <summary> + /// Formats plain text that is inserted at a specified offset. + /// </summary> + /// <returns> + /// The text that will get inserted at that position. + /// </returns> + /// <param name="offset">The offset where the text will be inserted.</param> + /// <param name="text">The text to be inserted.</param> + /// <param name="copyData">Additional data in case the text was copied from a Mono.TextEditor.</param> + string FormatPlainText(SourceText sourceText, int offset, string text, byte[] copyData); + + /// <summary> + /// Gets the copy data for a specific segment inside the document. This can contain additional information. + /// </summary> + /// <param name="segment">The text segment that is about to be copied.</param> + byte[] GetCopyData(SourceText sourceText, TextSpan segment); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IndentState.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IndentState.cs new file mode 100644 index 0000000000..d1f864dccf --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/IndentState.cs @@ -0,0 +1,2018 @@ +// +// IndentState.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + #region IndentState + + /// <summary> + /// The base class for all indentation states. + /// Each state defines the logic for indentation based on chars that + /// are pushed to it. + /// </summary> + public abstract class IndentState : ICloneable + { + #region Properties + + /// <summary> + /// The indentation engine using this state. + /// </summary> + public CSharpIndentEngine Engine; + + /// <summary> + /// The parent state. + /// This state can use the indentation levels of its parent. + /// When this state exits, the engine returns to the parent. + /// </summary> + public IndentState Parent; + + /// <summary> + /// The indentation of the current line. + /// This is set when the state is created and will be changed to + /// <see cref="NextLineIndent"/> when the <see cref="CSharpIndentEngine.newLineChar"/> + /// is pushed. + /// </summary> + public Indent ThisLineIndent; + + /// <summary> + /// The indentation of the next line. + /// This is set when the state is created and can change depending + /// on the pushed chars. + /// </summary> + public Indent NextLineIndent; + + #endregion + + #region Constructors + + protected IndentState() + { + } + + /// <summary> + /// Creates a new indentation state that is a copy of the given + /// prototype. + /// </summary> + /// <param name="prototype"> + /// The prototype state. + /// </param> + /// <param name="engine"> + /// The engine of the new state. + /// </param> + protected IndentState(IndentState prototype, CSharpIndentEngine engine) + { + Engine = engine; + Parent = prototype.Parent != null ? prototype.Parent.Clone(engine) : null; + + ThisLineIndent = prototype.ThisLineIndent.Clone(); + NextLineIndent = prototype.NextLineIndent.Clone(); + } + + #endregion + + #region IClonable + + object ICloneable.Clone() + { + return Clone(Engine); + } + + public abstract IndentState Clone(CSharpIndentEngine engine); + + #endregion + + #region Methods + + internal void Initialize (CSharpIndentEngine engine, IndentState parent = null) + { + Parent = parent; + Engine = engine; + + InitializeState(); + } + + /// <summary> + /// Initializes the state: + /// - sets the default indentation levels. + /// </summary> + /// <remarks> + /// Each state can override this method if it needs a different + /// logic for setting up the default indentations. + /// </remarks> + public virtual void InitializeState() + { + ThisLineIndent = new Indent(Engine.options); + NextLineIndent = ThisLineIndent.Clone(); + } + + /// <summary> + /// Actions performed when this state exits. + /// </summary> + public virtual void OnExit() + { + if (Parent != null) + { + // if a state exits on the newline character, it has to push + // it back to its parent (and so on recursively if the parent + // state also exits). Otherwise, the parent state wouldn't + // know that the engine isn't on the same line anymore. + if (Engine.currentChar == Engine.newLineChar) + { + Parent.Push(Engine.newLineChar); + } + + // when a state exits the engine stays on the same line and this + // state has to override the Parent.ThisLineIndent. + Parent.ThisLineIndent = ThisLineIndent.Clone(); + } + } + + /// <summary> + /// Changes the current state of the <see cref="CSharpIndentEngine"/> using the current + /// state as the parent for the new one. + /// </summary> + /// <typeparam name="T"> + /// The type of the new state. Must be assignable from <see cref="IndentState"/>. + /// </typeparam> + public void ChangeState<T>() + where T : IndentState, new () + { + var t = new T(); + t.Initialize(Engine, Engine.currentState); + Engine.currentState = t; + } + + /// <summary> + /// Exits this state by setting the current state of the + /// <see cref="CSharpIndentEngine"/> to this state's parent. + /// </summary> + public void ExitState() + { + OnExit(); + Engine.currentState = Engine.currentState.Parent ?? new GlobalBodyState(Engine); + } + + /// <summary> + /// Common logic behind the push method. + /// Each state can override this method and implement its own logic. + /// </summary> + /// <param name="ch"> + /// The current character that's being pushed. + /// </param> + public virtual void Push(char ch) + { + // replace ThisLineIndent with NextLineIndent if the newLineChar is pushed + if (ch == Engine.newLineChar) + { + var delta = Engine.options.GetOption(FormattingOptions.IndentationSize, LanguageNames.CSharp); + while (NextLineIndent.CurIndent - ThisLineIndent.CurIndent > delta && + NextLineIndent.PopIf(IndentType.Continuation)); + ThisLineIndent = NextLineIndent.Clone(); + + } + } + + /// <summary> + /// When derived, checks if the given sequence of chars form + /// a valid keyword or variable name, depending on the state. + /// </summary> + /// <param name="keyword"> + /// A possible keyword. + /// </param> + public virtual void CheckKeyword(string keyword) + { } + + /// <summary> + /// When derived, checks if the given sequence of chars form + /// a valid keyword or variable name, depending on the state. + /// </summary> + /// <param name="keyword"> + /// A possible keyword. + /// </param> + /// <remarks> + /// This method should be called from <see cref="Push(char)"/>. + /// It is left to derived classes to call this method because of + /// performance issues. + /// </remarks> + public virtual void CheckKeywordOnPush(string keyword) + { } + + #endregion + } + + #endregion + + #region Null state + + /// <summary> + /// Null state. + /// </summary> + /// <remarks> + /// Doesn't define any transitions to new states. + /// </remarks> + public class NullState : IndentState + { + public NullState() + { } + + public NullState(NullState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override void Push(char ch) + { } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new NullState(this, engine); + } + } + + #endregion + + #region Brackets body states + + #region Brackets body base + + /// <summary> + /// The base for all brackets body states. + /// </summary> + /// <remarks> + /// Represents a block of code between a pair of brackets. + /// </remarks> + public abstract class BracketsBodyBaseState : IndentState + { + + /// <summary> + /// When derived in a concrete bracket body state, represents + /// the closed bracket character pair. + /// </summary> + public abstract char ClosedBracket { get; } + + protected BracketsBodyBaseState() + { } + + protected BracketsBodyBaseState(BracketsBodyBaseState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override void Push(char ch) + { + base.Push(ch); + switch (ch) { + case '#': + if (Engine.isLineStart) + ChangeState<PreProcessorState>(); + break; + case '/': + if (Engine.previousChar == '/') + ChangeState<LineCommentState>(); + break; + case '*': + if (Engine.previousChar == '/') + ChangeState<MultiLineCommentState>(); + break; + case '"': + if (Engine.previousChar == '@') + { + ChangeState<VerbatimStringState>(); + } + else + { + ChangeState<StringLiteralState>(); + } + break; + case '\'': + ChangeState<CharacterState>(); + break; + case '{': + ChangeState<BracesBodyState>(); + break; + case '(': + ChangeState<ParenthesesBodyState>(); + break; + case '[': + ChangeState<SquareBracketsBodyState>(); + break; + default: + if (ch == ClosedBracket) + ExitState(); + break; + } + } + } + + #endregion + + #region Braces body state + + /// <summary> + /// Braces body state. + /// </summary> + /// <remarks> + /// Represents a block of code between { and }. + /// </remarks> + public class BracesBodyState : BracketsBodyBaseState + { + /// <summary> + /// Type of the current block body. + /// </summary> + public Body CurrentBody; + + /// <summary> + /// Type of the next block body. + /// Same as <see cref="CurrentBody"/> if none of the + /// <see cref="Body"/> keywords have been read. + /// </summary> + public Body NextBody; + + /// <summary> + /// Type of the current statement. + /// </summary> + public Statement CurrentStatement + { + get + { + return currentStatement; + } + set + { + // clear NestedIfStatementLevels if this statement breaks the sequence + if (currentStatement == Statement.None && value != Statement.Else) + { + NestedIfStatementLevels.Clear(); + } + + currentStatement = value; + } + } + Statement currentStatement; + + /// <summary> + /// Contains indent levels of nested if statements. + /// </summary> + internal CloneableStack<Indent> NestedIfStatementLevels = new CloneableStack<Indent>(); + + /// <summary> + /// Contains the indent level of the last statement or body keyword. + /// </summary> + public Indent LastBlockIndent; + + /// <summary> + /// True if the engine is on the right side of the equal operator '='. + /// </summary> + public bool IsRightHandExpression; + + /// <summary> + /// True if the '=' char has been pushed and it's not + /// a part of a relational operator (>=, <=, !=, ==). + /// </summary> + public bool IsEqualCharPushed; + + /// <summary> + /// The indentation of the previous line. + /// </summary> + public int PreviousLineIndent; + + /// <summary> + /// True if the dot member (e.g. method invocation) indentation has + /// been handled in the current statement. + /// </summary> + public bool IsMemberReferenceDotHandled; + + public override char ClosedBracket + { + get { return '}'; } + } + + public BracesBodyState() + { + } + + public BracesBodyState(BracesBodyState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + CurrentBody = prototype.CurrentBody; + NextBody = prototype.NextBody; + CurrentStatement = prototype.CurrentStatement; + NestedIfStatementLevels = prototype.NestedIfStatementLevels.Clone(); + IsRightHandExpression = prototype.IsRightHandExpression; + IsEqualCharPushed = prototype.IsEqualCharPushed; + IsMemberReferenceDotHandled = prototype.IsMemberReferenceDotHandled; + LastBlockIndent = prototype.LastBlockIndent; + PreviousLineIndent = prototype.PreviousLineIndent; + } + + public override void Push(char ch) + { + // handle IsRightHandExpression property + if (IsEqualCharPushed) + { + if (IsRightHandExpression) + { + if (ch == Engine.newLineChar) + { + NextLineIndent.RemoveAlignment(); + NextLineIndent.Push(IndentType.Continuation); + } + } + // ignore "==" and "=>" operators + else if (ch != '=' && ch != '>') + { + IsRightHandExpression = true; + + if (ch == Engine.newLineChar) + { + NextLineIndent.Push(IndentType.Continuation); + } + else + { + NextLineIndent.SetAlignment(Engine.column - NextLineIndent.CurIndent); + } + } + + IsEqualCharPushed = ch == ' ' || ch == '\t'; + } + + if (ch == ';' || (ch == ',' && IsRightHandExpression)) + { + OnStatementExit(); + } + else if (ch == '=' && !(Engine.previousChar == '=' || Engine.previousChar == '<' || Engine.previousChar == '>' || Engine.previousChar == '!')) + { + IsEqualCharPushed = true; + } + else if (ch == '.' && !IsMemberReferenceDotHandled) + { + // OPTION: CSharpFormattingOptions.AlignToMemberReferenceDot + if (true /*Engine.options.AlignToMemberReferenceDot*/ && !Engine.isLineStart) + { + IsMemberReferenceDotHandled = true; + NextLineIndent.RemoveAlignment(); + NextLineIndent.SetAlignment(Engine.column - NextLineIndent.CurIndent - 1, true); + } + else if (Engine.isLineStart) + { + IsMemberReferenceDotHandled = true; + + ThisLineIndent.RemoveAlignment(); + while (ThisLineIndent.CurIndent > PreviousLineIndent && + ThisLineIndent.PopIf(IndentType.Continuation)) ; + ThisLineIndent.Push(IndentType.Continuation); + NextLineIndent = ThisLineIndent.Clone(); + } + } + else if (ch == ':' && Engine.isLineStart && !IsRightHandExpression) + { + // try to capture ': base(...)', ': this(...)' and inherit statements when they are on a new line + ThisLineIndent.Push(IndentType.Continuation); + } + else if (ch == Engine.newLineChar) + { + PreviousLineIndent = ThisLineIndent.CurIndent; + } + + if (Engine.wordToken.ToString() == "else") + { + CheckKeywordOnPush("else"); + } + + base.Push(ch); + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = Parent.NextLineIndent.Clone(); + + // OPTION: IDocumentIndentEngine.EnableCustomIndentLevels + var parent = Parent as BracesBodyState; + if (parent == null || parent.LastBlockIndent == null || !Engine.EnableCustomIndentLevels) + { + NextLineIndent.RemoveAlignment(); + NextLineIndent.PopIf(IndentType.Continuation); + } + else + { + NextLineIndent = parent.LastBlockIndent.Clone(); + } + + if (Engine.isLineStart) + { + NextLineIndent.RemoveAlignment(); + NextLineIndent.PopIf(IndentType.Continuation); + + ThisLineIndent = NextLineIndent.Clone(); + } + + CurrentBody = extractBody(Parent); + NextBody = Body.None; + CurrentStatement = Statement.None; + + AddIndentation(CurrentBody); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new BracesBodyState(this, engine); + } + + public override void OnExit() + { + if (Parent is BracesBodyState) + { + ((BracesBodyState)Parent).OnStatementExit(); + } + + if (Engine.isLineStart) + { + ThisLineIndent.RemoveAlignment(); + ThisLineIndent.PopTry(); + /*BraceStyle style; + if (TryGetBraceStyle(this.CurrentBody, out style)) { + if (style == BraceStyle.NextLineShifted || + style == BraceStyle.NextLineShifted2|| + style == BraceStyle.BannerStyle) { + ThisLineIndent.Push(IndentType.Block); + } + }*/ + } + + base.OnExit(); + } + + /// <summary> + /// Actions performed when the current statement exits. + /// </summary> + public virtual void OnStatementExit() + { + IsRightHandExpression = false; + IsMemberReferenceDotHandled = false; + + NextLineIndent.RemoveAlignment(); + NextLineIndent.PopWhile(IndentType.Continuation); + + CurrentStatement = Statement.None; + NextBody = Body.None; + LastBlockIndent = null; + } + + #region Helpers + + /// <summary> + /// Types of braces bodies. + /// </summary> + public enum Body + { + None, + Namespace, + Class, + Struct, + Interface, + Enum, + Switch, + Case, + Try, + Catch, + Finally + } + + /// <summary> + /// Types of statements. + /// </summary> + public enum Statement + { + None, + If, + Else, + Do, + While, + For, + Foreach, + Lock, + Using, + Return + } + + static readonly Dictionary<string, Body> bodies = new Dictionary<string, Body> + { + { "namespace", Body.Namespace }, + { "class", Body.Class }, + { "struct", Body.Struct }, + { "interface", Body.Interface }, + { "enum", Body.Enum }, + { "switch", Body.Switch }, + { "try", Body.Try }, + { "catch", Body.Catch }, + { "finally", Body.Finally }, + }; + + static readonly Dictionary<string, Statement> statements = new Dictionary<string, Statement> + { + { "if", Statement.If }, + // { "else", Statement.Else }, // should be handled in CheckKeywordAtPush + { "do", Statement.Do }, + { "while", Statement.While }, + { "for", Statement.For }, + { "foreach", Statement.Foreach }, + { "lock", Statement.Lock }, + { "using", Statement.Using }, + { "return", Statement.Return }, + }; + + static readonly HashSet<string> blocks = new HashSet<string> + { + "namespace", + "class", + "struct", + "interface", + "enum", + "switch", + "try", + "catch", + "finally", + "if", + "else", + "do", + "while", + "for", + "foreach", + "lock", + "using", + }; + + readonly string[] caseDefaultKeywords = { + "case", + "default" + }; + + readonly string[] classStructKeywords = { + "class", + "struct" + }; + + /// <summary> + /// Checks if the given string is a keyword and sets the + /// <see cref="NextBody"/> and the <see cref="CurrentStatement"/> + /// variables appropriately. + /// </summary> + /// <param name="keyword"> + /// A possible keyword. + /// </param> + /// <remarks> + /// This method is called from <see cref="Push(char)"/> + /// </remarks> + public override void CheckKeywordOnPush(string keyword) + { + if (keyword == "else") + { + CurrentStatement = Statement.Else; + + // OPTION: CSharpFormattingOptions.AlignElseInIfStatements + if (true && NestedIfStatementLevels.Count > 0) + { + ThisLineIndent = NestedIfStatementLevels.Pop().Clone(); + NextLineIndent = ThisLineIndent.Clone(); + } + + NextLineIndent.Push(IndentType.Continuation); + } + + if (blocks.Contains(keyword) && Engine.NeedsReindent) + { + LastBlockIndent = Indent.ConvertFrom(Engine.CurrentIndent, ThisLineIndent, Engine.options); + } + } + + /// <summary> + /// Checks if the given string is a keyword and sets the + /// <see cref="NextBody"/> and the <see cref="CurrentStatement"/> + /// variables appropriately. + /// </summary> + /// <param name="keyword"> + /// A possible keyword. + /// </param> + public override void CheckKeyword(string keyword) + { + if (bodies.ContainsKey(keyword)) + { + var isKeywordTemplateConstraint = + classStructKeywords.Contains(keyword) && + (NextBody == Body.Class || NextBody == Body.Struct || NextBody == Body.Interface); + + if (!isKeywordTemplateConstraint) + { + NextBody = bodies[keyword]; + } + } + else if (caseDefaultKeywords.Contains(keyword) && CurrentBody == Body.Switch && Engine.isLineStartBeforeWordToken) + { + ChangeState<SwitchCaseState>(); + } + else if (keyword == "where" && Engine.isLineStartBeforeWordToken) + { + // try to capture where (generic type constraint) + ThisLineIndent.Push(IndentType.Continuation); + } + else if (statements.ContainsKey(keyword)) + { + Statement previousStatement = CurrentStatement; + CurrentStatement = statements[keyword]; + + // return if this is a using declaration or alias + if (CurrentStatement == Statement.Using && + (this is GlobalBodyState || CurrentBody == Body.Namespace)) + { + return; + } + // OPTION: CSharpFormattingOptions.AlignEmbeddedIfStatements + if (true /*Engine.options.AlignEmbeddedStatements*/ && + previousStatement == Statement.If && + CurrentStatement == Statement.If) + { + ThisLineIndent.PopIf(IndentType.Continuation); + NextLineIndent.PopIf(IndentType.Continuation); + } + + // OPTION: CSharpFormattingOptions.AlignEmbeddedStatements + if (true /*Engine.options.AlignEmbeddedStatements*/ && + previousStatement == Statement.Lock && + CurrentStatement == Statement.Lock) + { + ThisLineIndent.PopIf(IndentType.Continuation); + NextLineIndent.PopIf(IndentType.Continuation); + } + + // OPTION: CSharpFormattingOptions.AlignEmbeddedUsingStatements + if (true /*Engine.options.AlignEmbeddedStatements*/ && + previousStatement == Statement.Using && + CurrentStatement == Statement.Using) + { + ThisLineIndent.PopIf(IndentType.Continuation); + NextLineIndent.PopIf(IndentType.Continuation); + } + + // only add continuation for 'else' in 'else if' statement. + if (!(CurrentStatement == Statement.If && previousStatement == Statement.Else && !Engine.isLineStartBeforeWordToken)) + { + NextLineIndent.Push(IndentType.Continuation); + } + + if (CurrentStatement == Statement.If) + { + NestedIfStatementLevels.Push(ThisLineIndent); + } + } + + if (blocks.Contains(keyword) && Engine.NeedsReindent) + { + LastBlockIndent = Indent.ConvertFrom(Engine.CurrentIndent, ThisLineIndent, Engine.options); + } + } + +// /// <summary> +// /// Pushes a new level of indentation depending on the given +// /// <paramref name="braceStyle"/>. +// /// </summary> +// void AddIndentation(BraceStyle braceStyle) +// { +// switch (braceStyle) +// { +// case BraceStyle.NextLineShifted: +// ThisLineIndent.Push(IndentType.Block); +// NextLineIndent.Push(IndentType.Block); +// break; +// case BraceStyle.DoNotChange: +// case BraceStyle.EndOfLine: +// case BraceStyle.EndOfLineWithoutSpace: +// case BraceStyle.NextLine: +// case BraceStyle.BannerStyle: +// NextLineIndent.Push(IndentType.Block); +// break; +// case BraceStyle.NextLineShifted2: +// ThisLineIndent.Push(IndentType.Block); +// NextLineIndent.Push(IndentType.DoubleBlock); +// break; +// } +// } + +// bool TryGetBraceStyle (Body body, out BraceStyle style) +// { +// style = BraceStyle.DoNotChange; +// switch (body) +// { +// case Body.None: +// if (!Engine.options.IndentBlocks) +// return false; +// style = Engine.options.StatementBraceStyle; +// return true; +// case Body.Namespace: +// if (!Engine.options.IndentNamespaceBody) +// return false; +// style = Engine.options.NamespaceBraceStyle; +// return true; +// case Body.Class: +// if (!Engine.options.IndentClassBody) +// return false; +// style = Engine.options.ClassBraceStyle; +// return true; +// case Body.Struct: +// if (!Engine.options.IndentStructBody) +// return false; +// style = Engine.options.StructBraceStyle; +// return true; +// case Body.Interface: +// if (!Engine.options.IndentInterfaceBody) +// return false; +// style = Engine.options.InterfaceBraceStyle; +// return true; +// case Body.Enum: +// if (!Engine.options.IndentEnumBody) +// return false; +// style = Engine.options.EnumBraceStyle; +// return true; +// case Body.Switch: +// if (!Engine.options.IndentSwitchBody) +// return false; +// style = Engine.options.StatementBraceStyle; +// return true; +// case Body.Try: +// case Body.Catch: +// case Body.Finally: +// style = Engine.options.StatementBraceStyle; +// return true; +// } +// return false; +// } + + /// <summary> + /// Pushes a new level of indentation depending on the given + /// <paramref name="body"/>. + /// </summary> + void AddIndentation(Body body) + { + NextLineIndent.Push(IndentType.Block); + +// BraceStyle style; +// if (TryGetBraceStyle (body, out style)) { +// AddIndentation(style); +// } else { +// NextLineIndent.Push(IndentType.Empty); +// } + } + + /// <summary> + /// Extracts the <see cref="CurrentBody"/> from the given state. + /// </summary> + /// <returns> + /// The correct <see cref="Body"/> type for this state. + /// </returns> + static Body extractBody(IndentState state) + { + if (state != null && state is BracesBodyState) + { + return ((BracesBodyState)state).NextBody; + } + + return Body.None; + } + + #endregion + } + + #endregion + + #region Global body state + + /// <summary> + /// Global body state. + /// </summary> + /// <remarks> + /// Represents the global space of the program. + /// </remarks> + public class GlobalBodyState : BracesBodyState + { + public override char ClosedBracket + { + get { return '\0'; } + } + + public GlobalBodyState() + { } + + + public GlobalBodyState(CSharpIndentEngine engine) + { + Initialize (engine, null); + } + + public GlobalBodyState(GlobalBodyState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new GlobalBodyState(this, engine); + } + + public override void InitializeState() + { + ThisLineIndent = new Indent(Engine.options); + NextLineIndent = ThisLineIndent.Clone(); + } + } + + #endregion + + #region Switch-case body state + + /// <summary> + /// Switch-case statement state. + /// </summary> + /// <remarks> + /// Represents the block of code in one switch case (including default). + /// </remarks> + public class SwitchCaseState : BracesBodyState + { + public SwitchCaseState() + { } + + public SwitchCaseState(SwitchCaseState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override void Push(char ch) + { + // on ClosedBracket both this state (a case or a default statement) + // and also the whole switch block (handled in the base class) must exit. + if (ch == ClosedBracket) + { + ExitState(); + if (Parent is BracesBodyState) + Parent.OnExit(); + } + + base.Push(ch); + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = ThisLineIndent.Clone(); + + // remove all continuations and extra spaces + ThisLineIndent.RemoveAlignment(); + ThisLineIndent.PopWhile(IndentType.Continuation); + + NextLineIndent.RemoveAlignment(); + NextLineIndent.PopWhile(IndentType.Continuation); + + + if (Engine.options.GetOption(CSharpFormattingOptions.IndentSwitchCaseSection)) + { + NextLineIndent.Push(IndentType.Block); + } + else + { + NextLineIndent.Push(IndentType.Empty); + } + } + + static readonly string[] caseDefaultKeywords = { + "case", + "default" + }; + + static readonly string[] breakContinueReturnGotoKeywords = { + "break", + "continue", + "return", + "goto" + }; + + public override void CheckKeyword(string keyword) + { + if (caseDefaultKeywords.Contains(keyword) && Engine.isLineStartBeforeWordToken) + { + ExitState(); + ChangeState<SwitchCaseState>(); + } + else if (breakContinueReturnGotoKeywords.Contains(keyword) && Engine.isLineStartBeforeWordToken) + { + // OPTION: Engine.formattingOptions.IndentBreakStatements + if (true/*!Engine.options.IndentBreakStatements*/) + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + } + } + + base.CheckKeyword(keyword); + } + + + public override void OnExit() + { + //Parent.OnExit(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new SwitchCaseState(this, engine); + } + } + + #endregion + + #region Parentheses body state + + /// <summary> + /// Parentheses body state. + /// </summary> + /// <remarks> + /// Represents a block of code between ( and ). + /// </remarks> + public class ParenthesesBodyState : BracketsBodyBaseState + { + /// <summary> + /// True if any char has been pushed. + /// </summary> + public bool IsSomethingPushed; + + public override char ClosedBracket + { + get { return ')'; } + } + + public ParenthesesBodyState() + { } + + public ParenthesesBodyState(ParenthesesBodyState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsSomethingPushed = prototype.IsSomethingPushed; + } + + public override void Push(char ch) + { + if (ch == Engine.newLineChar) + { + if (!Engine.options.GetOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods)) { + if (NextLineIndent.PopIf(IndentType.Continuation)) { + NextLineIndent.Push(IndentType.Block); + } + } + } + else if (!IsSomethingPushed) + { + // OPTION: CSharpFormattingOptions.AlignToFirstMethodCallArgument + if (true /* Engine.options.AlignToFirstMethodCallArgument*/) + { + NextLineIndent.PopTry(); + // align the next line at the beginning of the open bracket + NextLineIndent.ExtraSpaces = Math.Max(0, Engine.column - NextLineIndent.CurIndent - 1); + } + } + + base.Push(ch); + IsSomethingPushed = true; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = ThisLineIndent.Clone(); + NextLineIndent.Push(IndentType.Continuation); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new ParenthesesBodyState(this, engine); + } + + public override void OnExit() + { + if (Engine.isLineStart) + { + if (ThisLineIndent.ExtraSpaces > 0) + { + ThisLineIndent.ExtraSpaces--; + } + else + { + ThisLineIndent.PopTry(); + } + } + + base.OnExit(); + } + } + + #endregion + + #region Square brackets body state + + /// <summary> + /// Square brackets body state. + /// </summary> + /// <remarks> + /// Represents a block of code between [ and ]. + /// </remarks> + public class SquareBracketsBodyState : BracketsBodyBaseState + { + /// <summary> + /// True if any char has been pushed. + /// </summary> + public bool IsSomethingPushed; + + public override char ClosedBracket + { + get { return ']'; } + } + + public SquareBracketsBodyState() + { } + + public SquareBracketsBodyState(SquareBracketsBodyState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsSomethingPushed = prototype.IsSomethingPushed; + } + + public override void Push(char ch) + { + if (ch == Engine.newLineChar) + { + if (NextLineIndent.PopIf(IndentType.Continuation)) + { + NextLineIndent.Push(IndentType.Block); + } + } + else if (!IsSomethingPushed) + { + // OPTION: CSharpFormattingOptions.AlignToFirstIndexerArgument + if (true /*Engine.options.AlignToFirstIndexerArgument*/) + { + NextLineIndent.PopTry(); + // align the next line at the beginning of the open bracket + NextLineIndent.ExtraSpaces = Math.Max(0, Engine.column - NextLineIndent.CurIndent - 1); + } + } + + base.Push(ch); + IsSomethingPushed = true; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = ThisLineIndent.Clone(); + NextLineIndent.Push(IndentType.Continuation); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new SquareBracketsBodyState(this, engine); + } + + public override void OnExit() + { + if (Engine.isLineStart) + { + if (ThisLineIndent.ExtraSpaces > 0) + { + ThisLineIndent.ExtraSpaces--; + } + else + { + ThisLineIndent.PopTry(); + } + } + + base.OnExit(); + } + } + + #endregion + + #endregion + + #region PreProcessor state + + /// <summary> + /// PreProcessor directive state. + /// </summary> + /// <remarks> + /// Activated when the '#' char is pushed and the + /// <see cref="CSharpIndentEngine.isLineStart"/> is true. + /// </remarks> + public class PreProcessorState : IndentState + { + /// <summary> + /// The type of the preprocessor directive. + /// </summary> + public PreProcessorDirective DirectiveType; + + /// <summary> + /// If <see cref="DirectiveType"/> is set (not equal to 'None'), this + /// stores the expression of the directive. + /// </summary> + public StringBuilder DirectiveStatement; + + public PreProcessorState() + { + DirectiveType = PreProcessorDirective.None; + DirectiveStatement = new StringBuilder(); + } + + public PreProcessorState(PreProcessorState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + DirectiveType = prototype.DirectiveType; + DirectiveStatement = new StringBuilder(prototype.DirectiveStatement.ToString()); + } + + public override void Push(char ch) + { + // HACK: if this change would be left for the CheckKeyword method, we will lose + // it if the next pushed char is newLineChar since ThisLineIndent will be + // immediately replaced with NextLineIndent. As this most likely will + // happen, we check for "endregion" on every push. + if (Engine.wordToken.ToString() == "endregion") + { + CheckKeywordOnPush("endregion"); + } + + base.Push(ch); + + if (DirectiveType != PreProcessorDirective.None) + { + DirectiveStatement.Append(ch); + } + + if (ch == Engine.newLineChar) + { + ExitState(); + switch (DirectiveType) + { + case PreProcessorDirective.If: + Engine.ifDirectiveEvalResults.Push(eval(DirectiveStatement.ToString())); + if (Engine.ifDirectiveEvalResults.Peek()) + { + // the if/elif directive is true -> continue with the previous state + } + else + { + // the if/elif directive is false -> change to a state that will + // ignore any chars until #endif or #elif + ChangeState<PreProcessorCommentState>(); + } + break; + case PreProcessorDirective.Elif: + if (Engine.ifDirectiveEvalResults.Count > 0) + { + if (!Engine.ifDirectiveEvalResults.Peek()) + { + ExitState(); + Engine.ifDirectiveEvalResults.Pop(); + goto case PreProcessorDirective.If; + } + } + // previous if was true -> comment + ChangeState<PreProcessorCommentState>(); + break; + case PreProcessorDirective.Else: + if (Engine.ifDirectiveEvalResults.Count > 0 && Engine.ifDirectiveEvalResults.Peek()) + { + // some if/elif directive was true -> change to a state that will + // ignore any chars until #endif + ChangeState<PreProcessorCommentState>(); + } + else + { + // none if/elif directives were true -> exit comment state. + if (Engine.currentState is PreProcessorCommentState) + ExitState(); + } + break; + case PreProcessorDirective.Define: + var defineSymbol = DirectiveStatement.ToString().Trim(); + if (!Engine.conditionalSymbols.Contains(defineSymbol)) + { + Engine.conditionalSymbols.Add(defineSymbol); + } + break; + case PreProcessorDirective.Undef: + var undefineSymbol = DirectiveStatement.ToString().Trim(); + if (Engine.conditionalSymbols.Contains(undefineSymbol)) + { + Engine.conditionalSymbols.Remove(undefineSymbol); + } + break; + case PreProcessorDirective.Endif: + // marks the end of this block + if (Engine.currentState is PreProcessorCommentState) + ExitState(); + Engine.ifDirectiveEvalResults.Pop(); + Engine.ifDirectiveIndents.Pop(); + break; + case PreProcessorDirective.Region: + case PreProcessorDirective.Pragma: + case PreProcessorDirective.Warning: + case PreProcessorDirective.Error: + case PreProcessorDirective.Line: + // continue with the previous state + break; + } + } + } + + public override void InitializeState() + { + // OPTION: IndentPreprocessorDirectives + if (true /*Engine.options.IndentPreprocessorDirectives*/) + { + if (Engine.ifDirectiveIndents.Count > 0) + { + ThisLineIndent = Engine.ifDirectiveIndents.Peek().Clone(); + } + else + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + } + } +// else +// { +// ThisLineIndent = new Indent(Engine.options); +// } + + NextLineIndent = Parent.NextLineIndent.Clone(); + } + + static readonly Dictionary<string, PreProcessorDirective> preProcessorDirectives = new Dictionary<string, PreProcessorDirective> + { + { "if", PreProcessorDirective.If }, + { "elif", PreProcessorDirective.Elif }, + { "else", PreProcessorDirective.Else }, + { "endif", PreProcessorDirective.Endif }, + { "region", PreProcessorDirective.Region }, + { "endregion", PreProcessorDirective.Endregion }, + { "pragma", PreProcessorDirective.Pragma }, + { "warning", PreProcessorDirective.Warning }, + { "error", PreProcessorDirective.Error }, + { "line", PreProcessorDirective.Line }, + { "define", PreProcessorDirective.Define }, + { "undef", PreProcessorDirective.Undef } + }; + + public override void CheckKeywordOnPush(string keyword) + { + if (keyword == "endregion") + { + DirectiveType = PreProcessorDirective.Endregion; + ThisLineIndent = Parent.NextLineIndent.Clone(); + } + } + + public override void CheckKeyword(string keyword) + { + // check if the directive type has already been set + if (DirectiveType != PreProcessorDirective.None) + { + return; + } + + if (preProcessorDirectives.ContainsKey(keyword)) + { + DirectiveType = preProcessorDirectives[keyword]; + + // adjust the indentation for the region directive + if (DirectiveType == PreProcessorDirective.Region) + { + ThisLineIndent = Parent.NextLineIndent.Clone(); + } + else if (DirectiveType == PreProcessorDirective.If) + { + Engine.ifDirectiveIndents.Push(ThisLineIndent.Clone()); + } + } + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new PreProcessorState(this, engine); + } + + /// <summary> + /// Types of preprocessor directives. + /// </summary> + public enum PreProcessorDirective + { + None, + If, + Elif, + Else, + Endif, + Region, + Endregion, + Pragma, + Warning, + Error, + Line, + Define, + Undef + } + + #region Pre processor evaluation (from cs-tokenizer.cs) + + static bool is_identifier_start_character(int c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || Char.IsLetter((char)c); + } + + static bool is_identifier_part_character(char c) + { + if (c >= 'a' && c <= 'z') + return true; + + if (c >= 'A' && c <= 'Z') + return true; + + if (c == '_' || (c >= '0' && c <= '9')) + return true; + + if (c < 0x80) + return false; + + return Char.IsLetter(c) || Char.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation; + } + + bool eval_val(string s) + { + if (s == "true") + return true; + if (s == "false") + return false; + + return Engine.conditionalSymbols != null && Engine.conditionalSymbols.Contains(s) || + Engine.customConditionalSymbols != null && Engine.customConditionalSymbols.Contains(s); + } + + bool pp_primary(ref string s) + { + s = s.Trim(); + int len = s.Length; + + if (len > 0) + { + char c = s[0]; + + if (c == '(') + { + s = s.Substring(1); + bool val = pp_expr(ref s, false); + if (s.Length > 0 && s[0] == ')') + { + s = s.Substring(1); + return val; + } + return false; + } + + if (is_identifier_start_character(c)) + { + int j = 1; + + while (j < len) + { + c = s[j]; + + if (is_identifier_part_character(c)) + { + j++; + continue; + } + bool v = eval_val(s.Substring(0, j)); + s = s.Substring(j); + return v; + } + bool vv = eval_val(s); + s = ""; + return vv; + } + } + return false; + } + + bool pp_unary(ref string s) + { + s = s.Trim(); + int len = s.Length; + + if (len > 0) + { + if (s[0] == '!') + { + if (len > 1 && s[1] == '=') + { + return false; + } + s = s.Substring(1); + return !pp_primary(ref s); + } + else + return pp_primary(ref s); + } + else + { + return false; + } + } + + bool pp_eq(ref string s) + { + bool va = pp_unary(ref s); + + s = s.Trim(); + int len = s.Length; + if (len > 0) + { + if (s[0] == '=') + { + if (len > 2 && s[1] == '=') + { + s = s.Substring(2); + return va == pp_unary(ref s); + } + else + { + return false; + } + } + else if (s[0] == '!' && len > 1 && s[1] == '=') + { + s = s.Substring(2); + + return va != pp_unary(ref s); + + } + } + + return va; + + } + + bool pp_and(ref string s) + { + bool va = pp_eq(ref s); + + s = s.Trim(); + int len = s.Length; + if (len > 0) + { + if (s[0] == '&') + { + if (len > 2 && s[1] == '&') + { + s = s.Substring(2); + return (va & pp_and(ref s)); + } + else + { + return false; + } + } + } + return va; + } + + // + // Evaluates an expression for `#if' or `#elif' + // + bool pp_expr(ref string s, bool isTerm) + { + bool va = pp_and(ref s); + s = s.Trim(); + int len = s.Length; + if (len > 0) + { + char c = s[0]; + + if (c == '|') + { + if (len > 2 && s[1] == '|') + { + s = s.Substring(2); + return va | pp_expr(ref s, isTerm); + } + else + { + + return false; + } + } + if (isTerm) + { + return false; + } + } + + return va; + } + + bool eval(string s) + { + bool v = pp_expr(ref s, true); + s = s.Trim(); + if (s.Length != 0) + { + return false; + } + + return v; + } + + #endregion + } + + #endregion + + #region PreProcessorComment state + + /// <summary> + /// PreProcessor comment state. + /// </summary> + /// <remarks> + /// Activates when the #if or #elif directive is false and ignores + /// all pushed chars until the next '#'. + /// </remarks> + public class PreProcessorCommentState : IndentState + { + public PreProcessorCommentState() + { } + + public PreProcessorCommentState(PreProcessorCommentState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == '#' && Engine.isLineStart) + { + ChangeState<PreProcessorState>(); + } + } + + public override void InitializeState() + { + // OPTION: IndentPreprocessorDirectives + if (true/*Engine.options.IndentPreprocessorDirectives*/ && + Engine.ifDirectiveIndents.Count > 0) + { + ThisLineIndent = Engine.ifDirectiveIndents.Peek().Clone(); + NextLineIndent = ThisLineIndent.Clone(); + } + else + { + ThisLineIndent = Parent.NextLineIndent.Clone(); + NextLineIndent = ThisLineIndent.Clone(); + } + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new PreProcessorCommentState(this, engine); + } + } + + #endregion + + #region LineComment state + + /// <summary> + /// Single-line comment state. + /// </summary> + public class LineCommentState : IndentState + { + /// <summary> + /// It's possible that this should be the DocComment state: + /// check if the first next pushed char is equal to '/'. + /// </summary> + public bool CheckForDocComment = true; + + public LineCommentState() + { + /* if (engine.formattingOptions.KeepCommentsAtFirstColumn && engine.column == 2) + ThisLineIndent.Reset();*/ + } + + public LineCommentState(LineCommentState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + CheckForDocComment = prototype.CheckForDocComment; + } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == Engine.newLineChar) + { + // to handle cases like //\n/* + // Otherwise line 2 would be treated as line comment. + Engine.previousChar = '\0'; + ExitState(); + } + else if (ch == '/' && CheckForDocComment) + { + // wrong state, should be DocComment. + ExitState(); + ChangeState<DocCommentState>(); + } + + CheckForDocComment = false; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = Parent.NextLineIndent.Clone(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new LineCommentState(this, engine); + } + } + + #endregion + + #region DocComment state + + /// <summary> + /// XML documentation comment state. + /// </summary> + public class DocCommentState : IndentState + { + public DocCommentState() + { } + + public DocCommentState(DocCommentState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == Engine.newLineChar) + { + ExitState(); + } + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = Parent.NextLineIndent.Clone(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new DocCommentState(this, engine); + } + } + + #endregion + + #region MultiLineComment state + + /// <summary> + /// Multi-line comment state. + /// </summary> + public class MultiLineCommentState : IndentState + { + /// <summary> + /// True if any char has been pushed to this state. + /// </summary> + /// <remarks> + /// Needed to resolve an issue when the first pushed char is '/'. + /// The state would falsely exit on this sequence of chars '/*/', + /// since it only checks if the last two chars are '/' and '*'. + /// </remarks> + public bool IsAnyCharPushed; + + public MultiLineCommentState() + { } + + public MultiLineCommentState(MultiLineCommentState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsAnyCharPushed = prototype.IsAnyCharPushed; + } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == '/' && Engine.previousChar == '*' && IsAnyCharPushed) + { + ExitState(); + } + + IsAnyCharPushed = true; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = ThisLineIndent.Clone(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new MultiLineCommentState(this, engine); + } + } + + #endregion + + #region StringLiteral state + + /// <summary> + /// StringLiteral state. + /// </summary> + public class StringLiteralState : IndentState + { + /// <summary> + /// True if the next char is escaped with '\'. + /// </summary> + public bool IsEscaped; + + public StringLiteralState() + { } + + public StringLiteralState(StringLiteralState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsEscaped = prototype.IsEscaped; + } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == Engine.newLineChar || (!IsEscaped && ch == '"')) { + ExitState(); + } else { + IsEscaped = ch == '\\' && !IsEscaped; + } + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = Parent.NextLineIndent.Clone(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new StringLiteralState(this, engine); + } + } + + #endregion + + #region Verbatim string state + + /// <summary> + /// Verbatim string state. + /// </summary> + public class VerbatimStringState : IndentState + { + /// <summary> + /// True if there is an odd number of '"' in a row. + /// </summary> + public bool IsEscaped; + + public VerbatimStringState() + { } + + public VerbatimStringState(VerbatimStringState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsEscaped = prototype.IsEscaped; + } + + public override void Push(char ch) + { + base.Push(ch); + + if (IsEscaped && ch != '"') + { + ExitState(); + // the char has been pushed to the wrong state, push it back + Engine.currentState.Push(ch); + } + + IsEscaped = ch == '"' && !IsEscaped; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = new Indent(Engine.options); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new VerbatimStringState(this, engine); + } + } + + #endregion + + #region Character state + + /// <summary> + /// Character state. + /// </summary> + public class CharacterState : IndentState + { + /// <summary> + /// True if the next char is escaped with '\'. + /// </summary> + public bool IsEscaped; + + public CharacterState() + { } + + public CharacterState(CharacterState prototype, CSharpIndentEngine engine) + : base(prototype, engine) + { + IsEscaped = prototype.IsEscaped; + } + + public override void Push(char ch) + { + base.Push(ch); + + if (ch == Engine.newLineChar) + { + ExitState(); + } + else if (!IsEscaped && ch == '\'') + { + ExitState(); + } + + IsEscaped = ch == '\\' && !IsEscaped; + } + + public override void InitializeState() + { + ThisLineIndent = Parent.ThisLineIndent.Clone(); + NextLineIndent = Parent.NextLineIndent.Clone(); + } + + public override IndentState Clone(CSharpIndentEngine engine) + { + return new CharacterState(this, engine); + } + } + + #endregion +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/NullIStateMachineIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/NullIStateMachineIndentEngine.cs new file mode 100644 index 0000000000..d4f32a1f80 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/NullIStateMachineIndentEngine.cs @@ -0,0 +1,207 @@ +// +// NullIStateMachineIndentEngine.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// An empty IStateMachineIndentEngine implementation that does nothing. + /// </summary> + public sealed class NullIStateMachineIndentEngine : IStateMachineIndentEngine + { + int offset; + + public NullIStateMachineIndentEngine() + { + } + + #region IStateMachineIndentEngine implementation + public IStateMachineIndentEngine Clone() + { + return new NullIStateMachineIndentEngine() { offset = this.offset }; + } + + bool IStateMachineIndentEngine.IsInsidePreprocessorDirective { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsidePreprocessorComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideStringLiteral { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideVerbatimString { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideCharacter { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideString { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideLineComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideMultiLineComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideDocLineComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideOrdinaryComment { + get { + return false; + } + } + + bool IStateMachineIndentEngine.IsInsideOrdinaryCommentOrString { + get { + return false; + } + } + + bool IStateMachineIndentEngine.LineBeganInsideVerbatimString { + get { + return false; + } + } + + bool IStateMachineIndentEngine.LineBeganInsideMultiLineComment { + get { + return false; + } + } + #endregion + + #region IDocumentIndentEngine implementation + void IDocumentIndentEngine.Push(char ch) + { + offset++; + } + + void IDocumentIndentEngine.Reset() + { + this.offset = 0; + } + + void IDocumentIndentEngine.Update(SourceText sourceText, int offset) + { + this.offset = offset; + } + + IDocumentIndentEngine IDocumentIndentEngine.Clone() + { + return Clone(); + } + + string IDocumentIndentEngine.ThisLineIndent { + get { + return ""; + } + } + + string IDocumentIndentEngine.NextLineIndent { + get { + return ""; + } + } + + string IDocumentIndentEngine.CurrentIndent { + get { + return ""; + } + } + + bool IDocumentIndentEngine.NeedsReindent { + get { + return false; + } + } + + int IDocumentIndentEngine.Offset { + get { + return offset; + } + } +// TextLocation IDocumentIndentEngine.Location { +// get { +// return TextLocation.Empty; +// } +// } + + /// <inheritdoc /> + public bool EnableCustomIndentLevels + { + get { return false; } + set { } + } + + #endregion + + #region ICloneable implementation + object ICloneable.Clone() + { + return Clone(); + } + #endregion + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/TextPasteIndentEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/TextPasteIndentEngine.cs new file mode 100644 index 0000000000..221a440012 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IndentEngine/TextPasteIndentEngine.cs @@ -0,0 +1,677 @@ +// +// TextPasteIndentEngine.cs +// +// Author: +// Matej Miklečić <matej.miklecic@gmail.com> +// +// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp +{ + /// <summary> + /// Represents a decorator of an IStateMachineIndentEngine instance + /// that provides logic for text paste events. + /// </summary> + public class TextPasteIndentEngine : IDocumentIndentEngine, ITextPasteHandler + { + + #region Properties + + /// <summary> + /// An instance of IStateMachineIndentEngine which handles + /// the indentation logic. + /// </summary> + IStateMachineIndentEngine engine; + /// <summary> + /// Text editor options. + /// </summary> + internal readonly OptionSet options; + #endregion + + #region Constructors + + /// <summary> + /// Creates a new TextPasteIndentEngine instance. + /// </summary> + /// <param name="decoratedEngine"> + /// An instance of <see cref="IStateMachineIndentEngine"/> to which the + /// logic for indentation will be delegated. + /// </param> + /// <param name = "options"></param> + public TextPasteIndentEngine(IStateMachineIndentEngine decoratedEngine, OptionSet options) + { + this.engine = decoratedEngine; + this.options = options; + this.engine.EnableCustomIndentLevels = false; + } + + #endregion + + #region ITextPasteHandler + + /// <inheritdoc /> + string ITextPasteHandler.FormatPlainText(SourceText sourceText, int offset, string text, byte[] copyData) + { + if (copyData != null && copyData.Length == 1) { + var strategy = TextPasteUtils.Strategies [(PasteStrategy)copyData [0]]; + text = strategy.Decode(text); + } + engine.Update(sourceText, offset); + + if (engine.IsInsideStringLiteral) { + int idx = text.IndexOf('"'); + if (idx > 0) { + var o = offset; + while (o < sourceText.Length) { + char ch = sourceText[o]; + engine.Push(ch); + if (NewLine.IsNewLine(ch)) + break; + o++; + if (!engine.IsInsideStringLiteral) + return TextPasteUtils.StringLiteralStrategy.Encode(text); + } + return TextPasteUtils.StringLiteralStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx); + } + return TextPasteUtils.StringLiteralStrategy.Encode(text); + + } else if (engine.IsInsideVerbatimString) { + + int idx = text.IndexOf('"'); + if (idx > 0) { + var o = offset; + while (o < sourceText.Length) { + char ch = sourceText[o]; + engine.Push(ch); + o++; + if (!engine.IsInsideVerbatimString) + return TextPasteUtils.VerbatimStringStrategy.Encode(text); + } + return TextPasteUtils.VerbatimStringStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx); + } + + return TextPasteUtils.VerbatimStringStrategy.Encode(text); + } + var line = sourceText.Lines.GetLineFromPosition(offset); + var pasteAtLineStart = line.Start == offset; + var indentedText = new StringBuilder(); + var curLine = new StringBuilder(); + var clonedEngine = engine.Clone(); + bool isNewLine = false, gotNewLine = false; + for (int i = 0; i < text.Length; i++) { + var ch = text [i]; + if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment) { + clonedEngine.Push(ch); + curLine.Append(ch); + continue; + } + + var delimiterLength = NewLine.GetDelimiterLength(ch, i + 1 < text.Length ? text[i + 1] : ' '); + if (delimiterLength > 0) { + isNewLine = true; + if (gotNewLine || pasteAtLineStart) { + if (curLine.Length > 0 /*|| formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent*/) + indentedText.Append(clonedEngine.ThisLineIndent); + } + indentedText.Append(curLine); + var newLine = options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); + indentedText.Append(newLine); + curLine.Length = 0; + gotNewLine = true; + i += delimiterLength - 1; + // textEditorOptions.EolMarker[0] is the newLineChar used by the indentation engine. + clonedEngine.Push(newLine [0]); + } else { + if (isNewLine) { + if (ch == '\t' || ch == ' ') { + clonedEngine.Push(ch); + continue; + } + isNewLine = false; + } + curLine.Append(ch); + clonedEngine.Push(ch); + } + if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment && + !(clonedEngine.LineBeganInsideVerbatimString || clonedEngine.LineBeganInsideMultiLineComment)) { + if (gotNewLine) { + if (curLine.Length > 0 /*|| formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent*/) + indentedText.Append(clonedEngine.ThisLineIndent); + } + pasteAtLineStart = false; + indentedText.Append(curLine); + curLine.Length = 0; + gotNewLine = false; + continue; + } + } + if (gotNewLine && (!pasteAtLineStart || curLine.Length > 0)) { + indentedText.Append(clonedEngine.ThisLineIndent); + } + if (curLine.Length > 0) { + indentedText.Append(curLine); + } + return indentedText.ToString(); + } + + /// <inheritdoc /> + byte[] ITextPasteHandler.GetCopyData(SourceText sourceText, TextSpan segment) + { + engine.Update(sourceText, segment.Start); + + if (engine.IsInsideStringLiteral) { + return new[] { (byte)PasteStrategy.StringLiteral }; + } else if (engine.IsInsideVerbatimString) { + return new[] { (byte)PasteStrategy.VerbatimString }; + } + + return null; + } + + #endregion + + #region IDocumentIndentEngine + + /// <inheritdoc /> + public string ThisLineIndent { + get { return engine.ThisLineIndent; } + } + + /// <inheritdoc /> + public string NextLineIndent { + get { return engine.NextLineIndent; } + } + + /// <inheritdoc /> + public string CurrentIndent { + get { return engine.CurrentIndent; } + } + + /// <inheritdoc /> + public bool NeedsReindent { + get { return engine.NeedsReindent; } + } + + /// <inheritdoc /> + public int Offset { + get { return engine.Offset; } + } + +// /// <inheritdoc /> +// public TextLocation Location { +// get { return engine.Location; } +// } + + /// <inheritdoc /> + public bool EnableCustomIndentLevels { + get { return engine.EnableCustomIndentLevels; } + set { engine.EnableCustomIndentLevels = value; } + } + + /// <inheritdoc /> + public void Push(char ch) + { + engine.Push(ch); + } + + /// <inheritdoc /> + public void Reset() + { + engine.Reset(); + } + + /// <inheritdoc /> + public void Update(SourceText sourceText, int offset) + { + engine.Update(sourceText, offset); + } + + #endregion + + #region IClonable + + public IDocumentIndentEngine Clone() + { + return new TextPasteIndentEngine(engine, options); + } + + object ICloneable.Clone() + { + return Clone(); + } + + #endregion + + } + + /// <summary> + /// Types of text-paste strategies. + /// </summary> + public enum PasteStrategy : byte + { + PlainText = 0, + StringLiteral = 1, + VerbatimString = 2 + } + + /// <summary> + /// Defines some helper methods for dealing with text-paste events. + /// </summary> + public static class TextPasteUtils + { + /// <summary> + /// Collection of text-paste strategies. + /// </summary> + public static TextPasteStrategies Strategies = new TextPasteStrategies(); + + /// <summary> + /// The interface for a text-paste strategy. + /// </summary> + public interface IPasteStrategy + { + /// <summary> + /// Formats the given text according with this strategy rules. + /// </summary> + /// <param name="text"> + /// The text to format. + /// </param> + /// <returns> + /// Formatted text. + /// </returns> + string Encode(string text); + + /// <summary> + /// Converts text formatted according with this strategy rules + /// to its original form. + /// </summary> + /// <param name="text"> + /// Formatted text to convert. + /// </param> + /// <returns> + /// Original form of the given formatted text. + /// </returns> + string Decode(string text); + + /// <summary> + /// Type of this strategy. + /// </summary> + PasteStrategy Type { get; } + } + + /// <summary> + /// Wrapper that discovers all defined text-paste strategies and defines a way + /// to easily access them through their <see cref="PasteStrategy"/> type. + /// </summary> + public sealed class TextPasteStrategies + { + /// <summary> + /// Collection of discovered text-paste strategies. + /// </summary> + IDictionary<PasteStrategy, IPasteStrategy> strategies; + + /// <summary> + /// Uses reflection to find all types derived from <see cref="IPasteStrategy"/> + /// and adds an instance of each strategy to <see cref="strategies"/>. + /// </summary> + public TextPasteStrategies() + { + strategies = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => typeof(IPasteStrategy).IsAssignableFrom(t) && t.IsClass) + .Select(t => (IPasteStrategy)t.GetProperty("Instance").GetValue(null, null)) + .ToDictionary(s => s.Type); + } + + /// <summary> + /// Checks if there is a strategy of the given type and returns it. + /// </summary> + /// <param name="strategy"> + /// Type of the strategy instance. + /// </param> + /// <returns> + /// A strategy instance of the requested type, + /// or <see cref="DefaultStrategy"/> if it wasn't found. + /// </returns> + public IPasteStrategy this [PasteStrategy strategy] { + get { + if (strategies.ContainsKey(strategy)) { + return strategies [strategy]; + } + + return DefaultStrategy; + } + } + } + + /// <summary> + /// Doesn't do any formatting. Serves as the default strategy. + /// </summary> + public class PlainTextPasteStrategy : IPasteStrategy + { + + #region Singleton + + public static IPasteStrategy Instance { + get { + return instance ?? (instance = new PlainTextPasteStrategy()); + } + } + + static PlainTextPasteStrategy instance; + + protected PlainTextPasteStrategy() + { + } + + #endregion + + /// <inheritdoc /> + public string Encode(string text) + { + return text; + } + + /// <inheritdoc /> + public string Decode(string text) + { + return text; + } + + /// <inheritdoc /> + public PasteStrategy Type { + get { return PasteStrategy.PlainText; } + } + } + + /// <summary> + /// Escapes chars in the given text so that they don't + /// break a valid string literal. + /// </summary> + public class StringLiteralPasteStrategy : IPasteStrategy + { + + #region Singleton + + public static IPasteStrategy Instance { + get { + return instance ?? (instance = new StringLiteralPasteStrategy()); + } + } + + static StringLiteralPasteStrategy instance; + + protected StringLiteralPasteStrategy() + { + } + + #endregion + + /// <inheritdoc /> + public string Encode(string text) + { + return ConvertString(text); + } + + /// <summary> + /// Gets the escape sequence for the specified character. + /// </summary> + /// <remarks>This method does not convert ' or ".</remarks> + public static string ConvertChar(char ch) + { + switch (ch) { + case '\\': + return "\\\\"; + case '\0': + return "\\0"; + case '\a': + return "\\a"; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + case '\v': + return "\\v"; + default: + if (char.IsControl(ch) || char.IsSurrogate(ch) || + // print all uncommon white spaces as numbers + (char.IsWhiteSpace(ch) && ch != ' ')) { + return "\\u" + ((int)ch).ToString("x4"); + } else { + return ch.ToString(); + } + } + } + + /// <summary> + /// Converts special characters to escape sequences within the given string. + /// </summary> + public static string ConvertString(string str) + { + StringBuilder sb = new StringBuilder (); + foreach (char ch in str) { + if (ch == '"') { + sb.Append("\\\""); + } else { + sb.Append(ConvertChar(ch)); + } + } + return sb.ToString(); + } + + /// <inheritdoc /> + public string Decode(string text) + { + var result = new StringBuilder(); + bool isEscaped = false; + + for (int i = 0; i < text.Length; i++) { + var ch = text[i]; + if (isEscaped) { + switch (ch) { + case 'a': + result.Append('\a'); + break; + case 'b': + result.Append('\b'); + break; + case 'n': + result.Append('\n'); + break; + case 't': + result.Append('\t'); + break; + case 'v': + result.Append('\v'); + break; + case 'r': + result.Append('\r'); + break; + case '\\': + result.Append('\\'); + break; + case 'f': + result.Append('\f'); + break; + case '0': + result.Append(0); + break; + case '"': + result.Append('"'); + break; + case '\'': + result.Append('\''); + break; + case 'x': + char r; + if (TryGetHex(text, -1, ref i, out r)) { + result.Append(r); + break; + } + goto default; + case 'u': + if (TryGetHex(text, 4, ref i, out r)) { + result.Append(r); + break; + } + goto default; + case 'U': + if (TryGetHex(text, 8, ref i, out r)) { + result.Append(r); + break; + } + goto default; + default: + result.Append('\\'); + result.Append(ch); + break; + } + isEscaped = false; + continue; + } + if (ch != '\\') { + result.Append(ch); + } + else { + isEscaped = true; + } + } + + return result.ToString(); + } + + static bool TryGetHex(string text, int count, ref int idx, out char r) + { + int i; + int total = 0; + int top = count != -1 ? count : 4; + + for (i = 0; i < top; i++) { + int c = text[idx + 1 + i]; + + if (c >= '0' && c <= '9') + c = (int) c - (int) '0'; + else if (c >= 'A' && c <= 'F') + c = (int) c - (int) 'A' + 10; + else if (c >= 'a' && c <= 'f') + c = (int) c - (int) 'a' + 10; + else { + r = '\0'; + return false; + } + total = (total * 16) + c; + } + + if (top == 8) { + if (total > 0x0010FFFF) { + r = '\0'; + return false; + } + + if (total >= 0x00010000) + total = ((total - 0x00010000) / 0x0400 + 0xD800); + } + r = (char)total; + idx += top; + return true; + } + + /// <inheritdoc /> + public PasteStrategy Type { + get { return PasteStrategy.StringLiteral; } + } + } + + /// <summary> + /// Escapes chars in the given text so that they don't + /// break a valid verbatim string. + /// </summary> + public class VerbatimStringPasteStrategy : IPasteStrategy + { + + #region Singleton + + public static IPasteStrategy Instance { + get { + return instance ?? (instance = new VerbatimStringPasteStrategy()); + } + } + + static VerbatimStringPasteStrategy instance; + + protected VerbatimStringPasteStrategy() + { + } + + #endregion + + static readonly Dictionary<char, IEnumerable<char>> encodeReplace = new Dictionary<char, IEnumerable<char>> { + { '\"', "\"\"" }, + }; + + /// <inheritdoc /> + public string Encode(string text) + { + return string.Concat(text.SelectMany(c => encodeReplace.ContainsKey(c) ? encodeReplace [c] : new[] { c })); + } + + /// <inheritdoc /> + public string Decode(string text) + { + bool isEscaped = false; + return string.Concat(text.Where(c => !(isEscaped = !isEscaped && c == '"'))); + } + + /// <inheritdoc /> + public PasteStrategy Type { + get { return PasteStrategy.VerbatimString; } + } + } + + /// <summary> + /// The default text-paste strategy. + /// </summary> + public static IPasteStrategy DefaultStrategy = PlainTextPasteStrategy.Instance; + /// <summary> + /// String literal text-paste strategy. + /// </summary> + public static IPasteStrategy StringLiteralStrategy = StringLiteralPasteStrategy.Instance; + /// <summary> + /// Verbatim string text-paste strategy. + /// </summary> + public static IPasteStrategy VerbatimStringStrategy = VerbatimStringPasteStrategy.Instance; + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs new file mode 100644 index 0000000000..358124a430 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs @@ -0,0 +1,134 @@ +// 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.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Simplification; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + internal abstract class AbstractIntroduceVariableCodeAction : CodeAction + { + private readonly bool _allOccurrences; + private readonly bool _isConstant; + private readonly bool _isLocal; + private readonly bool _isQueryLocal; + private readonly TExpressionSyntax _expression; + private readonly SemanticDocument _document; + private readonly TService _service; + private readonly string _title; + + private static Regex s_newlinePattern = new Regex(@"[\r\n]+", RegexOptions.Compiled); + + internal AbstractIntroduceVariableCodeAction( + TService service, + SemanticDocument document, + TExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + bool isLocal, + bool isQueryLocal) + { + _service = service; + _document = document; + _expression = expression; + _allOccurrences = allOccurrences; + _isConstant = isConstant; + _isLocal = isLocal; + _isQueryLocal = isQueryLocal; + _title = CreateDisplayText(expression); + } + + public override string Title + { + get { return _title; } + } + + protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var changedDocument = await GetChangedDocumentCoreAsync(cancellationToken).ConfigureAwait(false); + return await Simplifier.ReduceAsync(changedDocument, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private async Task<Document> GetChangedDocumentCoreAsync(CancellationToken cancellationToken) + { + if (_isQueryLocal) + { + return await _service.IntroduceQueryLocalAsync(_document, _expression, _allOccurrences, cancellationToken).ConfigureAwait(false); + } + else if (_isLocal) + { + return await _service.IntroduceLocalAsync(_document, _expression, _allOccurrences, _isConstant, cancellationToken).ConfigureAwait(false); + } + else + { + return await IntroduceFieldAsync(cancellationToken).ConfigureAwait(false); + } + } + + private async Task<Document> IntroduceFieldAsync(CancellationToken cancellationToken) + { + var result = await _service.IntroduceFieldAsync(_document, _expression, _allOccurrences, _isConstant, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + + private string CreateDisplayText(TExpressionSyntax expression) + { + var singleLineExpression = expression.ConvertToSingleLine(); + var nodeString = singleLineExpression.ToFullString().Trim(); + + // prevent the display string from spanning multiple lines + nodeString = s_newlinePattern.Replace(nodeString, " "); + + // prevent the display string from being too long + const int MaxLength = 40; + if (nodeString.Length > MaxLength) + { + nodeString = nodeString.Substring(0, MaxLength) + "..."; + } + + return CreateDisplayText(nodeString); + } + + private string CreateDisplayText(string nodeString) + { + // Indexed by: allOccurrences, isConstant, isLocal + var formatStrings = new string[2, 2, 2] + { + { + { Resources.IntroduceFieldFor, Resources.IntroduceLocalFor }, + { Resources.IntroduceConstantFor, Resources.IntroduceLocalConstantFor } + }, + { + { Resources.IntroduceFieldForAllOccurrences, Resources.IntroduceLocalForAllOccurrences }, + { Resources.IntroduceConstantForAllOccurrences, Resources.IntroduceLocalConstantForAll } + } + }; + + var formatString = _isQueryLocal + ? _allOccurrences + ? Resources.IntroduceQueryVariableForAll + : Resources.IntroduceQueryVariableFor + : formatStrings[_allOccurrences ? 1 : 0, _isConstant ? 1 : 0, _isLocal ? 1 : 0]; + return string.Format(formatString, nodeString); + } + + protected ITypeSymbol GetExpressionType( + CancellationToken cancellationToken) + { + var semanticModel = _document.SemanticModel; + var typeInfo = semanticModel.GetTypeInfo(_expression, cancellationToken); + + return typeInfo.Type ?? typeInfo.ConvertedType ?? semanticModel.Compilation.GetSpecialType(SpecialType.System_Object); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs new file mode 100644 index 0000000000..35f2203940 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs @@ -0,0 +1,28 @@ +// 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.CodeActions; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Simplification; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private class IntroduceVariableCodeAction : AbstractIntroduceVariableCodeAction + { + internal IntroduceVariableCodeAction( + TService service, + SemanticDocument document, + TExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + bool isLocal, + bool isQueryLocal) + : base(service, document, expression, allOccurrences, isConstant, isLocal, isQueryLocal) + { + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs new file mode 100644 index 0000000000..00d1748536 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs @@ -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. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CaseCorrection; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private class IntroduceVariableAllOccurrenceCodeAction : AbstractIntroduceVariableCodeAction + { + internal IntroduceVariableAllOccurrenceCodeAction( + TService service, + SemanticDocument document, + TExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + bool isLocal, + bool isQueryLocal) + : base(service, document, expression, allOccurrences, isConstant, isLocal, isQueryLocal) + { + } + + protected override async Task<Document> PostProcessChangesAsync(Document document, CancellationToken cancellationToken) + { + // TODO: Formatting conversation ? AllowDisjointSpanMerging not supported in nuget roslyn right now. + var optionSet = document.Project.Solution.Workspace.Options;//.WithChangedOption(FormattingOptions.AllowDisjointSpanMerging, true); + document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await CaseCorrector.CaseCorrectAsync(document, CaseCorrector.Annotation, cancellationToken).ConfigureAwait(false); + return document; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State.cs new file mode 100644 index 0000000000..0dc173010e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State.cs @@ -0,0 +1,271 @@ +// 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.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + public SemanticDocument Document { get; private set; } + public TExpressionSyntax Expression { get; private set; } + + public bool InAttributeContext { get; private set; } + public bool InBlockContext { get; private set; } + public bool InConstructorInitializerContext { get; private set; } + public bool InFieldContext { get; private set; } + public bool InParameterContext { get; private set; } + public bool InQueryContext { get; private set; } + public bool InExpressionBodiedMemberContext { get; private set; } + + public bool IsConstant { get; private set; } + + private SemanticMap _semanticMap; + private readonly TService _service; + + public State(TService service, SemanticDocument document) + { + _service = service; + this.Document = document; + } + + public static State Generate( + TService service, + SemanticDocument document, + TextSpan textSpan, + CancellationToken cancellationToken) + { + var state = new State(service, document); + if (!state.TryInitialize(textSpan, cancellationToken)) + { + return null; + } + + return state; + } + + private bool TryInitialize( + TextSpan textSpan, + CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return false; + } + + var tree = this.Document.SyntaxTree; + + this.Expression = this.GetExpressionUnderSpan(tree, textSpan, cancellationToken); + if (this.Expression == null) + { + return false; + } + + var containingType = this.Expression.AncestorsAndSelf() + .Select(n => this.Document.SemanticModel.GetDeclaredSymbol(n, cancellationToken)) + .OfType<INamedTypeSymbol>() + .FirstOrDefault(); + + containingType = containingType ?? this.Document.SemanticModel.Compilation.ScriptClass; + + if (containingType == null || containingType.TypeKind == TypeKind.Interface) + { + return false; + } + + if (!CanIntroduceVariable(cancellationToken)) + { + return false; + } + + this.IsConstant = this.Document.SemanticModel.GetConstantValue(this.Expression, cancellationToken).HasValue; + + // Note: the ordering of these clauses are important. They go, generally, from + // innermost to outermost order. + if (IsInQueryContext(cancellationToken)) + { + if (CanGenerateInto<TQueryExpressionSyntax>(cancellationToken)) + { + this.InQueryContext = true; + return true; + } + + return false; + } + + if (IsInConstructorInitializerContext(cancellationToken)) + { + if (CanGenerateInto<TTypeDeclarationSyntax>(cancellationToken)) + { + this.InConstructorInitializerContext = true; + return true; + } + + return false; + } + + var enclosingBlocks = _service.GetContainingExecutableBlocks(this.Expression); + if (enclosingBlocks.Any()) + { + // If we're inside a block, then don't even try the other options (like field, + // constructor initializer, etc.). This is desirable behavior. If we're in a + // block in a field, then we're in a lambda, and we want to offer to generate + // a local, and not a field. + if (IsInBlockContext(cancellationToken)) + { + this.InBlockContext = true; + return true; + } + + return false; + } + + // The ordering of checks is important here. If we are inside a block within an Expression + // bodied member, we should treat it as if we are in block context. + // For example, in such a scenario we should generate inside the block, instead of rewriting + // a concise expression bodied member to its equivalent that has a body with a block. + // For this reason, block should precede expression bodied member check. + if (_service.IsInExpressionBodiedMember(this.Expression)) + { + if (CanGenerateInto<TTypeDeclarationSyntax>(cancellationToken)) + { + this.InExpressionBodiedMemberContext = true; + return true; + } + + return false; + } + + if (CanGenerateInto<TTypeDeclarationSyntax>(cancellationToken)) + { + if (IsInParameterContext(cancellationToken)) + { + this.InParameterContext = true; + return true; + } + else if (IsInFieldContext(cancellationToken)) + { + this.InFieldContext = true; + return true; + } + else if (IsInAttributeContext(cancellationToken)) + { + this.InAttributeContext = true; + return true; + } + } + + return false; + } + + public SemanticMap GetSemanticMap(CancellationToken cancellationToken) + { + _semanticMap = _semanticMap ?? this.Document.SemanticModel.GetSemanticMap(this.Expression, cancellationToken); + return _semanticMap; + } + + private TExpressionSyntax GetExpressionUnderSpan(SyntaxTree tree, TextSpan textSpan, CancellationToken cancellationToken) + { + var root = tree.GetRoot(cancellationToken); + var startToken = root.FindToken(textSpan.Start); + var stopToken = root.FindToken(textSpan.End); + + if (textSpan.End <= stopToken.SpanStart) + { + stopToken = stopToken.GetPreviousToken(includeSkipped: true); + } + + if (startToken.RawKind == 0 || stopToken.RawKind == 0) + { + return null; + } + + var containingExpressions1 = startToken.GetAncestors<TExpressionSyntax>().ToList(); + var containingExpressions2 = stopToken.GetAncestors<TExpressionSyntax>().ToList(); + + var commonExpression = containingExpressions1.FirstOrDefault(containingExpressions2.Contains); + if (commonExpression == null) + { + return null; + } + + if (!(textSpan.Start >= commonExpression.FullSpan.Start && + textSpan.Start <= commonExpression.SpanStart)) + { + return null; + } + + if (!(textSpan.End >= commonExpression.Span.End && + textSpan.End <= commonExpression.FullSpan.End)) + { + return null; + } + + return commonExpression; + } + + private bool CanIntroduceVariable( + CancellationToken cancellationToken) + { + // Don't generate a variable for an expression that's the only expression in a + // statement. Otherwise we'll end up with something like "v;" which is not + // legal in C#. + if (!_service.CanIntroduceVariableFor(this.Expression)) + { + return false; + } + + if (this.Expression is TTypeSyntax) + { + return false; + } + + // Even though we're creating a variable, we still ask if we can be replaced with an + // RValue and not an LValue. This is because introduction of a local adds a *new* LValue + // location, and we want to ensure that any writes will still happen to the *original* + // LValue location. i.e. if you have: "a[1] = b" then you don't want to change that to + // "var c = a[1]; c = b", as that write is no longer happening into the right LValue. + // + // In essense, this says "i can be replaced with an expression as long as i'm not being + // written to". + return this.Document.SemanticModel.CanReplaceWithRValue(this.Expression, cancellationToken); + } + + private bool CanGenerateInto<TSyntax>(CancellationToken cancellationToken) + where TSyntax : SyntaxNode + { + if (this.Document.SemanticModel.Compilation.ScriptClass != null) + { + return true; + } + + var syntax = this.Expression.GetAncestor<TSyntax>(); + return syntax != null && !syntax.OverlapsHiddenPosition(cancellationToken); + } + + private bool IsInTypeDeclarationOrValidCompilationUnit() + { + if (this.Expression.GetAncestorOrThis<TTypeDeclarationSyntax>() != null) + { + return true; + } + + // If we're interactive/script, we can generate into the compilation unit. + if (this.Document.Document.SourceCodeKind != SourceCodeKind.Regular) + { + return true; + } + + return false; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Attribute.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Attribute.cs new file mode 100644 index 0000000000..ed36767c83 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Attribute.cs @@ -0,0 +1,25 @@ +// 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; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInAttributeContext( + CancellationToken cancellationToken) + { + if (!_service.IsInAttributeArgumentInitializer(this.Expression)) + { + return false; + } + + // Have to make sure we're on or inside a type decl so that we have some place to + // put the result. + return IsInTypeDeclarationOrValidCompilationUnit(); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Block.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Block.cs new file mode 100644 index 0000000000..5a480a5bf1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Block.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.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInBlockContext( + CancellationToken cancellationToken) + { + if (!this.IsInTypeDeclarationOrValidCompilationUnit()) + { + return false; + } + + // If refer to a query property, then we use the query context instead. + var bindingMap = GetSemanticMap(cancellationToken); + if (bindingMap.AllReferencedSymbols.Any(s => s is IRangeVariableSymbol)) + { + return false; + } + + var type = GetTypeSymbol(this.Document, this.Expression, cancellationToken, objectAsDefault: false); + if (type == null || type.SpecialType == SpecialType.System_Void) + { + return false; + } + + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_ConstructorInitializer.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_ConstructorInitializer.cs new file mode 100644 index 0000000000..d9d736cbad --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_ConstructorInitializer.cs @@ -0,0 +1,43 @@ +// 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.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInConstructorInitializerContext( + CancellationToken cancellationToken) + { + // Note: if we're in a lambda that has a block body, then we don't ever get here + // because of the early check for IsInBlockContext. + if (!_service.IsInConstructorInitializer(this.Expression)) + { + return false; + } + + var bindingMap = GetSemanticMap(cancellationToken); + + // Can't extract out if a parameter is referenced. + if (bindingMap.AllReferencedSymbols.OfType<IParameterSymbol>().Any()) + { + return false; + } + + // Can't extract out an anonymous type used in a constructor initializer. + var info = this.Document.SemanticModel.GetTypeInfo(this.Expression, cancellationToken); + if (info.Type.ContainsAnonymousType()) + { + return false; + } + + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Field.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Field.cs new file mode 100644 index 0000000000..1782195b7c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Field.cs @@ -0,0 +1,50 @@ +// 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.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInFieldContext( + CancellationToken cancellationToken) + { + // Note: if we're in a lambda that has a block body, then we don't ever get here + // because of the early check for IsInBlockContext. + if (!_service.IsInFieldInitializer(this.Expression)) + { + return false; + } + + if (!IsInTypeDeclarationOrValidCompilationUnit()) + { + return false; + } + + // if the expression in the field references any parameters then that means it was + // either an expression inside a lambda in the field, or it was an expression in a + // query inside the field. Either of which cannot be extracted out further by this + // fix. + var bindingMap = GetSemanticMap(cancellationToken); + if (bindingMap.AllReferencedSymbols.OfType<IParameterSymbol>().Any()) + { + return false; + } + + // Can't extract out an anonymous type used in a field initializer. + var info = this.Document.SemanticModel.GetTypeInfo(this.Expression, cancellationToken); + if (info.Type.ContainsAnonymousType()) + { + return false; + } + + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Parameter.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Parameter.cs new file mode 100644 index 0000000000..8b45d848f9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Parameter.cs @@ -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. + +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInParameterContext( + CancellationToken cancellationToken) + { + if (!_service.IsInParameterInitializer(this.Expression)) + { + return false; + } + + // The default value for a parameter is a constant. So we always allow it unless it + // happens to capture one of the method's type parameters. + var bindingMap = this.GetSemanticMap(cancellationToken); + if (bindingMap.AllReferencedSymbols.OfType<ITypeParameterSymbol>() + .Where(tp => tp.TypeParameterKind == TypeParameterKind.Method) + .Any()) + { + return false; + } + + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Query.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Query.cs new file mode 100644 index 0000000000..14397a41c4 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.State_Query.cs @@ -0,0 +1,37 @@ +// 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.Threading; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + { + private partial class State + { + private bool IsInQueryContext( + CancellationToken cancellationToken) + { + if (!_service.IsInNonFirstQueryClause(this.Expression)) + { + return false; + } + + var semanticMap = GetSemanticMap(cancellationToken); + if (!semanticMap.AllReferencedSymbols.Any(s => s is IRangeVariableSymbol)) + { + return false; + } + + var info = this.Document.SemanticModel.GetTypeInfo(this.Expression, cancellationToken); + if (info.Type == null || info.Type.SpecialType == SpecialType.System_Void) + { + return false; + } + + return true; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.cs new file mode 100644 index 0000000000..a8d59347a8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/AbstractIntroduceVariableService.cs @@ -0,0 +1,329 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public abstract partial class AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + where TService : AbstractIntroduceVariableService<TService, TExpressionSyntax, TTypeSyntax, TTypeDeclarationSyntax, TQueryExpressionSyntax> + where TExpressionSyntax : SyntaxNode + where TTypeSyntax : TExpressionSyntax + where TTypeDeclarationSyntax : SyntaxNode + where TQueryExpressionSyntax : TExpressionSyntax + { + protected abstract bool IsInNonFirstQueryClause(TExpressionSyntax expression); + protected abstract bool IsInFieldInitializer(TExpressionSyntax expression); + protected abstract bool IsInParameterInitializer(TExpressionSyntax expression); + protected abstract bool IsInConstructorInitializer(TExpressionSyntax expression); + protected abstract bool IsInAttributeArgumentInitializer(TExpressionSyntax expression); + protected abstract bool IsInExpressionBodiedMember(TExpressionSyntax expression); + + protected abstract IEnumerable<SyntaxNode> GetContainingExecutableBlocks(TExpressionSyntax expression); + protected abstract IList<bool> GetInsertionIndices(TTypeDeclarationSyntax destination, CancellationToken cancellationToken); + + protected abstract bool CanIntroduceVariableFor(TExpressionSyntax expression); + protected abstract bool CanReplace(TExpressionSyntax expression); + + protected abstract Task<Document> IntroduceQueryLocalAsync(SemanticDocument document, TExpressionSyntax expression, bool allOccurrences, CancellationToken cancellationToken); + protected abstract Task<Document> IntroduceLocalAsync(SemanticDocument document, TExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken); + protected abstract Task<Tuple<Document, SyntaxNode, int>> IntroduceFieldAsync(SemanticDocument document, TExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken); + + protected virtual bool BlockOverlapsHiddenPosition(SyntaxNode block, CancellationToken cancellationToken) + { + return block.OverlapsHiddenPosition(cancellationToken); + } + + public async Task<IntroduceVariableResult> IntroduceVariableAsync( + Document document, + TextSpan textSpan, + CancellationToken cancellationToken) + { + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + var state = State.Generate((TService)this, semanticDocument, textSpan, cancellationToken); + if (state == null) + { + return IntroduceVariableResult.Failure; + } + + var actions = await CreateActionsAsync(state, cancellationToken).ConfigureAwait(false); + if (actions.Count == 0) + { + return IntroduceVariableResult.Failure; + } + + return new IntroduceVariableResult(new CodeRefactoring(null, actions)); + } + + private async Task<List<CodeAction>> CreateActionsAsync(State state, CancellationToken cancellationToken) + { + var actions = new List<CodeAction>(); + + if (state.InQueryContext) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: false, isLocal: false, isQueryLocal: true)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: false, isLocal: false, isQueryLocal: true)); + } + else if (state.InParameterContext) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: true, isLocal: false, isQueryLocal: false)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: true, isLocal: false, isQueryLocal: false)); + } + else if (state.InFieldContext) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: state.IsConstant, isLocal: false, isQueryLocal: false)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: state.IsConstant, isLocal: false, isQueryLocal: false)); + } + else if (state.InConstructorInitializerContext) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: state.IsConstant, isLocal: false, isQueryLocal: false)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: state.IsConstant, isLocal: false, isQueryLocal: false)); + } + else if (state.InAttributeContext) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: true, isLocal: false, isQueryLocal: false)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: true, isLocal: false, isQueryLocal: false)); + } + else if (state.InBlockContext) + { + await CreateConstantFieldActionsAsync(state, actions, cancellationToken).ConfigureAwait(false); + + var blocks = this.GetContainingExecutableBlocks(state.Expression); + var block = blocks.FirstOrDefault(); + + if (!BlockOverlapsHiddenPosition(block, cancellationToken)) + { + actions.Add(CreateAction(state, allOccurrences: false, isConstant: state.IsConstant, isLocal: true, isQueryLocal: false)); + + if (blocks.All(b => !BlockOverlapsHiddenPosition(b, cancellationToken))) + { + actions.Add(CreateAction(state, allOccurrences: true, isConstant: state.IsConstant, isLocal: true, isQueryLocal: false)); + } + } + } + else if (state.InExpressionBodiedMemberContext) + { + await CreateConstantFieldActionsAsync(state, actions, cancellationToken).ConfigureAwait(false); + actions.Add(CreateAction(state, allOccurrences: false, isConstant: state.IsConstant, isLocal: true, isQueryLocal: false)); + actions.Add(CreateAction(state, allOccurrences: true, isConstant: state.IsConstant, isLocal: true, isQueryLocal: false)); + } + + return actions; + } + + private async Task CreateConstantFieldActionsAsync(State state, List<CodeAction> actions, CancellationToken cancellationToken) + { + if (state.IsConstant && + !state.GetSemanticMap(cancellationToken).AllReferencedSymbols.OfType<ILocalSymbol>().Any() && + !state.GetSemanticMap(cancellationToken).AllReferencedSymbols.OfType<IParameterSymbol>().Any()) + { + // If something is a constant, and it doesn't access any other locals constants, + // then we prefer to offer to generate a constant field instead of a constant + // local. + var action1 = CreateAction(state, allOccurrences: false, isConstant: true, isLocal: false, isQueryLocal: false); + if (await CanGenerateIntoContainerAsync(state, action1, cancellationToken).ConfigureAwait(false)) + { + actions.Add(action1); + } + + var action2 = CreateAction(state, allOccurrences: true, isConstant: true, isLocal: false, isQueryLocal: false); + if (await CanGenerateIntoContainerAsync(state, action2, cancellationToken).ConfigureAwait(false)) + { + actions.Add(action2); + } + } + } + + private async Task<bool> CanGenerateIntoContainerAsync(State state, CodeAction action, CancellationToken cancellationToken) + { + var result = await this.IntroduceFieldAsync( + state.Document, state.Expression, + allOccurrences: false, isConstant: state.IsConstant, cancellationToken: cancellationToken).ConfigureAwait(false); + + SyntaxNode destination = result.Item2; + int insertionIndex = result.Item3; + + if (!destination.OverlapsHiddenPosition(cancellationToken)) + { + return true; + } + + if (destination is TTypeDeclarationSyntax) + { + var insertionIndices = this.GetInsertionIndices((TTypeDeclarationSyntax)destination, cancellationToken); + if (insertionIndices != null && + insertionIndices.Count > insertionIndex && + insertionIndices[insertionIndex]) + { + return true; + } + } + + return false; + } + + private CodeAction CreateAction(State state, bool allOccurrences, bool isConstant, bool isLocal, bool isQueryLocal) + { + if (allOccurrences) + { + return new IntroduceVariableAllOccurrenceCodeAction((TService)this, state.Document, state.Expression, allOccurrences, isConstant, isLocal, isQueryLocal); + } + + return new IntroduceVariableCodeAction((TService)this, state.Document, state.Expression, allOccurrences, isConstant, isLocal, isQueryLocal); + } + + protected static SyntaxToken GenerateUniqueFieldName( + SemanticDocument document, + TExpressionSyntax expression, + bool isConstant, + CancellationToken cancellationToken) + { + var semanticModel = document.SemanticModel; + var baseName = semanticModel.GenerateNameForExpression(expression, isConstant); + + // A field can't conflict with any existing member names. + var declaringType = semanticModel.GetEnclosingNamedType(expression.SpanStart, cancellationToken); + var reservedNames = declaringType.GetMembers().Select(m => m.Name); + + return NameGenerator.EnsureUniqueness(baseName, reservedNames, true).ToIdentifierToken(); + } + + protected static SyntaxToken GenerateUniqueLocalName( + SemanticDocument document, + TExpressionSyntax expression, + bool isConstant, + CancellationToken cancellationToken) + { + + var semanticModel = document.SemanticModel; + var baseName = semanticModel.GenerateNameForExpression(expression, capitalize: isConstant); + var reservedNames = semanticModel.LookupSymbols(expression.SpanStart).Select(s => s.Name); + + return NameGenerator.EnsureUniqueness(baseName, reservedNames, true).ToIdentifierToken(); + } + + protected ISet<TExpressionSyntax> FindMatches( + SemanticDocument originalDocument, + TExpressionSyntax expressionInOriginal, + SemanticDocument currentDocument, + SyntaxNode withinNodeInCurrent, + bool allOccurrences, + CancellationToken cancellationToken) + { + var originalSemanticModel = originalDocument.SemanticModel; + var currentSemanticModel = currentDocument.SemanticModel; + + var matches = from nodeInCurrent in withinNodeInCurrent.DescendantNodesAndSelf().OfType<TExpressionSyntax>() + where NodeMatchesExpression(originalSemanticModel, currentSemanticModel, expressionInOriginal, nodeInCurrent, allOccurrences, cancellationToken) + select nodeInCurrent; + return new HashSet<TExpressionSyntax>(matches.OfType<TExpressionSyntax>()); + } + + private bool NodeMatchesExpression( + SemanticModel originalSemanticModel, + SemanticModel currentSemanticModel, + TExpressionSyntax expressionInOriginal, + TExpressionSyntax nodeInCurrent, + bool allOccurrences, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (nodeInCurrent == expressionInOriginal) + { + return true; + } + else + { + if (allOccurrences && + this.CanReplace(nodeInCurrent)) + { + return SemanticEquivalence.AreSemanticallyEquivalent( + originalSemanticModel, currentSemanticModel, expressionInOriginal, nodeInCurrent); + } + } + + return false; + } + + protected TNode Rewrite<TNode>( + SemanticDocument originalDocument, + TExpressionSyntax expressionInOriginal, + TExpressionSyntax variableName, + SemanticDocument currentDocument, + TNode withinNodeInCurrent, + bool allOccurrences, + CancellationToken cancellationToken) + where TNode : SyntaxNode + { + var matches = FindMatches(originalDocument, expressionInOriginal, currentDocument, withinNodeInCurrent, allOccurrences, cancellationToken); + + // Parenthesize the variable, and go and replace anything we find with it. + // NOTE: we do not want elastic trivia as we want to just replace the existing code + // as is, while preserving the trivia there. We do not want to update it. + var replacement = variableName.Parenthesize(includeElasticTrivia: false) + .WithAdditionalAnnotations(Formatter.Annotation); + + return RewriteCore(withinNodeInCurrent, replacement, matches); + } + + protected abstract TNode RewriteCore<TNode>( + TNode node, + SyntaxNode replacementNode, + ISet<TExpressionSyntax> matches) + where TNode : SyntaxNode; + + protected static ITypeSymbol GetTypeSymbol( + SemanticDocument document, + TExpressionSyntax expression, + CancellationToken cancellationToken, + bool objectAsDefault = true) + { + var semanticModel = document.SemanticModel; + var typeInfo = semanticModel.GetTypeInfo(expression, cancellationToken); + + if (typeInfo.Type != null) + { + return typeInfo.Type; + } + + if (typeInfo.ConvertedType != null) + { + return typeInfo.ConvertedType; + } + + if (objectAsDefault) + { + return semanticModel.Compilation.GetSpecialType(SpecialType.System_Object); + } + + return null; + } + + protected static IEnumerable<IParameterSymbol> GetAnonymousMethodParameters( + SemanticDocument document, TExpressionSyntax expression, CancellationToken cancellationToken) + { + var semanticModel = document.SemanticModel; + var semanticMap = semanticModel.GetSemanticMap(expression, cancellationToken); + + var anonymousMethodParameters = semanticMap.AllReferencedSymbols + .OfType<IParameterSymbol>() + .Where(p => p.ContainingSymbol.IsAnonymousFunction()); + return anonymousMethodParameters; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.Rewriter.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.Rewriter.cs new file mode 100644 index 0000000000..ce17b4ef4a --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.Rewriter.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.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Simplification; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class CSharpIntroduceVariableService + { + private class Rewriter : CSharpSyntaxRewriter + { + private readonly SyntaxAnnotation _replacementAnnotation = new SyntaxAnnotation (); + private readonly SyntaxNode _replacementNode; + private readonly ISet<ExpressionSyntax> _matches; + + private Rewriter (SyntaxNode replacementNode, ISet<ExpressionSyntax> matches) + { + _replacementNode = replacementNode; + _matches = matches; + } + + public override SyntaxNode Visit (SyntaxNode node) + { + var expression = node as ExpressionSyntax; + if (expression != null && + _matches.Contains (expression)) { + return _replacementNode + .WithLeadingTrivia (expression.GetLeadingTrivia ()) + .WithTrailingTrivia (expression.GetTrailingTrivia ()) + .WithAdditionalAnnotations (_replacementAnnotation); + } + + return base.Visit (node); + } + + public override SyntaxNode VisitParenthesizedExpression (ParenthesizedExpressionSyntax node) + { + var newNode = base.VisitParenthesizedExpression (node); + if (node != newNode && + newNode.IsKind (SyntaxKind.ParenthesizedExpression)) { + var parenthesizedExpression = (ParenthesizedExpressionSyntax)newNode; + var innerExpression = parenthesizedExpression.OpenParenToken.GetNextToken ().Parent; + if (innerExpression.HasAnnotation (_replacementAnnotation)) { + return newNode.WithAdditionalAnnotations (Simplifier.Annotation); + } + } + + return newNode; + } + + public static SyntaxNode Visit (SyntaxNode node, SyntaxNode replacementNode, ISet<ExpressionSyntax> matches) + { + return new Rewriter (replacementNode, matches).Visit ((SyntaxNode)node); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.cs new file mode 100644 index 0000000000..5b46645500 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService.cs @@ -0,0 +1,131 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable + { + public partial class CSharpIntroduceVariableService : + AbstractIntroduceVariableService<CSharpIntroduceVariableService, ExpressionSyntax, TypeSyntax, TypeDeclarationSyntax, QueryExpressionSyntax> + { + protected override bool IsInNonFirstQueryClause(ExpressionSyntax expression) + { + var query = expression.GetAncestor<QueryExpressionSyntax>(); + if (query != null) + { + // Can't introduce for the first clause in a query. + var fromClause = expression.GetAncestor<FromClauseSyntax>(); + if (fromClause == null || query.FromClause != fromClause) + { + return true; + } + } + + return false; + } + + protected override bool IsInFieldInitializer(ExpressionSyntax expression) + { + return expression.GetAncestorOrThis<VariableDeclaratorSyntax>() + .GetAncestorOrThis<FieldDeclarationSyntax>() != null; + } + + protected override bool IsInParameterInitializer(ExpressionSyntax expression) + { + return expression.GetAncestorOrThis<EqualsValueClauseSyntax>().IsParentKind(SyntaxKind.Parameter); + } + + protected override bool IsInConstructorInitializer(ExpressionSyntax expression) + { + return expression.GetAncestorOrThis<ConstructorInitializerSyntax>() != null; + } + + protected override bool IsInExpressionBodiedMember(ExpressionSyntax expression) + { + // walk up until we find a nearest enclosing block or arrow expression. + for (SyntaxNode node = expression; node != null; node = node.GetParent()) + { + // If we are in an expression bodied member and if the expression has a block body, then, + // act as if we're in a block context and not in an expression body context at all. + if (node.IsKind(SyntaxKind.Block)) + { + return false; + } + else if (node.IsKind(SyntaxKind.ArrowExpressionClause)) + { + return true; + } + } + + return false; + } + + protected override bool IsInAttributeArgumentInitializer(ExpressionSyntax expression) + { + // Don't call the base here. We want to let the user extract a constant if they've + // said "Foo(a = 10)" + var attributeArgument = expression.GetAncestorOrThis<AttributeArgumentSyntax>(); + if (attributeArgument != null) + { + // Can't extract an attribute initializer if it contains an array initializer of any + // sort. Also, we can't extract if there's any typeof expression within it. + if (!expression.DepthFirstTraversal().Any(n => n.RawKind == (int)SyntaxKind.ArrayCreationExpression) && + !expression.DepthFirstTraversal().Any(n => n.RawKind == (int)SyntaxKind.TypeOfExpression)) + { + var attributeDecl = attributeArgument.GetAncestorOrThis<AttributeListSyntax>(); + + // Also can't extract an attribute initializer if the attribute is a global one. + if (!attributeDecl.IsParentKind(SyntaxKind.CompilationUnit)) + { + return true; + } + } + } + + return false; + } + + protected override bool CanIntroduceVariableFor(ExpressionSyntax expression) + { + if (expression.WalkUpParentheses().IsParentKind(SyntaxKind.ExpressionStatement)) + { + return false; + } + + return true; + } + + protected override IEnumerable<SyntaxNode> GetContainingExecutableBlocks(ExpressionSyntax expression) + { + return expression.GetAncestorsOrThis<BlockSyntax>(); + } + + protected override IList<bool> GetInsertionIndices(TypeDeclarationSyntax destination, CancellationToken cancellationToken) + { + return destination.GetInsertionIndices(cancellationToken); + } + + protected override bool CanReplace(ExpressionSyntax expression) + { + return true; + } + + protected override TNode RewriteCore<TNode>( + TNode node, + SyntaxNode replacementNode, + ISet<ExpressionSyntax> matches) + { + return (TNode)Rewriter.Visit(node, replacementNode, matches); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs new file mode 100644 index 0000000000..7238f953a6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs @@ -0,0 +1,198 @@ +// 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; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class CSharpIntroduceVariableService + { + protected override Task<Tuple<Document, SyntaxNode, int>> IntroduceFieldAsync( + SemanticDocument document, + ExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + CancellationToken cancellationToken) + { + var oldTypeDeclaration = expression.GetAncestorOrThis<TypeDeclarationSyntax>(); + + var oldType = oldTypeDeclaration != null + ? document.SemanticModel.GetDeclaredSymbol(oldTypeDeclaration, cancellationToken) as INamedTypeSymbol + : document.SemanticModel.Compilation.ScriptClass; + var newNameToken = (SyntaxToken)GenerateUniqueFieldName(document, expression, isConstant, cancellationToken); + + var newQualifiedName = oldTypeDeclaration != null + ? SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ParseName(oldType.ToDisplayString (ICSharpCode.NRefactory6.CSharp.Completion.DelegateCreationContextHandler.NameFormat)), SyntaxFactory.IdentifierName(newNameToken)) + : (ExpressionSyntax)SyntaxFactory.IdentifierName(newNameToken); + + newQualifiedName = newQualifiedName.WithAdditionalAnnotations(Simplifier.Annotation); + + var newFieldDeclaration = SyntaxFactory.FieldDeclaration( + default(SyntaxList<AttributeListSyntax>), + MakeFieldModifiers(isConstant, inScript: oldType.IsScriptClass), + SyntaxFactory.VariableDeclaration( + GetTypeSymbol(document, expression, cancellationToken).GenerateTypeSyntax(), + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator( + newNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), + null, + SyntaxFactory.EqualsValueClause(expression))))).WithAdditionalAnnotations(Formatter.Annotation); + + if (oldTypeDeclaration != null) + { + var newTypeDeclaration = Rewrite( + document, expression, newQualifiedName, document, oldTypeDeclaration, allOccurrences, cancellationToken); + + var insertionIndex = isConstant ? + DetermineConstantInsertPosition(oldTypeDeclaration.Members, newTypeDeclaration.Members) : + DetermineFieldInsertPosition(oldTypeDeclaration.Members, newTypeDeclaration.Members); + + var finalTypeDeclaration = InsertMember(newTypeDeclaration, newFieldDeclaration, insertionIndex); + + SyntaxNode destination = oldTypeDeclaration; + var newRoot = document.Root.ReplaceNode(oldTypeDeclaration, finalTypeDeclaration); + return Task.FromResult(Tuple.Create(document.Document.WithSyntaxRoot(newRoot), destination, insertionIndex)); + } + else + { + var oldCompilationUnit = (CompilationUnitSyntax)document.Root; + var newCompilationUnit = Rewrite( + document, expression, newQualifiedName, document, oldCompilationUnit, allOccurrences, cancellationToken); + + var insertionIndex = isConstant ? + DetermineConstantInsertPosition(oldCompilationUnit.Members, newCompilationUnit.Members) : + DetermineFieldInsertPosition(oldCompilationUnit.Members, newCompilationUnit.Members); + + SyntaxNode destination = oldCompilationUnit; + var newRoot = newCompilationUnit.WithMembers(newCompilationUnit.Members.Insert(insertionIndex, newFieldDeclaration)); + return Task.FromResult(Tuple.Create(document.Document.WithSyntaxRoot(newRoot), destination, insertionIndex)); + } + } + + protected static int DetermineConstantInsertPosition( + SyntaxList<MemberDeclarationSyntax> oldMembers, + SyntaxList<MemberDeclarationSyntax> newMembers) + { + // 1) Place the constant after the last constant. + // + // 2) If there is no constant, place it before the first field + // + // 3) If the first change is before either of those, then place before the first + // change + // + // 4) Otherwise, place it at the start. + var index = 0; + var lastConstantIndex = oldMembers.LastIndexOf(IsConstantField); + + if (lastConstantIndex >= 0) + { + index = lastConstantIndex + 1; + } + else + { + var firstFieldIndex = oldMembers.IndexOf(member => member is FieldDeclarationSyntax); + if (firstFieldIndex >= 0) + { + index = firstFieldIndex; + } + } + + var firstChangeIndex = DetermineFirstChange(oldMembers, newMembers); + if (firstChangeIndex >= 0) + { + index = Math.Min(index, firstChangeIndex); + } + + return index; + } + + protected static int DetermineFieldInsertPosition( + SyntaxList<MemberDeclarationSyntax> oldMembers, + SyntaxList<MemberDeclarationSyntax> newMembers) + { + // 1) Place the constant after the last field. + // + // 2) If there is no field, place it after the last constant + // + // 3) If the first change is before either of those, then place before the first + // change + // + // 4) Otherwise, place it at the start. + var index = 0; + var lastFieldIndex = oldMembers.LastIndexOf(member => member is FieldDeclarationSyntax); + if (lastFieldIndex >= 0) + { + index = lastFieldIndex + 1; + } + else + { + var lastConstantIndex = oldMembers.LastIndexOf(IsConstantField); + if (lastConstantIndex >= 0) + { + index = lastConstantIndex + 1; + } + } + + var firstChangeIndex = DetermineFirstChange(oldMembers, newMembers); + if (firstChangeIndex >= 0) + { + index = Math.Min(index, firstChangeIndex); + } + + return index; + } + + private static bool IsConstantField(MemberDeclarationSyntax member) + { + var field = member as FieldDeclarationSyntax; + return field != null && field.Modifiers.Any(SyntaxKind.ConstKeyword); + } + + protected static int DetermineFirstChange(SyntaxList<MemberDeclarationSyntax> oldMembers, SyntaxList<MemberDeclarationSyntax> newMembers) + { + for (int i = 0; i < oldMembers.Count; i++) + { + if (!SyntaxFactory.AreEquivalent(oldMembers[i], newMembers[i], topLevel: false)) + { + return i; + } + } + + return -1; + } + + protected static TypeDeclarationSyntax InsertMember( + TypeDeclarationSyntax typeDeclaration, + MemberDeclarationSyntax memberDeclaration, + int index) + { + return typeDeclaration.WithMembers( + typeDeclaration.Members.Insert(index, memberDeclaration)); + } + + private SyntaxTokenList MakeFieldModifiers(bool isConstant, bool inScript) + { + if (isConstant) + { + return SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)); + } + else if (inScript) + { + return SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + } + else + { + return SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs new file mode 100644 index 0000000000..728ca0a1df --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs @@ -0,0 +1,378 @@ +// 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.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class CSharpIntroduceVariableService + { + protected override Task<Document> IntroduceLocalAsync( + SemanticDocument document, + ExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + CancellationToken cancellationToken) + { + var options = document.Project.Solution.Workspace.Options; + + var newLocalNameToken = (SyntaxToken)GenerateUniqueLocalName(document, expression, isConstant, cancellationToken); + var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); + + var modifiers = isConstant + ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) + : default(SyntaxTokenList); + + var declarationStatement = SyntaxFactory.LocalDeclarationStatement( + modifiers, + SyntaxFactory.VariableDeclaration( + this.GetTypeSyntax(document, expression, isConstant, options, cancellationToken), + SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( + newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), + null, + SyntaxFactory.EqualsValueClause(expression.WithoutTrailingTrivia().WithoutLeadingTrivia()))))); + + var anonymousMethodParameters = GetAnonymousMethodParameters(document, expression, cancellationToken); + var lambdas = anonymousMethodParameters.SelectMany(p => p.ContainingSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).AsEnumerable()) + .Where(n => n is ParenthesizedLambdaExpressionSyntax || n is SimpleLambdaExpressionSyntax) + .ToSet(); + + var parentLambda = GetParentLambda(expression, lambdas); + + if (parentLambda != null) + { + return Task.FromResult(IntroduceLocalDeclarationIntoLambda( + document, expression, newLocalName, declarationStatement, parentLambda, allOccurrences, cancellationToken)); + } + else if (IsInExpressionBodiedMember(expression)) + { + return Task.FromResult(RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( + document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken)); + } + else + { + return IntroduceLocalDeclarationIntoBlockAsync( + document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); + } + } + + private Document IntroduceLocalDeclarationIntoLambda( + SemanticDocument document, + ExpressionSyntax expression, + IdentifierNameSyntax newLocalName, + LocalDeclarationStatementSyntax declarationStatement, + SyntaxNode oldLambda, + bool allOccurrences, + CancellationToken cancellationToken) + { + var oldBody = oldLambda is ParenthesizedLambdaExpressionSyntax + ? (ExpressionSyntax)((ParenthesizedLambdaExpressionSyntax)oldLambda).Body + : (ExpressionSyntax)((SimpleLambdaExpressionSyntax)oldLambda).Body; + + var rewrittenBody = Rewrite( + document, expression, newLocalName, document, oldBody, allOccurrences, cancellationToken); + + var delegateType = document.SemanticModel.GetTypeInfo(oldLambda, cancellationToken).ConvertedType as INamedTypeSymbol; + + var newBody = delegateType != null && delegateType.DelegateInvokeMethod != null && delegateType.DelegateInvokeMethod.ReturnsVoid + ? SyntaxFactory.Block(declarationStatement) + : SyntaxFactory.Block(declarationStatement, SyntaxFactory.ReturnStatement(rewrittenBody)); + + newBody = newBody.WithAdditionalAnnotations(Formatter.Annotation); + + var newLambda = oldLambda is ParenthesizedLambdaExpressionSyntax + ? ((ParenthesizedLambdaExpressionSyntax)oldLambda).WithBody(newBody) + : (SyntaxNode)((SimpleLambdaExpressionSyntax)oldLambda).WithBody(newBody); + + var newRoot = document.Root.ReplaceNode(oldLambda, newLambda); + return document.Document.WithSyntaxRoot(newRoot); + } + + private SyntaxNode GetParentLambda(ExpressionSyntax expression, ISet<SyntaxNode> lambdas) + { + var current = expression; + while (current != null) + { + if (lambdas.Contains(current.Parent)) + { + return current.Parent; + } + + current = current.Parent as ExpressionSyntax; + } + + return null; + } + + private TypeSyntax GetTypeSyntax(SemanticDocument document, ExpressionSyntax expression, bool isConstant, OptionSet options, CancellationToken cancellationToken) + { + var typeSymbol = GetTypeSymbol(document, expression, cancellationToken); + if (typeSymbol.ContainsAnonymousType()) + { + return SyntaxFactory.IdentifierName("var"); + } + + if (!isConstant && true /*options.GetOption(CSharpCodeStyleOptions.UseVarWhenDeclaringLocals) */&& CanUseVar(typeSymbol)) + { + return SyntaxFactory.IdentifierName("var"); + } + + return typeSymbol.GenerateTypeSyntax(); + } + + private bool CanUseVar(ITypeSymbol typeSymbol) + { + return typeSymbol.TypeKind != TypeKind.Delegate && !typeSymbol.IsErrorType(); + } + + private static async Task<Tuple<SemanticDocument, ISet<ExpressionSyntax>>> ComplexifyParentingStatements( + SemanticDocument semanticDocument, + ISet<ExpressionSyntax> matches, + CancellationToken cancellationToken) + { + // First, track the matches so that we can get back to them later. + var newRoot = semanticDocument.Root.TrackNodes(matches); + var newDocument = semanticDocument.Document.WithSyntaxRoot(newRoot); + var newSemanticDocument = await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); + var newMatches = newSemanticDocument.Root.GetCurrentNodes(matches.AsEnumerable()).ToSet(); + + // Next, expand the topmost parenting expression of each match, being careful + // not to expand the matches themselves. + var topMostExpressions = newMatches + .Select(m => m.AncestorsAndSelf().OfType<ExpressionSyntax>().Last()) + .Distinct(); + + newRoot = await newSemanticDocument.Root + .ReplaceNodesAsync( + topMostExpressions, + computeReplacementAsync: async (oldNode, newNode, ct) => + { + return await Simplifier + .ExpandAsync( + oldNode, + newSemanticDocument.Document, + expandInsideNode: node => + { + var expression = node as ExpressionSyntax; + return expression == null + || !newMatches.Contains(expression); + }, + cancellationToken: ct) + .ConfigureAwait(false); + }, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + newDocument = newSemanticDocument.Document.WithSyntaxRoot(newRoot); + newSemanticDocument = await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); + newMatches = newSemanticDocument.Root.GetCurrentNodes(matches.AsEnumerable()).ToSet(); + + return Tuple.Create(newSemanticDocument, newMatches); + } + + private Document RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( + SemanticDocument document, + ExpressionSyntax expression, + NameSyntax newLocalName, + LocalDeclarationStatementSyntax declarationStatement, + bool allOccurrences, + CancellationToken cancellationToken) + { + var oldBody = expression.GetAncestorOrThis<ArrowExpressionClauseSyntax>(); + var oldParentingNode = oldBody.Parent; + var leadingTrivia = oldBody.GetLeadingTrivia() + .AddRange(oldBody.ArrowToken.TrailingTrivia); + + var newStatement = Rewrite(document, expression, newLocalName, document, oldBody.Expression, allOccurrences, cancellationToken); + var newBody = SyntaxFactory.Block(declarationStatement, SyntaxFactory.ReturnStatement(newStatement)) + .WithLeadingTrivia(leadingTrivia) + .WithTrailingTrivia(oldBody.GetTrailingTrivia()) + .WithAdditionalAnnotations(Formatter.Annotation); + + SyntaxNode newParentingNode = null; + if (oldParentingNode is BasePropertyDeclarationSyntax) + { + var getAccessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, newBody); + var accessorList = SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { getAccessor })); + + newParentingNode = ((BasePropertyDeclarationSyntax)oldParentingNode).RemoveNode(oldBody, SyntaxRemoveOptions.KeepNoTrivia); + + if (newParentingNode.IsKind(SyntaxKind.PropertyDeclaration)) + { + var propertyDeclaration = ((PropertyDeclarationSyntax)newParentingNode); + newParentingNode = propertyDeclaration + .WithAccessorList(accessorList) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia); + } + else if (newParentingNode.IsKind(SyntaxKind.IndexerDeclaration)) + { + var indexerDeclaration = ((IndexerDeclarationSyntax)newParentingNode); + newParentingNode = indexerDeclaration + .WithAccessorList(accessorList) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithTrailingTrivia(indexerDeclaration.SemicolonToken.TrailingTrivia); + } + } + else if (oldParentingNode is BaseMethodDeclarationSyntax) + { + newParentingNode = ((BaseMethodDeclarationSyntax)oldParentingNode) + .RemoveNode(oldBody, SyntaxRemoveOptions.KeepNoTrivia) + .WithBody(newBody); + + if (newParentingNode.IsKind(SyntaxKind.MethodDeclaration)) + { + var methodDeclaration = ((MethodDeclarationSyntax)newParentingNode); + newParentingNode = methodDeclaration + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithTrailingTrivia(methodDeclaration.SemicolonToken.TrailingTrivia); + } + else if (newParentingNode.IsKind(SyntaxKind.OperatorDeclaration)) + { + var operatorDeclaration = ((OperatorDeclarationSyntax)newParentingNode); + newParentingNode = operatorDeclaration + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithTrailingTrivia(operatorDeclaration.SemicolonToken.TrailingTrivia); + } + else if (newParentingNode.IsKind(SyntaxKind.ConversionOperatorDeclaration)) + { + var conversionOperatorDeclaration = ((ConversionOperatorDeclarationSyntax)newParentingNode); + newParentingNode = conversionOperatorDeclaration + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithTrailingTrivia(conversionOperatorDeclaration.SemicolonToken.TrailingTrivia); + } + } + + var newRoot = document.Root.ReplaceNode(oldParentingNode, newParentingNode); + return document.Document.WithSyntaxRoot(newRoot); + } + + private async Task<Document> IntroduceLocalDeclarationIntoBlockAsync( + SemanticDocument document, + ExpressionSyntax expression, + NameSyntax newLocalName, + LocalDeclarationStatementSyntax declarationStatement, + bool allOccurrences, + CancellationToken cancellationToken) + { + declarationStatement = declarationStatement.WithAdditionalAnnotations(Formatter.Annotation); + + var oldOutermostBlock = expression.GetAncestorsOrThis<BlockSyntax>().LastOrDefault(); + var matches = FindMatches(document, expression, document, oldOutermostBlock, allOccurrences, cancellationToken); + Debug.Assert(matches.Contains(expression)); + + var complexified = await ComplexifyParentingStatements(document, matches, cancellationToken).ConfigureAwait(false); + document = complexified.Item1; + matches = complexified.Item2; + + // Our original expression should have been one of the matches, which were tracked as part + // of complexification, so we can retrieve the latest version of the expression here. + expression = document.Root.GetCurrentNodes(expression).First(); + + var innermostStatements = new HashSet<StatementSyntax>( + matches.Select(expr => expr.GetAncestorOrThis<StatementSyntax>())); + + if (innermostStatements.Count == 1) + { + // If there was only one match, or all the matches came from the same + // statement, then we want to place the declaration right above that + // statement. Note: we special case this because the statement we are going + // to go above might not be in a block and we may have to generate it + return IntroduceLocalForSingleOccurrenceIntoBlock( + document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); + } + + var oldInnerMostCommonBlock = matches.FindInnermostCommonBlock(); + var allAffectedStatements = new HashSet<StatementSyntax>(matches.SelectMany(expr => expr.GetAncestorsOrThis<StatementSyntax>())); + var firstStatementAffectedInBlock = oldInnerMostCommonBlock.Statements.First(allAffectedStatements.Contains); + + var firstStatementAffectedIndex = oldInnerMostCommonBlock.Statements.IndexOf(firstStatementAffectedInBlock); + + var newInnerMostBlock = Rewrite( + document, expression, newLocalName, document, oldInnerMostCommonBlock, allOccurrences, cancellationToken); + + var statements = new List<StatementSyntax>(); + statements.AddRange(newInnerMostBlock.Statements.Take(firstStatementAffectedIndex)); + statements.Add(declarationStatement); + statements.AddRange(newInnerMostBlock.Statements.Skip(firstStatementAffectedIndex)); + + var finalInnerMostBlock = newInnerMostBlock.WithStatements( + SyntaxFactory.List<StatementSyntax>(statements)); + + var newRoot = document.Root.ReplaceNode(oldInnerMostCommonBlock, finalInnerMostBlock); + return document.Document.WithSyntaxRoot(newRoot); + } + + private Document IntroduceLocalForSingleOccurrenceIntoBlock( + SemanticDocument document, + ExpressionSyntax expression, + NameSyntax localName, + LocalDeclarationStatementSyntax localDeclaration, + bool allOccurrences, + CancellationToken cancellationToken) + { + var oldStatement = expression.GetAncestorOrThis<StatementSyntax>(); + var newStatement = Rewrite( + document, expression, localName, document, oldStatement, allOccurrences, cancellationToken); + + if (oldStatement.IsParentKind(SyntaxKind.Block)) + { + var oldBlock = oldStatement.Parent as BlockSyntax; + var statementIndex = oldBlock.Statements.IndexOf(oldStatement); + + var newBlock = oldBlock.WithStatements(CreateNewStatementList( + oldBlock.Statements, localDeclaration, newStatement, statementIndex)); + + var newRoot = document.Root.ReplaceNode(oldBlock, newBlock); + return document.Document.WithSyntaxRoot(newRoot); + } + else if (oldStatement.IsParentKind(SyntaxKind.SwitchSection)) + { + var oldSwitchSection = oldStatement.Parent as SwitchSectionSyntax; + var statementIndex = oldSwitchSection.Statements.IndexOf(oldStatement); + + var newSwitchSection = oldSwitchSection.WithStatements(CreateNewStatementList( + oldSwitchSection.Statements, localDeclaration, newStatement, statementIndex)); + + var newRoot = document.Root.ReplaceNode(oldSwitchSection, newSwitchSection); + return document.Document.WithSyntaxRoot(newRoot); + } + else + { + // we need to introduce a block to put the original statement, along with + // the statement we're generating + var newBlock = SyntaxFactory.Block(localDeclaration, newStatement).WithAdditionalAnnotations(Formatter.Annotation); + + var newRoot = document.Root.ReplaceNode(oldStatement, newBlock); + return document.Document.WithSyntaxRoot(newRoot); + } + } + + private static SyntaxList<StatementSyntax> CreateNewStatementList( + SyntaxList<StatementSyntax> oldStatements, + LocalDeclarationStatementSyntax localDeclaration, + StatementSyntax newStatement, + int statementIndex) + { + return oldStatements.Take(statementIndex) + .Concat(localDeclaration.WithLeadingTrivia(oldStatements.Skip(statementIndex).First().GetLeadingTrivia())) + .Concat(newStatement.WithoutLeadingTrivia()) + .Concat(oldStatements.Skip(statementIndex + 1)) + .ToSyntaxList(); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs new file mode 100644 index 0000000000..4bf767422f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/CSharpIntroduceVariableService_IntroduceQueryLocal.cs @@ -0,0 +1,107 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public partial class CSharpIntroduceVariableService + { + private static bool IsAnyQueryClause(SyntaxNode node) + { + return node is QueryClauseSyntax || node is SelectOrGroupClauseSyntax; + } + + protected override Task<Document> IntroduceQueryLocalAsync( + SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, CancellationToken cancellationToken) + { + var newLocalNameToken = (SyntaxToken)GenerateUniqueLocalName(document, expression, isConstant: false, cancellationToken: cancellationToken); + var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); + + var letClause = SyntaxFactory.LetClause( + newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), + expression).WithAdditionalAnnotations(Formatter.Annotation); + + var oldOutermostQuery = expression.GetAncestorsOrThis<QueryExpressionSyntax>().LastOrDefault(); + var matches = FindMatches(document, expression, document, oldOutermostQuery, allOccurrences, cancellationToken); + var innermostClauses = new HashSet<SyntaxNode>( + matches.Select(expr => expr.GetAncestorsOrThis<SyntaxNode>().First(IsAnyQueryClause))); + + if (innermostClauses.Count == 1) + { + // If there was only one match, or all the matches came from the same + // statement, hten we want to place the declaration right above that + // statement. Note: we special case this because the statement we are going + // to go above might not be in a block and we may have to generate it + return Task.FromResult(IntroduceQueryLocalForSingleOccurrence( + document, expression, newLocalName, letClause, allOccurrences, cancellationToken)); + } + + var oldInnerMostCommonQuery = matches.FindInnermostCommonNode<QueryExpressionSyntax>(); + var newInnerMostQuery = Rewrite( + document, expression, newLocalName, document, oldInnerMostCommonQuery, allOccurrences, cancellationToken); + + var allAffectedClauses = new HashSet<SyntaxNode>(matches.SelectMany(expr => expr.GetAncestorsOrThis<SyntaxNode>().Where(IsAnyQueryClause))); + + var oldClauses = oldInnerMostCommonQuery.GetAllClauses(); + var newClauses = newInnerMostQuery.GetAllClauses(); + + var firstClauseAffectedInQuery = oldClauses.First(allAffectedClauses.Contains); + var firstClauseAffectedIndex = oldClauses.IndexOf(firstClauseAffectedInQuery); + + var finalClauses = newClauses.Take(firstClauseAffectedIndex) + .Concat(letClause) + .Concat(newClauses.Skip(firstClauseAffectedIndex)).ToList(); + + var finalQuery = newInnerMostQuery.WithAllClauses(finalClauses); + var newRoot = document.Root.ReplaceNode(oldInnerMostCommonQuery, finalQuery); + + return Task.FromResult(document.Document.WithSyntaxRoot(newRoot)); + } + + private Document IntroduceQueryLocalForSingleOccurrence( + SemanticDocument document, + ExpressionSyntax expression, + NameSyntax newLocalName, + LetClauseSyntax letClause, + bool allOccurrences, + CancellationToken cancellationToken) + { + var oldClause = expression.GetAncestors<SyntaxNode>().First(IsAnyQueryClause); + var newClause = Rewrite( + document, expression, newLocalName, document, oldClause, allOccurrences, cancellationToken); + + var oldQuery = (QueryBodySyntax)oldClause.Parent; + var newQuery = GetNewQuery(oldQuery, oldClause, newClause, letClause); + + var newRoot = document.Root.ReplaceNode(oldQuery, newQuery); + return document.Document.WithSyntaxRoot(newRoot); + } + + private static QueryBodySyntax GetNewQuery( + QueryBodySyntax oldQuery, + SyntaxNode oldClause, + SyntaxNode newClause, + LetClauseSyntax letClause) + { + var oldClauses = oldQuery.GetAllClauses(); + var oldClauseIndex = oldClauses.IndexOf(oldClause); + + var newClauses = oldClauses.Take(oldClauseIndex) + .Concat(letClause) + .Concat(newClause) + .Concat(oldClauses.Skip(oldClauseIndex + 1)).ToList(); + return oldQuery.WithAllClauses(newClauses); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/IntroduceVariableResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/IntroduceVariableResult.cs new file mode 100644 index 0000000000..cb90794993 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/IntroduceVariable/IntroduceVariableResult.cs @@ -0,0 +1,33 @@ +// 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 ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace ICSharpCode.NRefactory6.CSharp.Features.IntroduceVariable +{ + public class IntroduceVariableResult + { + public static readonly IntroduceVariableResult Failure = new IntroduceVariableResult(null); + + private readonly CodeRefactoring _codeRefactoring; + + public IntroduceVariableResult(CodeRefactoring codeRefactoring) + { + _codeRefactoring = codeRefactoring; + } + + public bool ContainsChanges + { + get + { + return _codeRefactoring != null; + } + } + + public CodeRefactoring GetCodeRefactoring(CancellationToken cancellationToken) + { + return _codeRefactoring; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.Rewriter.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.Rewriter.cs new file mode 100644 index 0000000000..340d1fd1d8 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.Rewriter.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.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.OrganizeImports +{ + public partial class CSharpOrganizeImportsService + { + private class Rewriter : CSharpSyntaxRewriter + { + private readonly bool _placeSystemNamespaceFirst; + public readonly IList<TextChange> TextChanges = new List<TextChange>(); + + public Rewriter(bool placeSystemNamespaceFirst) + { + _placeSystemNamespaceFirst = placeSystemNamespaceFirst; + } + + public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) + { + node = (CompilationUnitSyntax)base.VisitCompilationUnit(node); + + SyntaxList<ExternAliasDirectiveSyntax> organizedExternAliasList; + SyntaxList<UsingDirectiveSyntax> organizedUsingList; + UsingsAndExternAliasesOrganizer.Organize( + node.Externs, node.Usings, _placeSystemNamespaceFirst, + out organizedExternAliasList, out organizedUsingList); + + var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList); + if (node != result) + { + AddTextChange(node.Externs, organizedExternAliasList); + AddTextChange(node.Usings, organizedUsingList); + } + + return result; + } + + public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + node = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node); + + SyntaxList<ExternAliasDirectiveSyntax> organizedExternAliasList; + SyntaxList<UsingDirectiveSyntax> organizedUsingList; + UsingsAndExternAliasesOrganizer.Organize( + node.Externs, node.Usings, _placeSystemNamespaceFirst, + out organizedExternAliasList, out organizedUsingList); + + var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList); + if (node != result) + { + AddTextChange(node.Externs, organizedExternAliasList); + AddTextChange(node.Usings, organizedUsingList); + } + + return result; + } + + private void AddTextChange<TSyntax>(SyntaxList<TSyntax> list, SyntaxList<TSyntax> organizedList) + where TSyntax : SyntaxNode + { + if (list.Count > 0) + { + this.TextChanges.Add(new TextChange(GetTextSpan(list), GetNewText(organizedList))); + } + } + + private string GetNewText<TSyntax>(SyntaxList<TSyntax> organizedList) + where TSyntax : SyntaxNode + { + return string.Join(string.Empty, organizedList.Select(t => t.ToFullString())); + } + + private TextSpan GetTextSpan<TSyntax>(SyntaxList<TSyntax> list) + where TSyntax : SyntaxNode + { + return TextSpan.FromBounds(list.First().FullSpan.Start, list.Last().FullSpan.End); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.cs new file mode 100644 index 0000000000..956af82e90 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/OrganizeImports/CSharpOrganizeImportsService.cs @@ -0,0 +1,56 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.OrganizeImports +{ + public partial class CSharpOrganizeImportsService + { + public async Task<Document> OrganizeImportsAsync(Document document, bool placeSystemNamespaceFirst, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var rewriter = new Rewriter(placeSystemNamespaceFirst); + var newRoot = rewriter.Visit(root); + + return document.WithSyntaxRoot(newRoot); + } + + public string OrganizeImportsDisplayStringWithAccelerator + { + get + { + return Resources.OrganizeUsingsWithAccelerator; + } + } + + public string SortImportsDisplayStringWithAccelerator + { + get + { + return Resources.SortUsingsWithAccelerator; + } + } + + public string RemoveUnusedImportsDisplayStringWithAccelerator + { + get + { + return Resources.RemoveUnnecessaryUsingsWithAccelerator; + } + } + + public string SortAndRemoveUnusedImportsDisplayStringWithAccelerator + { + get + { + return Resources.RemoveAndSortUsingsWithAccelerator; + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingData.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingData.cs new file mode 100644 index 0000000000..16f5eb956f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingData.cs @@ -0,0 +1,229 @@ +// +// IParameterDataProvider.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using System.Linq; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + /// <summary> + /// Provides intellisense information for a collection of parametrized members. + /// </summary> + public interface IParameterHintingData + { + /// <summary> + /// Gets the symbol for which the parameter should be created. + /// </summary> + ISymbol Symbol { + get; + } + + int ParameterCount { + get; + } + + bool IsParameterListAllowed { + get; + } + + string GetParameterName (int currentParameter); + } + + public class ParameterHintingData : IParameterHintingData + { + public ISymbol Symbol { + get; + private set; + } + + public ParameterHintingData(IMethodSymbol symbol) + { + this.Symbol = symbol; + } + + public ParameterHintingData(IPropertySymbol symbol) + { + this.Symbol = symbol; + } + + static ImmutableArray<IParameterSymbol> GetParameterList (IParameterHintingData data) + { + var ms = data.Symbol as IMethodSymbol; + if (ms != null) + return ms.Parameters; + + var ps = data.Symbol as IPropertySymbol; + if (ps != null) + return ps.Parameters; + + return ImmutableArray<IParameterSymbol>.Empty; + } + + public string GetParameterName (int currentParameter) + { + var list = GetParameterList (this); + if (currentParameter < 0 || currentParameter >= list.Length) + throw new ArgumentOutOfRangeException ("currentParameter"); + return list [currentParameter].Name; + } + + public int ParameterCount { + get { + return GetParameterList(this).Length; + } + } + + public bool IsParameterListAllowed { + get { + var param = GetParameterList(this).LastOrDefault(); + return param != null && param.IsParams; + } + } + } + + public class DelegateParameterHintingData : IParameterHintingData + { + readonly IMethodSymbol invocationMethod; + + public ISymbol Symbol { + get; + private set; + } + + public DelegateParameterHintingData(ITypeSymbol symbol) + { + this.Symbol = symbol; + this.invocationMethod = symbol.GetDelegateInvokeMethod(); + } + + public string GetParameterName (int currentParameter) + { + var list = invocationMethod.Parameters; + if (currentParameter < 0 || currentParameter >= list.Length) + throw new ArgumentOutOfRangeException ("currentParameter"); + return list [currentParameter].Name; + } + + public int ParameterCount { + get { + return invocationMethod.Parameters.Length; + } + } + + public bool IsParameterListAllowed { + get { + var param = invocationMethod.Parameters.LastOrDefault(); + return param != null && param.IsParams; + } + } + } + + public class ArrayParameterHintingData : IParameterHintingData + { + readonly IArrayTypeSymbol arrayType; + + public ISymbol Symbol { + get { + return arrayType; + } + } + + public ArrayParameterHintingData(IArrayTypeSymbol arrayType) + { + this.arrayType = arrayType; + } + + public string GetParameterName (int currentParameter) + { + return null; + } + + public int ParameterCount { + get { + return arrayType.Rank; + } + } + + public bool IsParameterListAllowed { + get { + return false; + } + } + } + + public class TypeParameterHintingData : IParameterHintingData + { + public ISymbol Symbol { + get; + private set; + } + + public TypeParameterHintingData(IMethodSymbol symbol) + { + this.Symbol = symbol; + } + + public TypeParameterHintingData(INamedTypeSymbol symbol) + { + this.Symbol = symbol; + } + + static ImmutableArray<ITypeParameterSymbol> GetTypeParameterList (IParameterHintingData data) + { + var ms = data.Symbol as IMethodSymbol; + if (ms != null) + return ms.TypeParameters; + + var ps = data.Symbol as INamedTypeSymbol; + if (ps != null) + return ps.TypeParameters; + + return ImmutableArray<ITypeParameterSymbol>.Empty; + } + + + public string GetParameterName (int currentParameter) + { + var list = GetTypeParameterList (this); + if (currentParameter < 0 || currentParameter >= list.Length) + throw new ArgumentOutOfRangeException ("currentParameter"); + return list [currentParameter].Name; + } + + public int ParameterCount { + get { + return GetTypeParameterList(this).Length; + } + } + + public bool IsParameterListAllowed { + get { + return false; + } + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingDataFactory.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingDataFactory.cs new file mode 100644 index 0000000000..4995e19c3c --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/IParameterHintingDataFactory.cs @@ -0,0 +1,47 @@ +// +// IParameterCopmletionFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public interface IParameterHintingDataFactory + { + IParameterHintingData CreateConstructorProvider (IMethodSymbol constructor); + + IParameterHintingData CreateMethodDataProvider (IMethodSymbol method); + + IParameterHintingData CreateDelegateDataProvider (ITypeSymbol delegateType); + + IParameterHintingData CreateIndexerParameterDataProvider (IPropertySymbol indexer, SyntaxNode resolvedNode); + + IParameterHintingData CreateArrayDataProvider (IArrayTypeSymbol arrayType); + + IParameterHintingData CreateTypeParameterDataProvider (INamedTypeSymbol type); + + IParameterHintingData CreateTypeParameterDataProvider (IMethodSymbol method); + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingEngine.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingEngine.cs new file mode 100644 index 0000000000..42fc3fe4cd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingEngine.cs @@ -0,0 +1,255 @@ +// +// CSharpParameterCompletionEngine.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using System.Threading; +using System.CodeDom; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using System.Threading.Tasks; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class ParameterHintingEngine + { + readonly IParameterHintingDataFactory factory; + readonly Workspace workspace; + + public ParameterHintingEngine(Workspace workspace, IParameterHintingDataFactory factory) + { + if (workspace == null) + throw new ArgumentNullException("workspace"); + if (factory == null) + throw new ArgumentNullException("factory"); + this.workspace = workspace; + this.factory = factory; + } + + public async Task<ParameterHintingResult> GetParameterDataProviderAsync(Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken = default(CancellationToken)) + { + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree.IsInNonUserCode(position, cancellationToken)) + return ParameterHintingResult.Empty; + var tokenLeftOfPosition = tree.FindTokenOnLeftOfPosition (position, cancellationToken); + + if (tokenLeftOfPosition.IsKind (SyntaxKind.LessThanToken)) { + var startToken = tokenLeftOfPosition.GetPreviousToken(); + return HandleTypeParameterCase(semanticModel, startToken.Parent, cancellationToken); + } + + var context = SyntaxContext.Create(workspace, document, semanticModel, position, cancellationToken); + var targetParent = context.TargetToken.Parent; + var node = targetParent.Parent; + // case: identifier<arg1,| + if (node == null) { + if (context.LeftToken.Kind() == SyntaxKind.CommaToken) { + targetParent = context.LeftToken.GetPreviousToken().Parent; + node = targetParent.Parent; + if (node.Kind() == SyntaxKind.LessThanExpression) { + return HandleTypeParameterCase(semanticModel, ((BinaryExpressionSyntax)node).Left, cancellationToken); + + } + } + return ParameterHintingResult.Empty; + } + if (node.IsKind (SyntaxKind.Argument)) + node = node.Parent.Parent; + switch (node.Kind()) { + case SyntaxKind.Attribute: + return HandleAttribute(semanticModel, node, cancellationToken); + case SyntaxKind.ThisConstructorInitializer: + case SyntaxKind.BaseConstructorInitializer: + return HandleConstructorInitializer(semanticModel, node, cancellationToken); + case SyntaxKind.ObjectCreationExpression: + return HandleObjectCreationExpression(semanticModel, node, cancellationToken); + case SyntaxKind.InvocationExpression: + return HandleInvocationExpression(semanticModel, (InvocationExpressionSyntax)node, cancellationToken); + case SyntaxKind.ElementAccessExpression: + return HandleElementAccessExpression(semanticModel, (ElementAccessExpressionSyntax)node, cancellationToken); + } + return ParameterHintingResult.Empty; + } + + ParameterHintingResult HandleInvocationExpression(SemanticModel semanticModel, InvocationExpressionSyntax node, CancellationToken cancellationToken) + { + var info = semanticModel.GetSymbolInfo(node, cancellationToken); + var result = new ParameterHintingResult(node.SpanStart); + + var targetTypeInfo = semanticModel.GetTypeInfo (node.Expression); + if (targetTypeInfo.Type != null && targetTypeInfo.Type.TypeKind == TypeKind.Delegate) { + result.AddData (factory.CreateMethodDataProvider (targetTypeInfo.Type.GetDelegateInvokeMethod ())); + return result; + } + + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(node.SpanStart, cancellationToken); + ITypeSymbol type; + var ma = node.Expression as MemberAccessExpressionSyntax; + string name = null; + bool staticLookup = false; + if (ma != null) { + staticLookup = semanticModel.GetSymbolInfo (ma.Expression).Symbol is ITypeSymbol; + type = semanticModel.GetTypeInfo (ma.Expression).Type; + name = ma.Name.ToString (); + } else { + type = within as ITypeSymbol; + name = node.Expression.ToString (); + var sym = semanticModel.GetEnclosingSymbol (node.SpanStart, cancellationToken); + staticLookup = sym.IsStatic; + } + var addedMethods = new List<IMethodSymbol> (); + var filterMethod = new HashSet<IMethodSymbol> (); + for (;type != null; type = type.BaseType) { + foreach (var method in type.GetMembers ().OfType<IMethodSymbol> ().Where (m => m.Name == name)) { + if (staticLookup && !method.IsStatic) + continue; + if (method.OverriddenMethod != null) + filterMethod.Add (method.OverriddenMethod); + if (filterMethod.Contains (method)) + continue; + if (addedMethods.Any (added => SignatureComparer.HaveSameSignature (method, added, true))) + continue; + if (method.IsAccessibleWithin (within)) { + addedMethods.Add (method); + result.AddData (factory.CreateMethodDataProvider (method)); + } + } + } + if (info.Symbol != null && !addedMethods.Contains (info.Symbol)) { + if (!staticLookup || info.Symbol.IsStatic) + result.AddData (factory.CreateMethodDataProvider ((IMethodSymbol)info.Symbol)); + } + foreach (var candidate in info.CandidateSymbols) { + if (staticLookup && !candidate.IsStatic) + continue; + + if (!addedMethods.Contains (candidate) && candidate.IsAccessibleWithin (within)) + result.AddData (factory.CreateMethodDataProvider ((IMethodSymbol)candidate)); + } + return result; + } + + ParameterHintingResult HandleTypeParameterCase(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + var result = new ParameterHintingResult(node.SpanStart); + string typeName; + var gns = node as GenericNameSyntax; + if (gns != null) { + typeName = gns.Identifier.ToString (); + } else { + typeName = node.ToString (); + } + + foreach (var cand in semanticModel.LookupSymbols (node.SpanStart).OfType<INamedTypeSymbol> ()) { + if (cand.TypeParameters.Length == 0) + continue; + if (cand.Name == typeName || cand.GetFullName () == typeName) + result.AddData(factory.CreateTypeParameterDataProvider(cand)); + } + + if (result.Count == 0) { + foreach (var cand in semanticModel.LookupSymbols (node.SpanStart).OfType<IMethodSymbol> ()) { + if (cand.TypeParameters.Length == 0) + continue; + if (cand.Name == typeName) + result.AddData (factory.CreateTypeParameterDataProvider (cand)); + } + } + return result; + } + + ParameterHintingResult HandleAttribute(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + var info = semanticModel.GetSymbolInfo(node, cancellationToken); + var result = new ParameterHintingResult(node.SpanStart); + var resolvedMethod = info.Symbol as IMethodSymbol; + if (resolvedMethod != null) + result.AddData(factory.CreateConstructorProvider(resolvedMethod)); + result.AddRange(info.CandidateSymbols.OfType<IMethodSymbol>().Select (m => factory.CreateConstructorProvider(m))); + return result; + } + + ParameterHintingResult HandleConstructorInitializer(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + var info = semanticModel.GetSymbolInfo(node, cancellationToken); + var result = new ParameterHintingResult(node.SpanStart); + + var resolvedMethod = info.Symbol as IMethodSymbol; + if (resolvedMethod != null) + result.AddData(factory.CreateConstructorProvider(resolvedMethod)); + result.AddRange(info.CandidateSymbols.OfType<IMethodSymbol>().Select (m => factory.CreateConstructorProvider(m))); + return result; + } + + ParameterHintingResult HandleElementAccessExpression(SemanticModel semanticModel, ElementAccessExpressionSyntax node, CancellationToken cancellationToken) + { + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(node.SpanStart, cancellationToken); + + var targetTypeInfo = semanticModel.GetTypeInfo (node.Expression); + ITypeSymbol type = targetTypeInfo.Type; + if (type == null) + return ParameterHintingResult.Empty; + + var result = new ParameterHintingResult(node.SpanStart); + if (type.TypeKind == TypeKind.Array) { + result.AddData (factory.CreateArrayDataProvider ((IArrayTypeSymbol)type)); + return result; + } + + var addedProperties = new List<IPropertySymbol> (); + for (;type != null; type = type.BaseType) { + foreach (var indexer in type.GetMembers ().OfType<IPropertySymbol> ().Where (p => p.IsIndexer)) { + if (addedProperties.Any (added => SignatureComparer.HaveSameSignature (indexer, added, true))) + continue; + + if (indexer.IsAccessibleWithin (within)) { + addedProperties.Add (indexer); + result.AddData (factory.CreateIndexerParameterDataProvider (indexer, node)); + } + } + } + return result; + } + + ParameterHintingResult HandleObjectCreationExpression (SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + // var info = semanticModel.GetSymbolInfo(node, cancellationToken); + var result = new ParameterHintingResult(node.SpanStart); + var within = semanticModel.GetEnclosingNamedTypeOrAssembly(node.SpanStart, cancellationToken); + + var targetTypeInfo = semanticModel.GetTypeInfo (node); + if (targetTypeInfo.Type != null) { + foreach (IMethodSymbol c in targetTypeInfo.Type.GetMembers().OfType<IMethodSymbol>().Where(m => m.MethodKind == MethodKind.Constructor)) { + if (c.IsAccessibleWithin (within)) { + result.AddData(factory.CreateConstructorProvider(c)); + } + } + } + return result; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingResult.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingResult.cs new file mode 100644 index 0000000000..3b9283cecd --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterHintingResult.cs @@ -0,0 +1,87 @@ +// +// ParameterHintingResult.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; + +namespace ICSharpCode.NRefactory6.CSharp.Completion +{ + public class ParameterHintingResult : IReadOnlyList<IParameterHintingData> + { + public static readonly ParameterHintingResult Empty = new ParameterHintingResult (-1); + + readonly List<IParameterHintingData> data = new List<IParameterHintingData> (); + + /// <summary> + /// Gets the start offset of the parameter expression node. + /// </summary> + public int StartOffset { + get; + private set; + } + + #region IReadOnlyList<IParameterHintingData> implementation + + public IEnumerator<IParameterHintingData> GetEnumerator() + { + return data.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return ((System.Collections.IEnumerable)data).GetEnumerator(); + } + + public IParameterHintingData this[int index] { + get { + return data [index]; + } + } + + public int Count { + get { + return data.Count; + } + } + + #endregion + + internal protected ParameterHintingResult(int startOffset) + { + this.StartOffset = startOffset; + } + + internal protected void AddData (IParameterHintingData parameterHintingData) + { + data.Add(parameterHintingData); + } + + internal protected void AddRange (IEnumerable<IParameterHintingData> parameterHintingDataCollection) + { + data.AddRange(parameterHintingDataCollection); + } + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterUtil.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterUtil.cs new file mode 100644 index 0000000000..dbb75a56d9 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/ParameterHinting/ParameterUtil.cs @@ -0,0 +1,215 @@ +// +// ParameterUtil.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ICSharpCode.NRefactory6.CSharp +{ + public class ParameterIndexResult + { + public readonly static ParameterIndexResult Invalid = new ParameterIndexResult (null, -1); + public readonly static ParameterIndexResult First = new ParameterIndexResult (null, 0); + + public readonly string[] UsedNamespaceParameters; + public readonly int ParameterIndex; + + + internal ParameterIndexResult(string[] usedNamespaceParameters, int parameterIndex) + { + UsedNamespaceParameters = usedNamespaceParameters; + ParameterIndex = parameterIndex; + } + } + + public static class ParameterUtil + { + public static async Task<ParameterIndexResult> GetCurrentParameterIndex (Document document, int startOffset, int caretOffset, CancellationToken cancellationToken = default(CancellationToken)) + { + var usedNamedParameters =new List<string> (); + var parameter = new Stack<int> (); + var bracketStack = new Stack<Stack<int>> (); + bool inSingleComment = false, inString = false, inVerbatimString = false, inChar = false, inMultiLineComment = false; + var word = new StringBuilder (); + bool foundCharAfterOpenBracket = false; + + var text = await document.GetTextAsync(cancellationToken); + for (int i = startOffset; i < caretOffset; i++) { + char ch = text[i]; + char nextCh = i + 1 < text.Length ? text[i + 1] : '\0'; + if (ch == ':') { + usedNamedParameters.Add (word.ToString ()); + word.Length = 0; + } else if (char.IsLetterOrDigit (ch) || ch =='_') { + word.Append (ch); + } else if (char.IsWhiteSpace (ch)) { + + } else { + word.Length = 0; + } + if (!char.IsWhiteSpace(ch) && parameter.Count > 0) + foundCharAfterOpenBracket = true; + + switch (ch) { + case '{': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + bracketStack.Push (parameter); + parameter = new Stack<int> (); + break; + case '[': + case '(': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + parameter.Push (0); + break; + case '}': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + if (bracketStack.Count > 0) { + parameter = bracketStack.Pop (); + } else { + return ParameterIndexResult.Invalid; + } + break; + case ']': + case ')': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + if (parameter.Count > 0) { + parameter.Pop (); + } else { + return ParameterIndexResult.Invalid; + } + break; + case '<': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + parameter.Push (0); + break; + case '=': + if (nextCh == '>') { + i++; + continue; + } + break; + case '>': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + if (parameter.Count > 0) { + parameter.Pop (); + } + break; + case ',': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + if (parameter.Count > 0) { + parameter.Push (parameter.Pop () + 1); + } + break; + case '/': + if (inString || inChar || inVerbatimString) { + break; + } + if (nextCh == '/') { + i++; + inSingleComment = true; + } + if (nextCh == '*') { + inMultiLineComment = true; + } + break; + case '*': + if (inString || inChar || inVerbatimString || inSingleComment) { + break; + } + if (nextCh == '/') { + i++; + inMultiLineComment = false; + } + break; + case '@': + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) { + break; + } + if (nextCh == '"') { + i++; + inVerbatimString = true; + } + break; + case '\\': + if (inString || inChar) { + i++; + } + break; + case '"': + if (inSingleComment || inMultiLineComment || inChar) { + break; + } + if (inVerbatimString) { + if (nextCh == '"') { + i++; + break; + } + inVerbatimString = false; + break; + } + inString = !inString; + break; + case '\'': + if (inSingleComment || inMultiLineComment || inString || inVerbatimString) { + break; + } + inChar = !inChar; + break; + default: + if (NewLine.IsNewLine(ch)) { + inSingleComment = false; + inString = false; + inChar = false; + } + break; + } + } + if (parameter.Count != 1 || bracketStack.Count > 0) { + return ParameterIndexResult.Invalid; + } + if (!foundCharAfterOpenBracket) + return ParameterIndexResult.First; + return new ParameterIndexResult (usedNamedParameters.ToArray(), parameter.Pop() + 1); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.Rewriter.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.Rewriter.cs new file mode 100644 index 0000000000..04e9f3797b --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.Rewriter.cs @@ -0,0 +1,168 @@ +// 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.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.RemoveUnnecessaryImports +{ + public partial class CSharpRemoveUnnecessaryImportsService + { + private class Rewriter : CSharpSyntaxRewriter + { + private readonly ISet<UsingDirectiveSyntax> _unnecessaryUsingsDoNotAccessDirectly; + private readonly CancellationToken _cancellationToken; + + public Rewriter(ISet<UsingDirectiveSyntax> unnecessaryUsings, CancellationToken cancellationToken) + : base(visitIntoStructuredTrivia: true) + { + _unnecessaryUsingsDoNotAccessDirectly = unnecessaryUsings; + _cancellationToken = cancellationToken; + } + + public override SyntaxNode DefaultVisit(SyntaxNode node) + { + _cancellationToken.ThrowIfCancellationRequested(); + return base.DefaultVisit(node); + } + + private void ProcessUsings( + SyntaxList<UsingDirectiveSyntax> usings, + ISet<UsingDirectiveSyntax> usingsToRemove, + out SyntaxList<UsingDirectiveSyntax> finalUsings, + out SyntaxTriviaList finalTrivia) + { + var currentUsings = new List<UsingDirectiveSyntax>(usings); + + finalTrivia = default(SyntaxTriviaList); + for (int i = 0; i < usings.Count; i++) + { + if (usingsToRemove.Contains(usings[i])) + { + var currentUsing = currentUsings[i]; + currentUsings[i] = null; + + var leadingTrivia = currentUsing.GetLeadingTrivia(); + if (leadingTrivia.Any(t => t.Kind() != SyntaxKind.EndOfLineTrivia && t.Kind() != SyntaxKind.WhitespaceTrivia)) + { + // This using had trivia we want to preserve. If we're the last + // directive, then copy this trivia out so that our caller can place + // it on the next token. If there is any directive following us, + // then place it on that. + if (i < usings.Count - 1) + { + currentUsings[i + 1] = currentUsings[i + 1].WithPrependedLeadingTrivia(leadingTrivia); + } + else + { + finalTrivia = leadingTrivia; + } + } + } + } + + finalUsings = currentUsings.WhereNotNull().ToSyntaxList(); + } + + private ISet<UsingDirectiveSyntax> GetUsingsToRemove( + SyntaxList<UsingDirectiveSyntax> oldUsings, + SyntaxList<UsingDirectiveSyntax> newUsings) + { + var result = new HashSet<UsingDirectiveSyntax>(); + for (int i = 0; i < oldUsings.Count; i++) + { + if (_unnecessaryUsingsDoNotAccessDirectly.Contains(oldUsings[i])) + { + result.Add(newUsings[i]); + } + } + + return result; + } + + public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) + { + var compilationUnit = (CompilationUnitSyntax)base.VisitCompilationUnit(node); + + var usingsToRemove = GetUsingsToRemove(node.Usings, compilationUnit.Usings); + if (usingsToRemove.Count == 0) + { + return compilationUnit; + } + + SyntaxList<UsingDirectiveSyntax> finalUsings; + SyntaxTriviaList finalTrivia; + ProcessUsings(compilationUnit.Usings, usingsToRemove, out finalUsings, out finalTrivia); + + // If there was any left over trivia, then attach it to the next token that + // follows the usings. + if (finalTrivia.Count > 0) + { + var nextToken = compilationUnit.Usings.Last().GetLastToken().GetNextToken(); + compilationUnit = compilationUnit.ReplaceToken(nextToken, nextToken.WithPrependedLeadingTrivia(finalTrivia)); + } + + var resultCompilationUnit = compilationUnit.WithUsings(finalUsings); + if (finalUsings.Count == 0 && + resultCompilationUnit.Externs.Count == 0 && + resultCompilationUnit.Members.Count >= 1) + { + // We've removed all the usings and now the first thing in the namespace is a + // type. In this case, remove any newlines preceding the type. + var firstToken = resultCompilationUnit.GetFirstToken(); + var newFirstToken = StripNewLines(firstToken); + resultCompilationUnit = resultCompilationUnit.ReplaceToken(firstToken, newFirstToken); + } + + return resultCompilationUnit; + } + + public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + var namespaceDeclaration = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node); + var usingsToRemove = GetUsingsToRemove(node.Usings, namespaceDeclaration.Usings); + if (usingsToRemove.Count == 0) + { + return namespaceDeclaration; + } + + SyntaxList<UsingDirectiveSyntax> finalUsings; + SyntaxTriviaList finalTrivia; + ProcessUsings(namespaceDeclaration.Usings, usingsToRemove, out finalUsings, out finalTrivia); + + // If there was any left over trivia, then attach it to the next token that + // follows the usings. + if (finalTrivia.Count > 0) + { + var nextToken = namespaceDeclaration.Usings.Last().GetLastToken().GetNextToken(); + namespaceDeclaration = namespaceDeclaration.ReplaceToken(nextToken, nextToken.WithPrependedLeadingTrivia(finalTrivia)); + } + + var resultNamespace = namespaceDeclaration.WithUsings(finalUsings); + if (finalUsings.Count == 0 && + resultNamespace.Externs.Count == 0 && + resultNamespace.Members.Count >= 1) + { + // We've removed all the usings and now the first thing in the namespace is a + // type. In this case, remove any newlines preceding the type. + var firstToken = resultNamespace.Members.First().GetFirstToken(); + var newFirstToken = StripNewLines(firstToken); + resultNamespace = resultNamespace.ReplaceToken(firstToken, newFirstToken); + } + + return resultNamespace; + } + + private static SyntaxToken StripNewLines(SyntaxToken firstToken) + { + return firstToken.WithLeadingTrivia(firstToken.LeadingTrivia.SkipWhile(t => t.Kind() == SyntaxKind.EndOfLineTrivia)); + } + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.cs new file mode 100644 index 0000000000..5d207a7780 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsService.cs @@ -0,0 +1,115 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; + +namespace ICSharpCode.NRefactory6.CSharp.Features.RemoveUnnecessaryImports +{ + public partial class CSharpRemoveUnnecessaryImportsService + { + public static IEnumerable<SyntaxNode> GetUnnecessaryImports(SemanticModel semanticModel, SyntaxNode root, CancellationToken cancellationToken) + { + var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken); + if (!diagnostics.Any()) + { + return null; + } + + var unnecessaryImports = new HashSet<UsingDirectiveSyntax>(); + + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Id == "CS8019") + { + var node = root.FindNode(diagnostic.Location.SourceSpan) as UsingDirectiveSyntax; + + if (node != null) + { + unnecessaryImports.Add(node); + } + } + } + + if (cancellationToken.IsCancellationRequested || !unnecessaryImports.Any()) + { + return null; + } + + return unnecessaryImports; + } + + public Document RemoveUnnecessaryImports(Document document, SemanticModel model, SyntaxNode root, CancellationToken cancellationToken) + { + var unnecessaryImports = GetUnnecessaryImports(model, root, cancellationToken) as ISet<UsingDirectiveSyntax>; + if (unnecessaryImports == null) + { + return document; + } + + var oldRoot = (CompilationUnitSyntax)root; + if (unnecessaryImports.Any(import => oldRoot.OverlapsHiddenPosition(cancellationToken))) + { + return document; + } + + var newRoot = (CompilationUnitSyntax)new Rewriter(unnecessaryImports, cancellationToken).Visit(oldRoot); + if (cancellationToken.IsCancellationRequested) + { + return null; + } + + return document.WithSyntaxRoot(FormatResult(document, newRoot, cancellationToken)); + } + + private SyntaxNode FormatResult(Document document, CompilationUnitSyntax newRoot, CancellationToken cancellationToken) + { + var spans = new List<TextSpan>(); + AddFormattingSpans(newRoot, spans, cancellationToken); + return Formatter.Format(newRoot, spans, document.Project.Solution.Workspace, document.Project.Solution.Workspace.Options, cancellationToken: cancellationToken); + } + + private void AddFormattingSpans( + CompilationUnitSyntax compilationUnit, + List<TextSpan> spans, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + spans.Add(TextSpan.FromBounds(0, GetEndPosition(compilationUnit, compilationUnit.Members))); + + foreach (var @namespace in compilationUnit.Members.OfType<NamespaceDeclarationSyntax>()) + { + AddFormattingSpans(@namespace, spans, cancellationToken); + } + } + + private void AddFormattingSpans( + NamespaceDeclarationSyntax namespaceMember, + List<TextSpan> spans, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + spans.Add(TextSpan.FromBounds(namespaceMember.SpanStart, GetEndPosition(namespaceMember, namespaceMember.Members))); + + foreach (var @namespace in namespaceMember.Members.OfType<NamespaceDeclarationSyntax>()) + { + AddFormattingSpans(@namespace, spans, cancellationToken); + } + } + + private int GetEndPosition(SyntaxNode container, SyntaxList<MemberDeclarationSyntax> list) + { + return list.Count > 0 ? list[0].SpanStart : container.Span.End; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/SemanticHighlighting/SemanticHighlightingVisitor.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/SemanticHighlighting/SemanticHighlightingVisitor.cs new file mode 100644 index 0000000000..08a35d5f77 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Features/SemanticHighlighting/SemanticHighlightingVisitor.cs @@ -0,0 +1,539 @@ +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.NRefactory6.CSharp.Analysis +{ + /// <summary> + /// C# Semantic highlighter. + /// </summary> + public abstract class SemanticHighlightingVisitor<TColor> : CSharpSyntaxWalker + { + protected CancellationToken cancellationToken = default(CancellationToken); + + protected TColor defaultTextColor; + protected TColor referenceTypeColor; + protected TColor valueTypeColor; + protected TColor interfaceTypeColor; + protected TColor enumerationTypeColor; + protected TColor typeParameterTypeColor; + protected TColor delegateTypeColor; + + protected TColor methodCallColor; + protected TColor methodDeclarationColor; + + protected TColor eventDeclarationColor; + protected TColor eventAccessColor; + + protected TColor propertyDeclarationColor; + protected TColor propertyAccessColor; + + protected TColor fieldDeclarationColor; + protected TColor fieldAccessColor; + + protected TColor variableDeclarationColor; + protected TColor variableAccessColor; + + protected TColor parameterDeclarationColor; + protected TColor parameterAccessColor; + + protected TColor valueKeywordColor; + protected TColor externAliasKeywordColor; + protected TColor varKeywordTypeColor; + protected TColor nameofKeywordColor; + protected TColor whenKeywordColor; + + /// <summary> + /// Used for 'in' modifiers on type parameters. + /// </summary> + /// <remarks> + /// 'in' may have a different color when used with 'foreach'. + /// 'out' is not colored by semantic highlighting, as syntax highlighting can already detect it as a parameter modifier. + /// </remarks> + protected TColor parameterModifierColor; + + /// <summary> + /// Used for inactive code (excluded by preprocessor or ConditionalAttribute) + /// </summary> + protected TColor inactiveCodeColor; + + protected TColor stringFormatItemColor; + + protected TextSpan region; + + protected SemanticModel semanticModel; + // bool isInAccessorContainingValueParameter; + + protected abstract void Colorize (TextSpan span, TColor color); + + protected SemanticHighlightingVisitor (SemanticModel semanticModel) + { + this.semanticModel = semanticModel; + } + + #region Colorize helper methods + protected void Colorize (SyntaxNode node, TColor color) + { + if (node == null) + return; + Colorize (node.Span, color); + } + + protected void Colorize (SyntaxToken node, TColor color) + { + Colorize (node.Span, color); + } + + #endregion + public override void VisitRefTypeExpression (Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeExpressionSyntax node) + { + base.VisitRefTypeExpression (node); + Colorize (node.Expression, referenceTypeColor); + } + + public override void VisitCompilationUnit (CompilationUnitSyntax node) + { + var startNode = node.DescendantNodesAndSelf (n => region.Start <= n.SpanStart).FirstOrDefault (); + if (startNode == node || startNode == null) { + base.VisitCompilationUnit (node); + } else { + this.Visit (startNode); + } + } + + public override void Visit (SyntaxNode node) + { + if (node.Span.End < region.Start) + return; + if (node.Span.Start > region.End) + return; + base.Visit(node); + } + + void HighlightStringFormatItems(LiteralExpressionSyntax expr) + { + if (!expr.Token.IsKind(SyntaxKind.StringLiteralToken)) + return; + var text = expr.Token.Text; + int start = -1; + for (int i = 0; i < text.Length; i++) { + char ch = text [i]; + + if (NewLine.GetDelimiterType(ch, i + 1 < text.Length ? text [i + 1] : '\0') != UnicodeNewline.Unknown) { + continue; + } + + if (ch == '{' && start < 0) { + char next = i + 1 < text.Length ? text [i + 1] : '\0'; + if (next == '{') { + i++; + continue; + } + start = i; + } + + if (ch == '}' && start >= 0) { + Colorize(new TextSpan (expr.SpanStart + start, i - start + 1), stringFormatItemColor); + start = -1; + } + } + } + + public override void VisitInvocationExpression(InvocationExpressionSyntax node) + { + var symbolInfo = semanticModel.GetSymbolInfo(node.Expression, cancellationToken); + + if (IsInactiveConditional (symbolInfo.Symbol) || IsEmptyPartialMethod(symbolInfo.Symbol, cancellationToken)) { + // mark the whole invocation statement as inactive code + Colorize(node.Span, inactiveCodeColor); + return; + } + if (node.Expression.IsKind (SyntaxKind.IdentifierName) && symbolInfo.Symbol == null) { + var id = (IdentifierNameSyntax)node.Expression; + if (id.Identifier.ValueText == "nameof") { + Colorize(id.Span, nameofKeywordColor); + } + } + + ExpressionSyntax fmtArgumets; + IList<ExpressionSyntax> args; + if (node.ArgumentList.Arguments.Count > 1 && FormatStringHelper.TryGetFormattingParameters(semanticModel, node, out fmtArgumets, out args, null, cancellationToken)) { + var expr = node.ArgumentList.Arguments.First(); + if (expr != null) { + var literalExpressionSyntax = expr.Expression as LiteralExpressionSyntax; + if (literalExpressionSyntax != null) + HighlightStringFormatItems(literalExpressionSyntax); + } + } + + base.VisitInvocationExpression(node); + } + + bool IsInactiveConditional(ISymbol member) + { + if (member == null || member.Kind != SymbolKind.Method) + return false; + var method = member as IMethodSymbol; + if (method.ReturnType.SpecialType != SpecialType.System_Void) + return false; + + var om = method.OverriddenMethod; + while (om != null) { + if (IsInactiveConditional (om.GetAttributes())) + return true; + om = om.OverriddenMethod; + } + + return IsInactiveConditional(member.GetAttributes()); + } + + bool IsInactiveConditional(System.Collections.Immutable.ImmutableArray<AttributeData> attributes) + { + foreach (var attr in attributes) { + if (attr.AttributeClass.Name == "ConditionalAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System.Diagnostics" && attr.ConstructorArguments.Length == 1) { + string symbol = attr.ConstructorArguments[0].Value as string; + if (symbol != null) { + var options = (CSharpParseOptions)semanticModel.SyntaxTree.Options; + if (!options.PreprocessorSymbolNames.Contains(symbol)) + return true; + } + } + } + return false; + } + + static bool IsEmptyPartialMethod(ISymbol member, CancellationToken cancellationToken = default(CancellationToken)) + { + var method = member as IMethodSymbol; + if (method == null || method.IsDefinedInMetadata ()) + return false; + foreach (var r in method.DeclaringSyntaxReferences) { + var node = r.GetSyntax (cancellationToken) as MethodDeclarationSyntax; + if (node == null) + continue; + if (node.Body != null || !node.Modifiers.Any(m => m.IsKind (SyntaxKind.PartialKeyword))) + return false; + } + + return true; + } + + public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node) + { + base.VisitExternAliasDirective(node); + Colorize (node.AliasKeyword.Span, externAliasKeywordColor); + } + + public override void VisitGenericName(GenericNameSyntax node) + { + base.VisitGenericName(node); + var info = semanticModel.GetSymbolInfo(node, cancellationToken); + TColor color; + if (TryGetSymbolColor(info, out color)) { + Colorize(node.Identifier.Span, color); + } + } + + public override void VisitStructDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.StructDeclarationSyntax node) + { + base.VisitStructDeclaration(node); + Colorize(node.Identifier, valueTypeColor); + } + + public override void VisitInterfaceDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.InterfaceDeclarationSyntax node) + { + base.VisitInterfaceDeclaration(node); + Colorize(node.Identifier, interfaceTypeColor); + } + + public override void VisitClassDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax node) + { + base.VisitClassDeclaration(node); + Colorize(node.Identifier, referenceTypeColor); + } + + public override void VisitEnumDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.EnumDeclarationSyntax node) + { + base.VisitEnumDeclaration(node); + Colorize(node.Identifier, enumerationTypeColor); + } + + public override void VisitDelegateDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.DelegateDeclarationSyntax node) + { + base.VisitDelegateDeclaration(node); + Colorize(node.Identifier, delegateTypeColor); + } + + public override void VisitTypeParameter(Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterSyntax node) + { + base.VisitTypeParameter(node); + Colorize(node.Identifier, typeParameterTypeColor); + } + + public override void VisitEventDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax node) + { + base.VisitEventDeclaration(node); + Colorize(node.Identifier, eventDeclarationColor); + } + + public override void VisitMethodDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax node) + { + base.VisitMethodDeclaration(node); + Colorize(node.Identifier, methodDeclarationColor); + } + + public override void VisitPropertyDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyDeclarationSyntax node) + { + base.VisitPropertyDeclaration(node); + Colorize(node.Identifier, propertyDeclarationColor); + } + + public override void VisitParameter(Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax node) + { + base.VisitParameter(node); + Colorize(node.Identifier, parameterDeclarationColor); + } + + public override void VisitIdentifierName(Microsoft.CodeAnalysis.CSharp.Syntax.IdentifierNameSyntax node) + { + base.VisitIdentifierName(node); + if (node.IsVar) { + var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken); + if (node.Parent is ForEachStatementSyntax) { + var sym = semanticModel.GetDeclaredSymbol(node.Parent, cancellationToken); + if (sym != null) { + Colorize(node.Span, varKeywordTypeColor); + return; + } + } + var vds = node.Parent as VariableDeclarationSyntax; + if (vds != null && vds.Variables.Count == 1) { + // var sym = vds.Variables[0].Initializer != null ? vds.Variables[0].Initializer.Value as LiteralExpressionSyntax : null; + if (symbolInfo.Symbol == null || symbolInfo.Symbol.Name != "var") { + Colorize(node.Span, varKeywordTypeColor); + return; + } + } + } + + switch (node.Identifier.Text) { + case "add": + case "async": + case "await": + case "get": + case "partial": + case "remove": + case "set": + case "where": + case "yield": + case "from": + case "select": + case "group": + case "into": + case "orderby": + case "join": + case "let": + case "on": + case "equals": + case "by": + case "ascending": + case "descending": + case "dynamic": + // Reset color of contextual keyword to default if it's used as an identifier. + // Note that this method does not get called when 'var' or 'dynamic' is used as a type, + // because types get highlighted with valueTypeColor/referenceTypeColor instead. + Colorize(node.Span, defaultTextColor); + break; + case "global": +// // Reset color of 'global' keyword to default unless its used as part of 'global::'. +// MemberType parentMemberType = identifier.Parent as MemberType; +// if (parentMemberType == null || !parentMemberType.IsDoubleColon) + Colorize(node.Span, defaultTextColor); + break; + } + // "value" is handled in VisitIdentifierExpression() + // "alias" is handled in VisitExternAliasDeclaration() + + TColor color; + if (TryGetSymbolColor (semanticModel.GetSymbolInfo (node, cancellationToken), out color)) { + if (node.Parent is AttributeSyntax || node.Parent is QualifiedNameSyntax && node.Parent.Parent is AttributeSyntax) + color = referenceTypeColor; + Colorize (node.Span, color); + } + } + + bool TryGetSymbolColor(SymbolInfo info, out TColor color) + { + var symbol = info.Symbol; + + if (symbol == null) { + color = default(TColor); + return false; + } + + switch (symbol.Kind) { + case SymbolKind.Field: + color = fieldAccessColor; + return true; + case SymbolKind.Event: + color = eventAccessColor; + return true; + case SymbolKind.Parameter: + var param = (IParameterSymbol)symbol; + var method = param.ContainingSymbol as IMethodSymbol; + if (param.Name == "value" && method != null && ( + method.MethodKind == MethodKind.EventAdd || + method.MethodKind == MethodKind.EventRaise || + method.MethodKind == MethodKind.EventRemove || + method.MethodKind == MethodKind.PropertySet)) { + color = valueKeywordColor; + } else { + color = parameterAccessColor; + } + return true; + case SymbolKind.RangeVariable: + color = variableAccessColor; + return true; + case SymbolKind.Method: + color = methodCallColor; + return true; + case SymbolKind.Property: + color = propertyAccessColor; + return true; + case SymbolKind.TypeParameter: + color = typeParameterTypeColor; + return true; + case SymbolKind.Local: + color = variableAccessColor; + return true; + case SymbolKind.NamedType: + var type = (INamedTypeSymbol)symbol; + switch (type.TypeKind) { + case TypeKind.Class: + color = referenceTypeColor; + break; + case TypeKind.Delegate: + color = delegateTypeColor; + break; + case TypeKind.Enum: + color = enumerationTypeColor; + break; + case TypeKind.Error: + color = default(TColor); + return false; + case TypeKind.Interface: + color = interfaceTypeColor; + break; + case TypeKind.Struct: + color = valueTypeColor; + break; + case TypeKind.TypeParameter: + color = typeParameterTypeColor; + break; + default: + color = referenceTypeColor; + break; + } + return true; + } + color = default(TColor); + return false; + } + + public override void VisitVariableDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax node) + { + base.VisitVariableDeclaration(node); + TColor color; + if (node.Parent.IsKind(SyntaxKind.EventFieldDeclaration)) + color = eventDeclarationColor; + else if (node.Parent.IsKind(SyntaxKind.FieldDeclaration)) + color = fieldDeclarationColor; + else + color = variableDeclarationColor; + + foreach (var declarations in node.Variables) { + // var info = semanticModel.GetTypeInfo(declarations, cancellationToken); + Colorize(declarations.Identifier, color); + } + } + +// public override void VisitComment(Comment comment) +// { +// if (comment.CommentType == CommentType.InactiveCode) { +// Colorize(comment, inactiveCodeColor); +// } +// } +// +// public override void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective) +// { +// } + +// public override void VisitAttribute(Microsoft.CodeAnalysis.CSharp.Syntax.AttributeSyntax node) +// { +// var symbol = semanticModel.GetDeclaredSymbol(node) as INamedTypeSymbol; +// if (symbol != null && IsInactiveConditional(symbol)) { +// Colorize(attribute, inactiveCodeColor); +// } else { +// base.VisitAttribute(node); +// } +// } + + public override void VisitInitializerExpression(Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax node) + { + base.VisitInitializerExpression(node); + + foreach (var a in node.Expressions) { + // TODO +// var namedElement = a as NamedExpression; +// if (namedElement != null) { +// var result = resolver.Resolve (namedElement, cancellationToken); +// if (result.IsError) +// Colorize (namedElement.NameToken, syntaxErrorColor); +// namedElement.Expression.AcceptVisitor (this); +// } + } + } + + int blockDepth; + + public override void VisitBlock(Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax node) + { + blockDepth++; + cancellationToken.ThrowIfCancellationRequested (); + base.VisitBlock(node); + blockDepth--; + } + + public override void VisitCatchFilterClause (CatchFilterClauseSyntax node) + { + if (!node.WhenKeyword.IsMissing) { + Colorize(node.WhenKeyword, whenKeywordColor); + } + base.VisitCatchFilterClause (node); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs index ceefda25db..e752e330da 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs @@ -2468,7 +2468,7 @@ namespace MonoDevelop.Debugger public event EventHandler CompletionListClosed; } - class DebugCompletionData : MonoDevelop.Ide.CodeCompletion.CompletionData, ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData + class DebugCompletionData : MonoDevelop.Ide.CodeCompletion.CompletionData { readonly CompletionItem item; @@ -2494,42 +2494,5 @@ namespace MonoDevelop.Debugger return item.Name; } } - - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler keyHandler; - - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionKeyHandler ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.KeyHandler { - get { - return keyHandler; - } - } - - void ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.AddOverload (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData data) - { - base.AddOverload ((MonoDevelop.Ide.CodeCompletion.CompletionData)data); - } - - IEnumerable<ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData> ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.OverloadedData { - get { - return OverloadedData.OfType<ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData> (); - } - } - - ICSharpCode.NRefactory6.CSharp.Completion.ICompletionCategory ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.CompletionCategory { - get { - return (ICSharpCode.NRefactory6.CSharp.Completion.ICompletionCategory)base.CompletionCategory; - } - set { - base.CompletionCategory = (CompletionCategory)value; - } - } - - ICSharpCode.NRefactory6.CSharp.Completion.DisplayFlags ICSharpCode.NRefactory6.CSharp.Completion.ICompletionData.DisplayFlags { - get { - return (ICSharpCode.NRefactory6.CSharp.Completion.DisplayFlags)base.DisplayFlags; - } - set { - base.DisplayFlags = (DisplayFlags)value; - } - } } } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs index 7461d80052..2af9d4e374 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs @@ -38,7 +38,6 @@ using Microsoft.CodeAnalysis.Text; using MonoDevelop.Ide.TypeSystem; using MonoDevelop.Ide; using Microsoft.CodeAnalysis.CodeActions; -using ICSharpCode.NRefactory6.CSharp.Refactoring; using MonoDevelop.AnalysisCore; using MonoDevelop.Ide.Editor; using MonoDevelop.Components; @@ -47,6 +46,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis; using System.Reflection; +using RefactoringEssentials; namespace MonoDevelop.CodeActions { diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs index 5692ba40e1..a58a70929e 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs @@ -36,7 +36,6 @@ using MonoDevelop.Core; using MonoDevelop.Ide.Editor; using MonoDevelop.CodeIssues; using Mono.Addins; -using ICSharpCode.NRefactory6.CSharp.Refactoring; using MonoDevelop.Core.Text; using System.Linq; diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs index 0d38d291ba..16da9df145 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs @@ -27,21 +27,17 @@ using System; using System.Linq; using System.Collections.Generic; -using System.Threading; -using MonoDevelop.CodeIssues; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.CodeFixes; -using ICSharpCode.NRefactory6.CSharp.Refactoring; using MonoDevelop.Core; -using System.Threading.Tasks; -using MonoDevelop.Ide.Editor; using MonoDevelop.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using ICSharpCode.NRefactory6.CSharp; +using RefactoringEssentials; namespace MonoDevelop.CodeIssues { - + class AnalyzersFromAssembly { public List<CodeDiagnosticDescriptor> Analyzers; @@ -70,7 +66,7 @@ namespace MonoDevelop.CodeIssues var assemblyName = asm.GetName ().Name; if (assemblyName == "MonoDevelop.AspNet" || assemblyName == "Microsoft.CodeAnalysis.CSharp" || - assemblyName != "NR6Pack" && + assemblyName != "RefactoringEssentials" && !(asm.GetReferencedAssemblies ().Any (a => a.Name == diagnosticAnalyzerAssembly) && asm.GetReferencedAssemblies ().Any (a => a.Name == "MonoDevelop.Ide"))) return; } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/DiagnosticResult.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/DiagnosticResult.cs index f14a822101..830d8483bb 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/DiagnosticResult.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/DiagnosticResult.cs @@ -26,9 +26,9 @@ //#define PROFILE using System; using MonoDevelop.AnalysisCore; -using ICSharpCode.NRefactory6.CSharp; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis; +using RefactoringEssentials; namespace MonoDevelop.CodeIssues { diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj index 77a3178061..8d1e9bd31b 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj @@ -274,9 +274,9 @@ <Name>MonoDevelop.SourceEditor</Name> <Private>False</Private> </ProjectReference> - <ProjectReference Include="..\..\..\external\NRefactory6\NR6Pack\NR6Pack.csproj"> + <ProjectReference Include="..\..\..\external\RefactoringEssentials\RefactoringEssentials\RefactoringEssentials.csproj"> <Project>{C465A5DC-AD28-49A2-89C0-F81838814A7E}</Project> - <Name>NR6Pack</Name> + <Name>RefactoringEssentials</Name> <Private>False</Private> </ProjectReference> </ItemGroup> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionData.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionData.cs index 77490702aa..8bd55966cf 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionData.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionData.cs @@ -168,4 +168,11 @@ namespace MonoDevelop.Ide.CodeCompletion return ApplyDiplayFlagsFormatting (GLib.Markup.EscapeText (DisplayText)); } } + + public interface ISymbolCompletionData + { + Microsoft.CodeAnalysis.ISymbol Symbol { + get; + } + } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWidget.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWidget.cs index c60add505c..0eb0a0612d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWidget.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWidget.cs @@ -30,7 +30,6 @@ using System.Linq; using Gdk; using Gtk; using Pango; -using ICSharpCode.NRefactory6.CSharp.Completion; using MonoDevelop.Components; using MonoDevelop.Ide.Fonts; using MonoDevelop.Ide.Gui.Content; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWindow.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWindow.cs index dd329a7475..653615f2bf 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWindow.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ListWindow.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; using MonoDevelop.Core.Text; -using ICSharpCode.NRefactory6.CSharp.Completion; using MonoDevelop.Ide.Gui.Content; using System.Linq; using Gdk; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ParameterHintingData.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ParameterHintingData.cs index 616c158ad8..638f156bd1 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ParameterHintingData.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/ParameterHintingData.cs @@ -25,7 +25,6 @@ // THE SOFTWARE. using System; using ICSharpCode.NRefactory.Completion; -using ICSharpCode.NRefactory6.CSharp.Completion; using Microsoft.CodeAnalysis; using System.Collections.Immutable; using System.Linq; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/ExpansionObject.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/ExpansionObject.cs index 946cca22c3..cba9e81770 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/ExpansionObject.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/ExpansionObject.cs @@ -36,7 +36,6 @@ using Microsoft.CodeAnalysis; using System.Threading.Tasks; using System.Linq; using ICSharpCode.NRefactory6.CSharp; -using ICSharpCode.NRefactory6.CSharp.Completion; using MonoDevelop.Ide.Editor; using MonoDevelop.Ide.Editor.Extension; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index 450cef6250..31ea34124d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -202,14 +202,14 @@ <Project>{7E891659-45F3-42B5-B940-A728780CCAE9}</Project> <Name>ICSharpCode.NRefactory6.CSharp</Name> </ProjectReference> - <ProjectReference Include="..\..\..\external\NRefactory6\NR6Pack\NR6Pack.csproj"> - <Project>{C465A5DC-AD28-49A2-89C0-F81838814A7E}</Project> - <Name>NR6Pack</Name> - </ProjectReference> <ProjectReference Include="..\..\..\external\nrefactory\ICSharpCode.NRefactory.Cecil\ICSharpCode.NRefactory.Cecil.csproj"> <Project>{2B8F4F83-C2B3-4E84-A27B-8DEE1BE0E006}</Project> <Name>ICSharpCode.NRefactory.Cecil</Name> </ProjectReference> + <ProjectReference Include="..\..\..\external\RefactoringEssentials\RefactoringEssentials\RefactoringEssentials.csproj"> + <Project>{C465A5DC-AD28-49A2-89C0-F81838814A7E}</Project> + <Name>RefactoringEssentials</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <EmbeddedResource Include="templates\AppConfigFile.xft.xml"> |